diff --git a/package.json b/package.json index 0ed12790..d3cde959 100755 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@craco/craco": "^7.0.0", "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", - "@frontend/kitui": "^1.0.110", + "@frontend/kitui": "1.0.110", "@frontend/squzanswerer": "^1.0.57", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", diff --git a/src/App.tsx b/src/App.tsx index 3fe1ff21..059ccfd3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { clearAuthToken, getMessageFromFetchError, handleComponentError, UserAccount, useTicketsFetcher, useUserFetcher } from "@frontend/kitui"; +import { clearAuthToken, createMakeRequestConfig, getMessageFromFetchError, handleComponentError, UserAccount, useTicketsFetcher, useUserFetcher } from "@frontend/kitui"; import type { OriginalUserAccount } from "@root/user"; import { clearUserData, setCustomerAccount, setUser, setUserAccount, useUserStore } from "@root/user"; import ContactFormModal from "@ui_kit/ContactForm"; @@ -27,6 +27,7 @@ import Debug from "./pages/Debug"; import { setTicketData, setTickets, useTicketStore } from "./stores/ticket"; import { parseAxiosError } from "./utils/parse-error"; import { ErrorBoundary } from "react-error-boundary"; +import { handleLogoutClick } from "./utils/HandleLogoutClick"; const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull")); const QuizGallery = lazy(() => import("./pages/createQuize/QuizGallery")); @@ -42,6 +43,12 @@ const PersonalizationAI = lazy(() => import("./pages/PersonalizationAI/Personali let params = new URLSearchParams(document.location.search); const isTest = Boolean(params.get("test")) +createMakeRequestConfig( + handleLogoutClick, + (error, info, getTickets) => handleComponentError(error, info, getTickets()), + () => useTicketStore.getState().tickets +); + const routeslink = [ { path: "/edit", @@ -185,7 +192,7 @@ export default function App() { return ( handleComponentError(error, info, tickets)} + onError={(error, info) => handleComponentError(error, info, () => useTicketStore.getState().tickets)} > {amoAccount && } diff --git a/src/ui_kit/FloatingSupportChat/Chat.tsx b/src/ui_kit/FloatingSupportChat/Chat.tsx index ab7f231f..5e6046d1 100644 --- a/src/ui_kit/FloatingSupportChat/Chat.tsx +++ b/src/ui_kit/FloatingSupportChat/Chat.tsx @@ -30,7 +30,18 @@ interface Props { sendFile: (a: File | undefined) => Promise; } -const greetingMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; +const greetingMessage: TicketMessage = { + id: "greeting", + ticket_id: "", + user_id: "system", + session_id: "", + message: "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут", + files: [], + shown: {}, + request_screenshot: "", + created_at: new Date().toISOString(), + system: false +}; export default function Chat({ open = false, @@ -197,10 +208,7 @@ export default function Chat({ > {ticket.sessionData?.ticketId && messages.map((message) => { - const isSelf = useMemo(() => - (ticket.sessionData?.sessionId || user) === message.user_id, - [ticket.sessionData?.sessionId, user, message.user_id] - ); + const isSelf = (ticket.sessionData?.sessionId || user) === message.user_id; return ( - (ticket.sessionData?.sessionId || user) === greetingMessage.user_id, - [ticket.sessionData?.sessionId, user, greetingMessage.user_id] - )} + isSelf={false} /> )} diff --git a/src/ui_kit/FloatingSupportChat/index.tsx b/src/ui_kit/FloatingSupportChat/index.tsx index d50fd684..dc0830b5 100644 --- a/src/ui_kit/FloatingSupportChat/index.tsx +++ b/src/ui_kit/FloatingSupportChat/index.tsx @@ -130,9 +130,15 @@ export default () => { ({ shown }) => shown?.me !== 1, ); - newMessages.forEach(({ id, user_id }) => { - if ((ticket.sessionData?.sessionId || user) === user_id) shownMessage(id); - }); + // Находим последнее сообщение, которое не от пользователя + const lastNonUserMessage = newMessages + .filter(({ user_id }) => (ticket.sessionData?.sessionId || user) !== user_id) + .pop(); + + // Отправляем shown только на последнее сообщение, которое не от пользователя + if (lastNonUserMessage) { + shownMessage(lastNonUserMessage.id); + } } }, [isChatOpened, ticket.messages]); diff --git a/src/utils/handleComponentError.ts b/src/utils/handleComponentError.ts new file mode 100644 index 00000000..fd08857a --- /dev/null +++ b/src/utils/handleComponentError.ts @@ -0,0 +1,109 @@ +import { ErrorInfo } from "react"; +import { Ticket, createTicket, getAuthToken, sendTicketMessage } from ".."; + +let errorsQueue: ComponentError[] = []; +let timeoutId: ReturnType; + +interface ComponentError { + timestamp: number; + message: string; + callStack: string | undefined; + componentStack: string | null | undefined; +} + +function isErrorReportingAllowed(error?: Error): boolean { + // Если ошибка помечена как debug-override — всегда отправлять + if (error && (error as any).__forceSend) return true; + // Проверяем домен + const currentDomain = window.location.hostname; + return currentDomain !== 'localhost'; +} + +// Новый API: getTickets — callback, возвращающий актуальные тикеты +export function handleComponentError(error: Error, info: ErrorInfo, getTickets: () => Ticket[]) { + //репортим только о авторизонышах + if (!getAuthToken()) return; + // Проверяем разрешение на отправку ошибок (по домену) + if (!isErrorReportingAllowed(error)) { + console.log('❌ Отправка ошибки заблокирована:', error.message); + return; + } + console.log(`✅ Обработка ошибки: ${error.message}`); + // Копируем __forceSend если есть + const componentError: ComponentError & { __forceSend?: boolean } = { + timestamp: Math.floor(Date.now() / 1000), + message: error.message, + callStack: error.stack, + componentStack: info.componentStack, + ...(error && (error as any).__forceSend ? { __forceSend: true } : {}) + }; + queueErrorRequest(componentError, getTickets); +} + +// Ставит ошибку в очередь для отправки, через 1 секунду вызывает sendErrorsToServer +export function queueErrorRequest(error: ComponentError, getTickets: () => Ticket[]) { + errorsQueue.push(error); + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + sendErrorsToServer(getTickets); + }, 1000); +} + +// Отправляет накопленные ошибки в тикеты, ищет существующий тикет с system: true или создает новый +export async function sendErrorsToServer(getTickets: () => Ticket[]) { + if (errorsQueue.length === 0) return; + // Проверяем разрешение на отправку ошибок (по домену и debug-override) + // Если хотя бы одна ошибка в очереди с __forceSend, отправляем всё + const forceSend = errorsQueue.some(e => (e as any).__forceSend); + if (!forceSend && !isErrorReportingAllowed()) { + console.log('❌ Отправка ошибок заблокирована, очищаем очередь'); + errorsQueue = []; + return; + } + const tickets = getTickets(); + try { + // Формируем сообщение об ошибке + const errorMessage = errorsQueue.map(error => { + return `[${new Date(error.timestamp * 1000).toISOString()}] ${error.message}\n\nCall Stack:\n${error.callStack || 'N/A'}\n\nComponent Stack:\n${error.componentStack || 'N/A'}`; + }).join('\n\n---\n\n'); + // ВСЕГДА ищем тикет через API + const existingSystemTicket = await findSystemTicket(tickets); + if (existingSystemTicket) { + sendTicketMessage({ + ticketId: existingSystemTicket, + message: errorMessage, + systemError: true, + }); + } else { + // Создаем новый тикет для ошибки + createTicket({ + message: errorMessage, + useToken: true, + systemError: true, + }); + } + } catch (error) { + console.error('Error in sendErrorsToServer:', error); + } finally { + // Очищаем очередь ошибок + errorsQueue = []; + } +} + +// Ищет существующий тикет с system: true +export async function findSystemTicket(tickets: Ticket[]) { + for (const ticket of tickets) { + console.log("[findSystemTicket] Проверяем тикет:", ticket); + if (!('messages' in ticket)) { + if (ticket.top_message && ticket.top_message.system === true) { + console.log("[findSystemTicket] Найден тикет по top_message.system:true:", ticket.id); + return ticket.id; + } + } + } +} + +export function clearErrorHandlingConfig () { + clearTimeout(timeoutId); + errorsQueue = []; +} \ No newline at end of file diff --git a/src/utils/hooks/useAutoPay.ts b/src/utils/hooks/useAutoPay.ts index 116fb66e..c1a79df3 100644 --- a/src/utils/hooks/useAutoPay.ts +++ b/src/utils/hooks/useAutoPay.ts @@ -28,13 +28,6 @@ export const useAfterPay = () => { let URLadditionalinformation = searchParams.get("additionalinformation");//его токен useEffect(() => { - console.log("useAutoPay: Processing return from payment", { - URLaction, - URLuserId, - URLadditionalinformation, - userId, - wayback: searchParams.get("wayback") - }); setSearchParams({}, { replace: true }); diff --git a/yarn.lock b/yarn.lock index c542980c..8d5e615e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1443,10 +1443,10 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== -"@frontend/kitui@^1.0.110": +"@frontend/kitui@1.0.110": version "1.0.110" - resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.110/kitui-1.0.110.tgz#969f70636508e9efd6c8d81e62a6913b18a0c029" - integrity sha512-M+U9a4qylLb9ZOUn57v7lm/Rutqicm04vJsJrxeAY/6G4ma1bC29toOZwTt/uJUlF4gk4ojyWIIjiGTVM1/hKQ== + resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.110/kitui-1.0.110.tgz#0c0a968293338537a2811e7761f8efe933893573" + integrity sha512-XOCev5zNtNZ8fu3IfK6oFNOqT8lE9jlmUX1kQ3OO+H30/LBpnBrww9nV/aHV2TEm0wYXdRMvaEtU6VOb72sDdg== dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2"