diff --git a/package.json b/package.json index 40c62754..0ed12790 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.109", + "@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 e86c5fea..3fe1ff21 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { clearAuthToken, getMessageFromFetchError, UserAccount, useUserFetcher } from "@frontend/kitui"; +import { clearAuthToken, 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"; @@ -8,7 +8,7 @@ import { useAfterPay } from "@utils/hooks/useAutoPay"; import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher"; import { enqueueSnackbar } from "notistack"; import type { SuspenseProps } from "react"; -import { lazy, Suspense } from "react"; +import { lazy, Suspense, useEffect } from "react"; import { lazily } from "react-lazily"; import { Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { useAmoAccount } from "./api/integration"; @@ -23,6 +23,9 @@ import { InfoPrivilege } from "./pages/InfoPrivilege"; import AmoTokenExpiredDialog from "./pages/IntegrationsPage/IntegrationsModal/Amo/AmoTokenExpiredDialog"; import Landing from "./pages/Landing/Landing"; import Main from "./pages/main"; +import Debug from "./pages/Debug"; +import { setTicketData, setTickets, useTicketStore } from "./stores/ticket"; +import { parseAxiosError } from "./utils/parse-error"; import { ErrorBoundary } from "react-error-boundary"; const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull")); @@ -73,12 +76,16 @@ const LazyLoading = ({ children, fallback }: SuspenseProps) => ( }>{children} ); +const ApologyPage = () =>

Что-то пошло не так

+ export default function App() { window.LoadingObserver = false; const userId = useUserStore((state) => state.userId); const location = useLocation(); const navigate = useNavigate(); const { data: amoAccount } = useAmoAccount(); + const tickets = useTicketStore(store => store.tickets); + useUserFetcher({ url: `${process.env.REACT_APP_DOMAIN}/user/${userId}`, @@ -133,6 +140,37 @@ export default function App() { }, }); + useTicketsFetcher({ + url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`, + ticketsPerPage: 10, + ticketApiPage: 0, + onSuccess: (result) => { + if (result.data?.length) { + // Записываем все тикеты в стор + setTickets(result.data); + + const currentTicket = result.data.find( + ({ origin }) => !origin.includes("/support"), + ); + + if (!currentTicket) { + return; + } + + setTicketData({ + ticketId: currentTicket.id, + sessionId: currentTicket.sess, + }); + } + }, + onError: (error: Error) => { + const message = parseAxiosError(error); + if (message) enqueueSnackbar(message); + }, + onFetchStateChange: () => { }, + enabled: Boolean(userId), + }); + useAfterPay(); if (location.state?.redirectTo) @@ -145,9 +183,12 @@ export default function App() { ); return ( - <> + handleComponentError(error, info, tickets)} + > {amoAccount && } - + {!isTest && } {location.state?.backgroundLocation && ( @@ -268,6 +309,10 @@ export default function App() { path={"/image/:srcImage"} element={} /> + } + /> }> {routeslink.map((e, i) => ( - + + {/* Компонент отладки ошибок - доступен по Ctrl+Shift+D */} + + ); } diff --git a/src/api/auth.ts b/src/api/auth.ts index b47d6fbf..5dfd5833 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/cart.ts b/src/api/cart.ts index d9ec8a24..a08fb904 100644 --- a/src/api/cart.ts +++ b/src/api/cart.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/contactForm.ts b/src/api/contactForm.ts index 6ed0f71d..df81d211 100644 --- a/src/api/contactForm.ts +++ b/src/api/contactForm.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/integration.ts b/src/api/integration.ts index 40f91b44..177c5bf0 100644 --- a/src/api/integration.ts +++ b/src/api/integration.ts @@ -1,5 +1,5 @@ import { QuestionKeys } from "@/pages/IntegrationsPage/IntegrationsModal/Amo/types"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { useToken } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; import useSWR from "swr"; diff --git a/src/api/makeRequest.ts b/src/api/makeRequest.ts deleted file mode 100644 index ebc554b4..00000000 --- a/src/api/makeRequest.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as KIT from "@frontend/kitui"; -import { Method, ResponseType, AxiosError } from "axios"; -import { redirect } from "react-router-dom"; -import { clearAuthToken } from "@frontend/kitui"; - -import { cleanAuthTicketData } from "@root/ticket"; -import { clearUserData } from "@root/user"; -import { clearQuizData } from "@root/quizes/store"; - -import type { AxiosResponse } from "axios"; -import { selectSendingMethod } from "@/ui_kit/FloatingSupportChat/utils"; - -interface MakeRequest { - method?: Method | undefined; - url: string; - body?: unknown; - useToken?: boolean | undefined; - contentType?: boolean | undefined; - responseType?: ResponseType | undefined; - signal?: AbortSignal | undefined; - withCredentials?: boolean | undefined; -} - -type ExtendedAxiosResponse = AxiosResponse & { message: string }; - -export const makeRequest = async ( - data: MakeRequest, -): Promise => { - try { - const response = await KIT.makeRequest(data); - - return response; - } catch (nativeError) { - const error = nativeError as AxiosError; - - // if (window.location.hostname !== 'localhost') selectSendingMethod({ - // messageField: `status: ${error.response?.status}. Message ${(error.response?.data as ExtendedAxiosResponse)?.message}`, - // isSnackbar: false, - // systemError: true - // }); - if ( - error.response?.status === 400 && - (error.response?.data as ExtendedAxiosResponse)?.message === - "refreshToken is empty" - ) { - cleanAuthTicketData(); - clearAuthToken(); - clearUserData(); - clearQuizData(); - redirect("/"); - } - - throw nativeError; - } -}; diff --git a/src/api/promocode.ts b/src/api/promocode.ts index 87e90a01..f20a7305 100644 --- a/src/api/promocode.ts +++ b/src/api/promocode.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/question.ts b/src/api/question.ts index f15fcee6..cf4f902b 100644 --- a/src/api/question.ts +++ b/src/api/question.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { replaceSpacesToEmptyLines } from "@utils/replaceSpacesToEmptyLines"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/quiz.ts b/src/api/quiz.ts index 1bcea534..e189a7be 100644 --- a/src/api/quiz.ts +++ b/src/api/quiz.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { defaultQuizConfig } from "@model/quizSettings"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/result.ts b/src/api/result.ts index 74d0a5d8..c04edea0 100644 --- a/src/api/result.ts +++ b/src/api/result.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/statistic.ts b/src/api/statistic.ts index 1fd3c245..8310deae 100644 --- a/src/api/statistic.ts +++ b/src/api/statistic.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/tariff.ts b/src/api/tariff.ts index 48efae84..d43b056d 100644 --- a/src/api/tariff.ts +++ b/src/api/tariff.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; import type { GetTariffsResponse } from "@frontend/kitui"; diff --git a/src/api/ticket.ts b/src/api/ticket.ts index 44a1d393..f6f5a181 100644 --- a/src/api/ticket.ts +++ b/src/api/ticket.ts @@ -1,6 +1,6 @@ import { createTicket as createTicketRequest } from "@frontend/kitui"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/api/user.ts b/src/api/user.ts index 14b072c3..4f4c0d07 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,4 +1,4 @@ -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; diff --git a/src/index.tsx b/src/index.tsx index 86b5737e..436554ae 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -18,7 +18,7 @@ import CloseIcon from "@icons/CloseBold"; import type { SnackbarKey } from "notistack"; import { CheckFastlink } from "@ui_kit/CheckFastlink"; import { ErrorBoundary } from "react-error-boundary"; -import { handleComponentError } from "./utils/handleComponentError"; +import { handleComponentError } from "@frontend/kitui"; moment.locale("ru"); polyfillCountryFlagEmojis(); @@ -38,7 +38,6 @@ const snackbarAction = (snackbarId: SnackbarKey) => ( ); -const ApologyPage = () =>

Что-то пошло не так

const root = createRoot(document.getElementById("root")!); @@ -65,12 +64,7 @@ root.render( > - - diff --git a/src/pages/Debug.tsx b/src/pages/Debug.tsx new file mode 100644 index 00000000..bd9a64c7 --- /dev/null +++ b/src/pages/Debug.tsx @@ -0,0 +1,149 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Button, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper +} from '@mui/material'; + +// Функции для создания тестовых ошибок +const createTestError = (message: string) => { + const error = new Error(message); + (error as any).__forceSend = true; + throw error; +}; + +const createReactComponentError = (message: string) => { + const error = new Error(message); + (error as any).__forceSend = true; + throw error; +}; + +/** + * Простая страница отладки системы обработки ошибок + * Активируется по Ctrl+Shift+F + */ +const Debug: React.FC = () => { + const [isVisible, setIsVisible] = useState(false); + + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.ctrlKey && event.shiftKey && event.code === 'KeyF') { + event.preventDefault(); + setIsVisible(!isVisible); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [isVisible]); + + if (!isVisible) { + return null; + } + + const errorTests = [ + { + description: 'Простая ошибка', + action: () => { + createTestError('Тестовая ошибка отладки'); + } + }, + { + description: 'React ошибка компонента', + action: () => { + createReactComponentError('Тестовая ошибка React компонента'); + } + }, + { + description: 'Прямая ошибка в обработчике', + action: () => { + const error = new Error('Прямая ошибка в компоненте'); + (error as any).__forceSend = true; + throw error; + } + }, + { + description: 'Ошибка с длинным стеком', + action: () => { + const deepError = new Error('Глубокая ошибка с длинным стеком'); + deepError.stack = 'Error: Глубокая ошибка\n at level1 (debug.ts:10)\n at level2 (debug.ts:15)\n at level3 (debug.ts:20)\n at level4 (debug.ts:25)'; + (deepError as any).__forceSend = true; + throw deepError; + } + }, + { + description: 'Ошибка с undefined', + action: () => { + const obj: any = null; + try { + obj.nonExistentMethod(); + } catch (error) { + (error as any).__forceSend = true; + throw error; + } + } + } + ]; + + return ( + + + + 🛠️ Отладка ошибок + + + + Ctrl+Shift+F для закрытия + + + + + + + Описание ошибки + Действие + + + + {errorTests.map((test, index) => ( + + {test.description} + + + + + ))} + +
+
+
+
+ ); +}; + +export default Debug; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/IntegrationsModal/Amo/useAmoIntegration.ts b/src/pages/IntegrationsPage/IntegrationsModal/Amo/useAmoIntegration.ts index 5a983e76..a5a106df 100644 --- a/src/pages/IntegrationsPage/IntegrationsModal/Amo/useAmoIntegration.ts +++ b/src/pages/IntegrationsPage/IntegrationsModal/Amo/useAmoIntegration.ts @@ -353,7 +353,7 @@ export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID, que }; }; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`; export const resetAmoTagsFields = async () => { diff --git a/src/pages/PersonalizationAI/PersonalizationAI.tsx b/src/pages/PersonalizationAI/PersonalizationAI.tsx index 1476a6b3..661b4879 100644 --- a/src/pages/PersonalizationAI/PersonalizationAI.tsx +++ b/src/pages/PersonalizationAI/PersonalizationAI.tsx @@ -16,7 +16,7 @@ import { inCart } from "../Tariffs/utils"; import { isTestServer } from "@/utils/hooks/useDomainDefine"; import { useToken } from "@frontend/kitui"; import { useSWRConfig } from "swr"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { setUserAccount, setCustomerAccount } from "@/stores/user"; import { quizApi } from "@api/quiz"; import { setQuizes } from "@root/quizes/actions"; diff --git a/src/pages/auth/RecoverPassword.tsx b/src/pages/auth/RecoverPassword.tsx index 8afd81ca..4849d9e4 100644 --- a/src/pages/auth/RecoverPassword.tsx +++ b/src/pages/auth/RecoverPassword.tsx @@ -18,7 +18,7 @@ import { object, string } from "yup"; import { useEffect, useState } from "react"; import { useUserStore } from "@root/user"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { setAuthToken } from "@frontend/kitui"; import { parseAxiosError } from "@utils/parse-error"; import { recoverUser } from "@api/user"; diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx index 9ffc4d44..3a5045be 100755 --- a/src/pages/createQuize/QuizCard.tsx +++ b/src/pages/createQuize/QuizCard.tsx @@ -7,7 +7,7 @@ import { Box, Button, IconButton, Popover, Typography, useMediaQuery, useTheme } import { deleteQuiz, setEditQuizId } from "@root/quizes/actions"; import { Link, useNavigate } from "react-router-dom"; import { inCart } from "../../pages/Tariffs/utils"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { enqueueSnackbar } from "notistack"; import { useDomainDefine } from "@utils/hooks/useDomainDefine"; import CopyIcon from "@icons/CopyIcon"; diff --git a/src/stores/ticket.ts b/src/stores/ticket.ts index 185fc788..4a47adb0 100644 --- a/src/stores/ticket.ts +++ b/src/stores/ticket.ts @@ -1,4 +1,4 @@ -import { FetchState, TicketMessage } from "@frontend/kitui"; +import { FetchState, Ticket, TicketMessage } from "@frontend/kitui"; import { create } from "zustand"; import { createJSONStorage, devtools, persist } from "zustand/middleware"; import { useUserStore } from "./user"; @@ -21,11 +21,12 @@ interface AuthData { interface TicketStore { unauthData: AuthData; authData: AuthData; + tickets: Ticket[]; } let params = new URLSearchParams(document.location.search); -const debug = params.get("debug"); + const initAuthData = { sessionData: null, isMessageSending: false, @@ -35,10 +36,12 @@ const initAuthData = { lastMessageId: undefined, isPreventAutoscroll: false, unauthTicketMessageFetchState: "idle" as FetchState, + tickets: [] }; const initState = { unauthData: initAuthData, authData: initAuthData, + tickets: [] }; export const useTicketStore = create()( @@ -156,9 +159,17 @@ export const updateTicket = ( }, ); -function setProducedState( +function setProducedState( recipe: (state: TicketStore) => void, action?: A, ) { useTicketStore.setState((state) => produce(state, recipe), false, action); } + +// Функция для записи тикетов в стор +export const setTickets = (tickets: Ticket[] | null) => { + useTicketStore.setState((state) => ({ + ...state, + tickets: tickets || [] + }), false); +}; diff --git a/src/ui_kit/CheckFastlink.tsx b/src/ui_kit/CheckFastlink.tsx index ce7cbbaa..f90db289 100644 --- a/src/ui_kit/CheckFastlink.tsx +++ b/src/ui_kit/CheckFastlink.tsx @@ -3,7 +3,7 @@ import { Box, Button, Modal, Typography } from "@mui/material"; import { enqueueSnackbar } from "notistack"; import { mutate } from "swr"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import { getDiscounts } from "@api/discounts"; import { clearUserData, OriginalUserAccount, setUserAccount, useUserStore } from "@root/user"; diff --git a/src/ui_kit/FloatingSupportChat/index.tsx b/src/ui_kit/FloatingSupportChat/index.tsx index b4a4f32a..d50fd684 100644 --- a/src/ui_kit/FloatingSupportChat/index.tsx +++ b/src/ui_kit/FloatingSupportChat/index.tsx @@ -72,33 +72,6 @@ export default () => { setIsChatOpened((state) => !state); }; - useTicketsFetcher({ - url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`, - ticketsPerPage: 10, - ticketApiPage: 0, - onSuccess: (result) => { - if (result.data?.length) { - const currentTicket = result.data.find( - ({ origin }) => !origin.includes("/support"), - ); - - if (!currentTicket) { - return; - } - - setTicketData({ - ticketId: currentTicket.id, - sessionId: currentTicket.sess, - }); - } - }, - onError: (error: Error) => { - const message = parseAxiosError(error); - if (message) enqueueSnackbar(message); - }, - onFetchStateChange: () => { }, - enabled: Boolean(user), - }); useTicketMessages({ url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getMessages`, diff --git a/src/utils/handleComponentError.ts b/src/utils/handleComponentError.ts deleted file mode 100644 index 94369f24..00000000 --- a/src/utils/handleComponentError.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { selectSendingMethod } from "@/ui_kit/FloatingSupportChat/utils"; -import { ErrorInfo } from "react"; - -interface ComponentError { - timestamp: number; - message: string; - callStack: string | undefined; - componentStack: string | null | undefined; -} - -export function handleComponentError(error: Error, info: ErrorInfo) { - const componentError: ComponentError = { - timestamp: Math.floor(Date.now() / 1000), - message: error.message, - callStack: error.stack, - componentStack: info.componentStack, - }; - - queueErrorRequest(componentError); -} - -let errorsQueue: ComponentError[] = []; -let timeoutId: ReturnType; - -function queueErrorRequest(error: ComponentError) { - errorsQueue.push(error); - - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - sendErrorsToServer(); - }, 1000); -} - -async function sendErrorsToServer() { - // makeRequest({ - // url: "", - // method: "POST", - // body: errorsQueue, - // useToken: true, - // }); - // selectSendingMethod({ - // messageField: `Fake-sending ${errorsQueue.length} errors to server ${JSON.stringify(errorsQueue)}`, - // isSnackbar: false, - // systemError: true - // }); -// errorsQueue = []; -} diff --git a/src/utils/hooks/useUserAccountFetcher.ts b/src/utils/hooks/useUserAccountFetcher.ts index 819e35fb..5785cdc4 100644 --- a/src/utils/hooks/useUserAccountFetcher.ts +++ b/src/utils/hooks/useUserAccountFetcher.ts @@ -1,7 +1,7 @@ import { useEffect, useLayoutEffect, useRef } from "react"; import { createUserAccount, devlog } from "@frontend/kitui"; import { isAxiosError } from "axios"; -import { makeRequest } from "@api/makeRequest"; +import { makeRequest } from "@frontend/kitui"; import type { UserAccount } from "@frontend/kitui"; import { setUserAccount } from "@/stores/user"; diff --git a/yarn.lock b/yarn.lock index 8ed63d0c..c542980c 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.109": - version "1.0.109" - resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.109/kitui-1.0.109.tgz#a9611e7b69dbd2bbc46e78c083d0442fc22bdcef" - integrity sha512-y6wzLDEWfTXMjL2gDucs/AzAc0fyh80aIbiokGz1ZgaHMR0XQhV2E/VqlUvK95VZRFO7UqnEaJofpY7iwSQjQA== +"@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== dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2"