From 572dfae0165e3a94b1d8a70616f72ec7c84505a2 Mon Sep 17 00:00:00 2001 From: Nastya Date: Sat, 5 Jul 2025 03:27:58 +0300 Subject: [PATCH 01/18] =?UTF-8?q?=D0=B0=D0=BF=D0=B8=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=B8=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/api/ticket.ts | 40 ----------- src/ui_kit/FloatingSupportChat/Chat.tsx | 2 +- .../FloatingSupportChat.tsx | 2 +- src/ui_kit/FloatingSupportChat/index.tsx | 43 ++++++----- .../useTechnicalSupport.ts | 46 +++++++----- src/ui_kit/FloatingSupportChat/utils.ts | 72 +++++++++---------- yarn.lock | 8 +-- 8 files changed, 95 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 5f34f899..40c62754 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.108", + "@frontend/kitui": "^1.0.109", "@frontend/squzanswerer": "^1.0.57", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", diff --git a/src/api/ticket.ts b/src/api/ticket.ts index 5056dc64..44a1d393 100644 --- a/src/api/ticket.ts +++ b/src/api/ticket.ts @@ -15,47 +15,7 @@ type SendFileResponse = { const API_URL = `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0`; -export const sendTicketMessage = async ( - ticketId: string, - message: string, - systemError: boolean -): Promise<[null, string?]> => { - try { - const sendTicketMessageResponse = await makeRequest< - SendTicketMessageRequest, - null - >({ - url: `${API_URL}/send`, - method: "POST", - useToken: true, - body: { ticket: ticketId, message: message, lang: "ru", files: [], system: systemError }, - - }); - return [sendTicketMessageResponse]; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - - return [null, `Не удалось отправить сообщение. ${error}`]; - } -}; - -export const shownMessage = async (id: string): Promise<[null, string?]> => { - try { - const shownMessageResponse = await makeRequest<{ id: string }, null>({ - url: `${API_URL}/shown`, - method: "POST", - useToken: true, - body: { id }, - }); - - return [shownMessageResponse]; - } catch (nativeError) { - const [error] = parseAxiosError(nativeError); - - return [null, `Не удалось прочесть сообщение. ${error}`]; - } -}; export const sendFile = async ( ticketId: string, diff --git a/src/ui_kit/FloatingSupportChat/Chat.tsx b/src/ui_kit/FloatingSupportChat/Chat.tsx index 28e17676..7ce80acd 100644 --- a/src/ui_kit/FloatingSupportChat/Chat.tsx +++ b/src/ui_kit/FloatingSupportChat/Chat.tsx @@ -40,7 +40,7 @@ interface Props { sx?: SxProps; onclickArrow?: () => void; sendMessage: (a: string) => Promise; - sendFile: (a: File | undefined) => Promise; + sendFile: (a: File | undefined) => Promise; greetingMessage: TicketMessage; } diff --git a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx index c59aa418..1ee0ba3e 100644 --- a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx +++ b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx @@ -44,7 +44,7 @@ interface Props { handleChatClickClose: () => void; handleChatClickSwitch: () => void; sendMessage: (a: string) => Promise; - sendFile: (a: File | undefined) => Promise; + sendFile: (a: File | undefined) => Promise; modalWarningType: string | null; setModalWarningType: any; greetingMessage: TicketMessage; diff --git a/src/ui_kit/FloatingSupportChat/index.tsx b/src/ui_kit/FloatingSupportChat/index.tsx index eb17a552..6b9e8c92 100644 --- a/src/ui_kit/FloatingSupportChat/index.tsx +++ b/src/ui_kit/FloatingSupportChat/index.tsx @@ -1,13 +1,15 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; import { TicketMessage, + createTicket, + shownMessage, useSSESubscription, useTicketMessages, useTicketsFetcher, + sendFile as sf } from "@frontend/kitui"; import FloatingSupportChat from "./FloatingSupportChat"; import { useUserStore } from "@root/user"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { sendTicketMessage, shownMessage } from "../../api/ticket"; import { useSSETab } from "../../utils/hooks/useSSETab"; import { addOrUpdateUnauthMessages, @@ -21,7 +23,6 @@ import { } from "@root/ticket"; import { enqueueSnackbar } from "notistack"; import { parseAxiosError } from "@utils/parse-error"; -import { createTicket, sendFile as sendFileRequest } from "@api/ticket"; import { selectSendingMethod } from "./utils"; type ModalWarningType = @@ -75,7 +76,7 @@ export default () => { const workingHoursMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; const offHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; + "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; const date = new Date(); const currentHourUTC = date.getUTCHours(); const MscTime = 3; // Москва UTC+3; @@ -122,7 +123,7 @@ export default () => { const message = parseAxiosError(error); if (message) enqueueSnackbar(message); }, - onFetchStateChange: () => {}, + onFetchStateChange: () => { }, enabled: Boolean(user), }); @@ -192,25 +193,30 @@ export default () => { const sendMessage = async (messageField: string) => { if (!messageField || ticket.isMessageSending) return false; - + setSseEnabled(true); setIsMessageSending(true); - let successful = await selectSendingMethod({messageField}); + let successful = await selectSendingMethod({ messageField }); setIsMessageSending(false); return successful; }; - const sendFile = async (file: File) => { - if (file === undefined) return true; + const sendFile = async (file: File | undefined): Promise => { + if (file === undefined) return; let ticketId = ticket.sessionData?.ticketId; if (!ticket.sessionData?.ticketId) { - const [data, createError] = await createTicket("", Boolean(user)); + const [data, createError] = await createTicket({ + message: "", + useToken: Boolean(user), + systemError: false + }); ticketId = data?.Ticket; if (createError || !data) { - enqueueSnackbar(createError); + enqueueSnackbar(`Не удалось создать диалог ${parseAxiosError(createError)}`); + return; } else { setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); } @@ -219,15 +225,16 @@ export default () => { } if (ticketId !== undefined) { - if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); - - const [_, sendFileError] = await sendFileRequest(ticketId, file); - - if (sendFileError) { - enqueueSnackbar(sendFileError); + if (file.size > MAX_FILE_SIZE) { + setModalWarningType("errorSize"); + return; } - return true; + const [_, sendFileError] = await sf({ticketId, file}); + + if (sendFileError) { + enqueueSnackbar(`Не удалось отправить файл ${parseAxiosError(sendFileError)}`); + } } }; diff --git a/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts b/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts index 21430c7e..e9f42cbe 100644 --- a/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts +++ b/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts @@ -1,7 +1,6 @@ -import { sendTicketMessage, shownMessage } from "@/api/ticket"; import { useSSETab } from "@/utils/hooks/useSSETab"; import { parseAxiosError } from "@/utils/parse-error"; -import { TicketMessage, createTicket, useSSESubscription, useTicketMessages, useTicketsFetcher, sendFile as sf } from "@frontend/kitui"; +import { TicketMessage, createTicket, useSSESubscription, useTicketMessages, useTicketsFetcher, sendFile as sf, sendTicketMessage, shownMessage } from "@frontend/kitui"; import { addOrUpdateUnauthMessages, @@ -17,7 +16,7 @@ import { enqueueSnackbar } from "notistack"; import { useCallback, useEffect, useMemo, useState } from "react"; interface Props { - userId?: string; + userId?: string; } @@ -42,7 +41,7 @@ const ACCEPT_SEND_FILE_TYPES_MAP = [ ".xlsx", ".csv", ] as const; -export default ({ userId }:Props) => { +export default ({ userId }: Props) => { const ticket = useTicketStore((state) => state[userId ? "authData" : "unauthData"]); const { isActiveSSETab, updateSSEValue } = useSSETab( @@ -69,7 +68,7 @@ export default ({ userId }:Props) => { const workingHoursMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; const offHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; + "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; const date = new Date(); const currentHourUTC = date.getUTCHours(); const MscTime = 3; // Москва UTC+3; @@ -116,7 +115,7 @@ export default ({ userId }:Props) => { const message = parseAxiosError(error); if (message) enqueueSnackbar(message); }, - onFetchStateChange: () => {}, + onFetchStateChange: () => { }, enabled: Boolean(userId), }); @@ -190,15 +189,16 @@ export default ({ userId }:Props) => { let successful = false; setIsMessageSending(true); if (!ticket.sessionData?.ticketId) { - const [data, createError] = await createTicket( - messageField, - Boolean(userId), - ); + const [data, createError] = await createTicket({ + message: messageField, + useToken: Boolean(userId), + systemError: false + }); if (createError || !data) { successful = false; - enqueueSnackbar(createError); + enqueueSnackbar(`Не удалось создать чат ${(createError)}`); } else { successful = true; @@ -207,15 +207,16 @@ export default ({ userId }:Props) => { setIsMessageSending(false); } else { - const [_, sendTicketMessageError] = await sendTicketMessage( - ticket.sessionData?.ticketId, - messageField, - ); + const [_, sendTicketMessageError] = await sendTicketMessage({ + ticketId: ticket.sessionData?.ticketId, + message: messageField, + systemError: false + }); successful = true; if (sendTicketMessageError) { successful = false; - enqueueSnackbar(sendTicketMessageError); + enqueueSnackbar(`Ошибка отправки сообщения ${parseAxiosError(sendTicketMessageError)}`); } setIsMessageSending(false); } @@ -227,11 +228,15 @@ export default ({ userId }:Props) => { let ticketId = ticket.sessionData?.ticketId; if (!ticket.sessionData?.ticketId) { - const [data, createError] = await createTicket("", Boolean(userId)); + const [data, createError] = await createTicket({ + message: "", + useToken: Boolean(userId), + systemError: false + }); ticketId = data?.Ticket; if (createError || !data) { - enqueueSnackbar(createError); + enqueueSnackbar(`Не удалось создать диалог ${parseAxiosError(createError)}`); } else { setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); } @@ -242,7 +247,10 @@ export default ({ userId }:Props) => { if (ticketId !== undefined) { if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); - const [_, sendFileError] = await sf(ticketId, file); + const [_, sendFileError] = await sf({ + ticketId, + file + }); if (sendFileError) { enqueueSnackbar(sendFileError); diff --git a/src/ui_kit/FloatingSupportChat/utils.ts b/src/ui_kit/FloatingSupportChat/utils.ts index a603e348..87d80ca6 100644 --- a/src/ui_kit/FloatingSupportChat/utils.ts +++ b/src/ui_kit/FloatingSupportChat/utils.ts @@ -1,55 +1,55 @@ -import { sendTicketMessage } from "@/api/ticket"; import { setTicketData, useTicketStore } from "@/stores/ticket"; import { useUserStore } from "@root/user"; -import { createTicket, sendFile as sendFileRequest } from "@api/ticket"; import { enqueueSnackbar } from "notistack"; +import { createTicket, sendTicketMessage } from "@frontend/kitui"; +import { parseAxiosError } from "@/utils/parse-error"; interface SelectSendingMethod { - messageField: string; - isSnackbar?: boolean; - systemError?: boolean; - - } -export const selectSendingMethod = async ({messageField, isSnackbar = true, systemError = false}: SelectSendingMethod) => { - console.log("click") + messageField: string; + isSnackbar?: boolean; + systemError?: boolean; + +} +export const selectSendingMethod = async ({ messageField, isSnackbar = true, systemError = false }: SelectSendingMethod) => { + console.log("click") const user = useUserStore.getState().user?._id; const ticket = useTicketStore.getState()[user ? "authData" : "unauthData"]; console.log(ticket) console.log("click 2") let successful = false; - if (!(window.location.hostname == 'localhost' && systemError )) { //предупреждать о системных ошибках вне локалхост - if (!ticket.sessionData?.ticketId) { - console.log("autorisated 2") - const [data, createError] = await createTicket( - messageField, - Boolean(user), - false, - ); + if (!(window.location.hostname == 'localhost' && systemError)) { //предупреждать о системных ошибках вне локалхост + if (!ticket.sessionData?.ticketId) { + console.log("autorisated 2") + const [data, createError] = await createTicket({ + message: messageField, + useToken: Boolean(user), + systemError: false, + }); - if (createError || !data) { - successful = false; - - if (isSnackbar) enqueueSnackbar(createError); - } else { - successful = true; - - setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); - } + if (createError || !data) { + successful = false; + if (isSnackbar) enqueueSnackbar(`Не удалось открыть диалог ${parseAxiosError(createError)}`); } else { - const [_, sendTicketMessageError] = await sendTicketMessage( - ticket.sessionData?.ticketId, - messageField, - false, - ); successful = true; - if (sendTicketMessageError) { - successful = false; - if (isSnackbar) enqueueSnackbar(sendTicketMessageError); - } + setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); } - } + + } else { + const [_, sendTicketMessageError] = await sendTicketMessage({ + ticketId: ticket.sessionData?.ticketId, + message: messageField, + systemError: false, + }); + successful = true; + + if (sendTicketMessageError) { + successful = false; + if (isSnackbar) enqueueSnackbar(sendTicketMessageError); + } + } + } return successful; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 00050294..8ed63d0c 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.108": - version "1.0.108" - resolved "http://gitea.pena/api/packages/skeris/npm/%40frontend%2Fkitui/-/1.0.108/kitui-1.0.108.tgz#1bb609dfe07668b6fd9a7b8618c229e0bb609f1e" - integrity sha512-4DiF7GHX0RbBMZpFioclc3B87N+HrGLv1B3DveUCdHzukfxvFXyEKnRZQ4wYljO2A3FLSD9+4Dr6cSuZYw95OQ== +"@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== dependencies: immer "^10.0.2" reconnecting-eventsource "^1.6.2" From 66456bac315e2d22fb3c3b2cc357d8cc8a6e0ac1 Mon Sep 17 00:00:00 2001 From: Nastya Date: Fri, 11 Jul 2025 19:21:36 +0300 Subject: [PATCH 02/18] =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=82=D0=B0=D1=80=D0=B8=D1=84?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersonalizationAI/PersonalizationAI.tsx | 4 +- .../Tariffs/TariffCardDisplaySelector.tsx | 147 ++++++++++ src/pages/Tariffs/Tariffs.tsx | 221 +++++--------- .../components/PaymentConfirmationModal.tsx | 51 ++++ .../Tariffs/components/TariffsHeader.tsx | 83 ++++++ src/pages/Tariffs/pages/HideLogo.tsx | 0 src/pages/Tariffs/pages/Other.tsx | 97 ------- src/pages/Tariffs/types.ts | 2 +- src/pages/Tariffs/utils.ts | 46 +++ src/pages/createQuize/QuizCard.tsx | 2 +- src/ui_kit/FloatingSupportChat/Chat.tsx | 221 ++------------ src/ui_kit/FloatingSupportChat/ChatInput.tsx | 137 +++++++++ .../ChatMessageRenderer.tsx | 70 +++++ .../FloatingSupportChat.tsx | 4 - src/ui_kit/FloatingSupportChat/index.tsx | 33 +-- .../useTechnicalSupport.ts | 274 ------------------ src/utils/hooks/usePipeSubscriber.ts | 2 +- 17 files changed, 641 insertions(+), 753 deletions(-) create mode 100644 src/pages/Tariffs/TariffCardDisplaySelector.tsx create mode 100644 src/pages/Tariffs/components/PaymentConfirmationModal.tsx create mode 100644 src/pages/Tariffs/components/TariffsHeader.tsx delete mode 100644 src/pages/Tariffs/pages/HideLogo.tsx delete mode 100644 src/pages/Tariffs/pages/Other.tsx create mode 100644 src/pages/Tariffs/utils.ts create mode 100644 src/ui_kit/FloatingSupportChat/ChatInput.tsx create mode 100644 src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx delete mode 100644 src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts diff --git a/src/pages/PersonalizationAI/PersonalizationAI.tsx b/src/pages/PersonalizationAI/PersonalizationAI.tsx index d86fede9..0aacd104 100644 --- a/src/pages/PersonalizationAI/PersonalizationAI.tsx +++ b/src/pages/PersonalizationAI/PersonalizationAI.tsx @@ -11,8 +11,8 @@ import { useSnackbar } from "notistack"; import { PayModal } from "./PayModal"; import { useUserStore } from "@/stores/user"; import { cartApi } from "@/api/cart"; -import { outCart } from "../Tariffs/Tariffs"; -import { inCart } from "../Tariffs/Tariffs"; +import { outCart } from "../Tariffs/utils"; +import { inCart } from "../Tariffs/utils"; import { isTestServer } from "@/utils/hooks/useDomainDefine"; import { useToken } from "@frontend/kitui"; import { useSWRConfig } from "swr"; diff --git a/src/pages/Tariffs/TariffCardDisplaySelector.tsx b/src/pages/Tariffs/TariffCardDisplaySelector.tsx new file mode 100644 index 00000000..617febfa --- /dev/null +++ b/src/pages/Tariffs/TariffCardDisplaySelector.tsx @@ -0,0 +1,147 @@ +import { Box, useMediaQuery, useTheme } from "@mui/material" +import { NavCard } from "./components/NavCard" +import { createTariffElements } from "./tariffsUtils/createTariffElements" +import SmallIconPena from "@/assets/icons/SmallIconPena" + +interface TariffCardDisplaySelectorProps { + content: { + title: string, + onClick: () => void + }[] + selectedItem: TypePages + tariffs: any[] + user: any + discounts: any[] + openModalHC: (tariffInfo: any) => void + userPrivilegies: any + startRequestCreate: () => void +} + +export const TariffCardDisplaySelector = ({ + content, + selectedItem, + tariffs, + user, + discounts, + openModalHC, + userPrivilegies, + startRequestCreate +}: TariffCardDisplaySelectorProps) => { + const theme = useTheme() + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const sendRequest = userPrivilegies?.quizManual?.amount > 0 ? startRequestCreate : undefined + + switch (selectedItem) { + case "dop": + return + {content.map(data => )} + + + case "hide": + const filteredBadgeTariffs = tariffs.filter((tariff) => { + return ( + tariff.privileges[0].serviceKey === "squiz" && + !tariff.isDeleted && + !tariff.isCustom && + tariff.privileges[0].privilegeId === "squizHideBadge" && + tariff.privileges[0]?.type === "day" + ); + }); + return createTariffElements( + filteredBadgeTariffs, + false, + user, + discounts, + openModalHC, + ) + + case "create": + const filteredCreateTariffs = tariffs.filter((tariff) => { + return ( + tariff.privileges[0].serviceKey === "squiz" && + !tariff.isDeleted && + !tariff.isCustom && + tariff.privileges[0].privilegeId === "quizManual" && + tariff.privileges[0]?.type === "count" + ); + }); + return createTariffElements( + filteredCreateTariffs, + false, + user, + discounts, + openModalHC, + sendRequest, + true, + + ) + + case "premium": + const filteredPremiumTariffs = tariffs.filter((tariff) => { + return ( + tariff.privileges[0].serviceKey === "squiz" && + !tariff.isDeleted && + !tariff.isCustom && + tariff.privileges[0].privilegeId === "squizPremium" && + tariff.privileges[0]?.type === "day" + ); + }); + return createTariffElements( + filteredPremiumTariffs, + false, + user, + discounts, + openModalHC, + ) + + case "analytics": + const filteredAnalyticsTariffs = tariffs.filter((tariff) => { + return ( + tariff.privileges[0].serviceKey === "squiz" && + !tariff.isDeleted && + !tariff.isCustom && + tariff.privileges[0].privilegeId === "squizAnalytics" && + tariff.privileges[0]?.type === "count" + ); + }); + return createTariffElements( + filteredAnalyticsTariffs, + false, + user, + discounts, + openModalHC, + ) + + case "custom": + const filteredCustomTariffs = tariffs.filter((tariff) => { + return ( + tariff.privileges[0].serviceKey === "squiz" && + !tariff.isDeleted && + tariff.isCustom && + tariff.privileges[0]?.type === "day" + ); + }); + return createTariffElements( + filteredCustomTariffs, + false, + user, + discounts, + openModalHC, + ) + + default: + return + {content.map(data => )} + + } +} \ No newline at end of file diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index 5a886bb0..ed8fc735 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -1,39 +1,33 @@ import { activatePromocode } from "@api/promocode"; import { useToken } from "@frontend/kitui"; -import ArrowLeft from "@icons/questionsPage/arrowLeft"; import { Box, - Button, - Container, - IconButton, - Modal, - Paper, Typography, useMediaQuery, useTheme, } from "@mui/material"; import { useUserStore } from "@root/user"; -import { LogoutButton } from "@ui_kit/LogoutButton"; import { useDomainDefine } from "@utils/hooks/useDomainDefine"; import { enqueueSnackbar } from "notistack"; import { useEffect, useState } from "react"; import { withErrorBoundary } from "react-error-boundary"; -import { Link, useNavigate } from "react-router-dom"; -import Logotip from "../../pages/Landing/images/icons/QuizLogo"; +import { useNavigate } from "react-router-dom"; import CollapsiblePromocodeField from "./CollapsiblePromocodeField"; import { Tabs } from "./Tabs"; import { createTariffElements } from "./tariffsUtils/createTariffElements"; import { currencyFormatter } from "./tariffsUtils/currencyFormatter"; import { useWallet, setCash } from "@root/cash"; -import { handleLogoutClick } from "@utils/HandleLogoutClick"; import { cartApi } from "@api/cart"; -import { Other } from "./pages/Other"; +import { TariffCardDisplaySelector } from "./TariffCardDisplaySelector"; import { ModalRequestCreate } from "./ModalRequestCreate"; import { cancelCC, useCC } from "@/stores/cc"; import { NavSelect } from "./NavSelect"; import { useTariffs } from '@utils/hooks/useTariffs'; import { useDiscounts } from '@utils/hooks/useDiscounts'; +import { PaymentConfirmationModal } from "./components/PaymentConfirmationModal"; +import { TariffsHeader } from "./components/TariffsHeader"; +import { inCart, outCart } from "./utils"; const StepperText: Record = { day: "Тарифы на время", @@ -50,9 +44,9 @@ function TariffPage() { const userId = useUserStore((state) => state.userId); const navigate = useNavigate(); const user = useUserStore((state) => state.customerAccount); - const a = useUserStore((state) => state.customerAccount); //c wallet + const userWithWallet = useUserStore((state) => state.customerAccount); //c wallet console.log("________________34563875693785692576_____________USERRRRRRR") - console.log(a) + // console.log(userWithWallet) const { data: discounts } = useDiscounts(userId); const [isRequestCreate, setIsRequestCreate] = useState(false); const [openModal, setOpenModal] = useState({}); @@ -69,13 +63,13 @@ console.log("________34563875693785692576_____ TARIFFS") console.log(tariffs) useEffect(() => { - if (a) { + if (userWithWallet) { let cs = currencyFormatter.format(Number(user.wallet.cash) / 100); let cc = Number(user.wallet.cash); let cr = Number(user.wallet.cash) / 100; setCash(cs, cc, cr); } - }, [a]); + }, [userWithWallet]); useEffect(() => { if (cc) { @@ -169,63 +163,10 @@ console.log(tariffs) setIsRequestCreate(true) } - if (!a) return null; + if (!userWithWallet) return null; return ( <> - - - - - navigate("/list")}> - - - - - - Мой баланс - - 9 ? "13px" : "16px") : "16px" - } - > - {cashString} - - - { - navigate("/"); - handleLogoutClick(); - }} - sx={{ - ml: "20px", - }} - /> - - + setSelectedItem("create") }, + { + title: "Премиум функции", + onClick: () => setSelectedItem("premium") + }, + { + title: "Расширенная аналитика", + onClick: () => setSelectedItem("analytics") + }, + { + title: "Кастомные тарифы", + onClick: () => setSelectedItem("custom") + }, ]} tariffs={tariffs} @@ -304,38 +257,60 @@ console.log(tariffs) startRequestCreate={startRequestCreate} /> )} + {selectedItem === "dop" && ( + setSelectedItem("hide") + }, + { + title: "Создать квиз на заказ", + onClick: () => setSelectedItem("create") + }, + ] + : [ + { + title: `Убрать логотип "PenaQuiz"`, + onClick: () => setSelectedItem("hide") + }, + { + title: "Создать квиз на заказ", + onClick: () => setSelectedItem("create") + }, + { + title: "Премиум функции", + onClick: () => setSelectedItem("premium") + }, + { + title: "Расширенная аналитика", + onClick: () => setSelectedItem("analytics") + }, + { + title: "Кастомные тарифы", + onClick: () => setSelectedItem("custom") + }, + ] + } + + tariffs={tariffs} + user={user} + discounts={discounts} + openModalHC={openModalHC} + userPrivilegies={userPrivilegies} + startRequestCreate={startRequestCreate} + /> + )} - 0} onClose={() => setOpenModal({})} - > - - - Вы подтверждаете платёж в сумму{" "} - {openModal.price ? openModal.price.toFixed(2) : 0} ₽ - - - - + onConfirm={() => tryBuy(openModal)} + price={openModal.price} + /> setIsRequestCreate(false)} /> ); @@ -364,47 +339,3 @@ const LoadingPage = () => ( ); - -export const inCart = () => { - let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]"); - if (Array.isArray(saveCart)) { - saveCart.forEach(async (id: string) => { - const [_, addError] = await cartApi.add(id); - - if (addError) { - console.error(addError); - } else { - let index = saveCart.indexOf("green"); - - if (index !== -1) { - saveCart.splice(index, 1); - } - - localStorage.setItem("saveCart", JSON.stringify(saveCart)); - } - }); - } else { - localStorage.setItem("saveCart", "[]"); - } -}; -export const outCart = (cart: string[]) => { - //Сделаем муторно и подольше, зато при прерывании сессии данные потеряются минимально - if (cart.length > 0) { - cart.forEach(async (id: string) => { - const [_, deleteError] = await cartApi.delete(id); - - if (deleteError) { - console.error(deleteError); - cancelCC()//мы хотели открыть модалку после покупки тарифа на создание квиза, но не вышло и модалку не откроем - - return; - } - - let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]") || []; - if (!Array.isArray(saveCart)) saveCart = [] - saveCart = saveCart.push(id); - localStorage.setItem("saveCart", JSON.stringify(saveCart)); - }); - } - -}; diff --git a/src/pages/Tariffs/components/PaymentConfirmationModal.tsx b/src/pages/Tariffs/components/PaymentConfirmationModal.tsx new file mode 100644 index 00000000..0d13eb46 --- /dev/null +++ b/src/pages/Tariffs/components/PaymentConfirmationModal.tsx @@ -0,0 +1,51 @@ +import { + Button, + Modal, + Paper, + Typography, +} from "@mui/material"; + +interface PaymentConfirmationModalProps { + open: boolean; + onClose: () => void; + onConfirm: () => void; + price: number; +} + +export const PaymentConfirmationModal = ({ + open, + onClose, + onConfirm, + price, +}: PaymentConfirmationModalProps) => { + return ( + + + + Вы подтверждаете платёж в сумму{" "} + {price ? price.toFixed(2) : 0} ₽ + + + + + ); +}; \ No newline at end of file diff --git a/src/pages/Tariffs/components/TariffsHeader.tsx b/src/pages/Tariffs/components/TariffsHeader.tsx new file mode 100644 index 00000000..63965510 --- /dev/null +++ b/src/pages/Tariffs/components/TariffsHeader.tsx @@ -0,0 +1,83 @@ +import { useToken } from "@frontend/kitui"; +import ArrowLeft from "@icons/questionsPage/arrowLeft"; +import { + Box, + Container, + IconButton, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useUserStore } from "@root/user"; +import { LogoutButton } from "@ui_kit/LogoutButton"; +import { handleLogoutClick } from "@utils/HandleLogoutClick"; +import { Link, useNavigate } from "react-router-dom"; +import Logotip from "../../../pages/Landing/images/icons/QuizLogo"; + +interface TariffsHeaderProps { + cashString: string; +} + +export const TariffsHeader = ({ cashString }: TariffsHeaderProps) => { + const theme = useTheme(); + const navigate = useNavigate(); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + + return ( + + + + + navigate("/list")}> + + + + + + Мой баланс + + 9 ? "13px" : "16px") : "16px" + } + > + {cashString} + + + { + navigate("/"); + handleLogoutClick(); + }} + sx={{ + ml: "20px", + }} + /> + + + ); +}; \ No newline at end of file diff --git a/src/pages/Tariffs/pages/HideLogo.tsx b/src/pages/Tariffs/pages/HideLogo.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/Tariffs/pages/Other.tsx b/src/pages/Tariffs/pages/Other.tsx deleted file mode 100644 index 5373da51..00000000 --- a/src/pages/Tariffs/pages/Other.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Box, useMediaQuery, useTheme } from "@mui/material" -import { NavCard } from "../components/NavCard" -import { createTariffElements } from "../tariffsUtils/createTariffElements" -import SmallIconPena from "@/assets/icons/SmallIconPena" - -interface Props { - content: { - title: string, - onClick: () => void - }[] - selectedItem: TypePages -} - -export const Other = ({ - content, - selectedItem, - - tariffs, - user, - discounts, - openModalHC, - userPrivilegies, - startRequestCreate -}: any) => { - const theme = useTheme() - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); -const sendRequest = userPrivilegies?.quizManual?.amount > 0 ? startRequestCreate : undefined - - switch (selectedItem) { - case "hide": - const filteredBadgeTariffs = tariffs.filter((tariff) => { - return ( - tariff.privileges[0].serviceKey === "squiz" && - !tariff.isDeleted && - !tariff.isCustom && - tariff.privileges[0].privilegeId === "squizHideBadge" && - tariff.privileges[0]?.type === "day" - ); - }); - return - {createTariffElements( - filteredBadgeTariffs, - false, - user, - discounts, - openModalHC, - )} - - case "create": - const filteredCreateTariffs = tariffs.filter((tariff) => { - return ( - tariff.privileges[0].serviceKey === "squiz" && - !tariff.isDeleted && - !tariff.isCustom && - tariff.privileges[0].privilegeId === "quizManual" && - tariff.privileges[0]?.type === "count" - ); - }); - return - {createTariffElements( - filteredCreateTariffs, - false, - user, - discounts, - openModalHC, - sendRequest, - true, - - )} - - default: - return - {content.map(data => )} - - } -} diff --git a/src/pages/Tariffs/types.ts b/src/pages/Tariffs/types.ts index fd6c0dea..6db0e8d6 100644 --- a/src/pages/Tariffs/types.ts +++ b/src/pages/Tariffs/types.ts @@ -1 +1 @@ -type TypePages = "count" | "day" | "dop" | "hide" | "create" \ No newline at end of file +type TypePages = "count" | "day" | "dop" | "hide" | "create" | "premium" | "analytics" | "custom" \ No newline at end of file diff --git a/src/pages/Tariffs/utils.ts b/src/pages/Tariffs/utils.ts new file mode 100644 index 00000000..ca612a35 --- /dev/null +++ b/src/pages/Tariffs/utils.ts @@ -0,0 +1,46 @@ +import { cartApi } from "@api/cart"; +import { cancelCC } from "@/stores/cc"; + +export const inCart = () => { + let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]"); + if (Array.isArray(saveCart)) { + saveCart.forEach(async (id: string) => { + const [_, addError] = await cartApi.add(id); + + if (addError) { + console.error(addError); + } else { + let index = saveCart.indexOf("green"); + + if (index !== -1) { + saveCart.splice(index, 1); + } + + localStorage.setItem("saveCart", JSON.stringify(saveCart)); + } + }); + } else { + localStorage.setItem("saveCart", "[]"); + } +}; + +export const outCart = (cart: string[]) => { + //Сделаем муторно и подольше, зато при прерывании сессии данные потеряются минимально + if (cart.length > 0) { + cart.forEach(async (id: string) => { + const [_, deleteError] = await cartApi.delete(id); + + if (deleteError) { + console.error(deleteError); + cancelCC()//мы хотели открыть модалку после покупки тарифа на создание квиза, но не вышло и модалку не откроем + + return; + } + + let saveCart = JSON.parse(localStorage.getItem("saveCart") || "[]") || []; + if (!Array.isArray(saveCart)) saveCart = [] + saveCart = saveCart.push(id); + localStorage.setItem("saveCart", JSON.stringify(saveCart)); + }); + } +}; \ No newline at end of file diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx index dabf007a..9ffc4d44 100755 --- a/src/pages/createQuize/QuizCard.tsx +++ b/src/pages/createQuize/QuizCard.tsx @@ -6,7 +6,7 @@ import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import { Box, Button, IconButton, Popover, Typography, useMediaQuery, useTheme } from "@mui/material"; import { deleteQuiz, setEditQuizId } from "@root/quizes/actions"; import { Link, useNavigate } from "react-router-dom"; -import { inCart } from "../../pages/Tariffs/Tariffs"; +import { inCart } from "../../pages/Tariffs/utils"; import { makeRequest } from "@api/makeRequest"; import { enqueueSnackbar } from "notistack"; import { useDomainDefine } from "@utils/hooks/useDomainDefine"; diff --git a/src/ui_kit/FloatingSupportChat/Chat.tsx b/src/ui_kit/FloatingSupportChat/Chat.tsx index 7ce80acd..ab7f231f 100644 --- a/src/ui_kit/FloatingSupportChat/Chat.tsx +++ b/src/ui_kit/FloatingSupportChat/Chat.tsx @@ -1,9 +1,6 @@ import { Box, - FormControl, IconButton, - InputAdornment, - InputBase, SxProps, Theme, Typography, @@ -17,23 +14,13 @@ import { useTicketStore, } from "@root/ticket"; import type { TouchEvent, WheelEvent } from "react"; -import * as React from "react"; -import { useEffect, useMemo, useRef, useState } from "react"; -import ChatMessage from "./ChatMessage"; -import ChatVideo from "./ChatVideo"; -import SendIcon from "@icons/SendIcon"; +import { useEffect, useMemo, useRef } from "react"; +import ChatMessageRenderer from "./ChatMessageRenderer"; +import ChatInput from "./ChatInput"; import UserCircleIcon from "./UserCircleIcon"; import { throttle, TicketMessage } from "@frontend/kitui"; import ArrowLeft from "@icons/questionsPage/arrowLeft"; import { useUserStore } from "@root/user"; -import AttachFileIcon from "@mui/icons-material/AttachFile"; -import ChatImage from "./ChatImage"; -import ChatDocument from "@ui_kit/FloatingSupportChat/ChatDocument"; -import { - ACCEPT_SEND_MEDIA_TYPES_MAP, - checkAcceptableMediaType, -} from "@utils/checkAcceptableMediaType"; -import { enqueueSnackbar } from "notistack"; interface Props { open: boolean; @@ -41,22 +28,20 @@ interface Props { onclickArrow?: () => void; sendMessage: (a: string) => Promise; sendFile: (a: File | undefined) => Promise; - greetingMessage: TicketMessage; } +const greetingMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; + export default function Chat({ open = false, sx, onclickArrow, sendMessage, sendFile, - greetingMessage, }: Props) { const theme = useTheme(); const upMd = useMediaQuery(theme.breakpoints.up("md")); const isMobile = useMediaQuery(theme.breakpoints.down(800)); - const [messageField, setMessageField] = useState(""); - const [disableFileButton, setDisableFileButton] = useState(false); const user = useUserStore((state) => state.user?._id); const ticket = useTicketStore( @@ -72,31 +57,11 @@ export default function Chat({ const chatBoxRef = useRef(null); useEffect(() => { - addOrUpdateUnauthMessages([greetingMessage]); if (open) { scrollToBottom(); } }, [open]); - const sendMessageHC = async () => { - const successful = await sendMessage(messageField); - if (successful) { - setMessageField(""); - } - }; - const sendFileHC = async (file: File) => { - const check = checkAcceptableMediaType(file); - if (check.length > 0) { - enqueueSnackbar(check); - return; - } - setDisableFileButton(true); - await sendFile(file); - setDisableFileButton(false); - }; - - const fileInputRef = useRef(null); - const throttledScrollHandler = useMemo( () => throttle(() => { @@ -152,14 +117,6 @@ export default function Chat({ behavior, }); } - const handleTextfieldKeyPress: React.KeyboardEventHandler< - HTMLInputElement | HTMLTextAreaElement - > = (e) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - sendMessageHC(); - } - }; return ( <> @@ -240,164 +197,34 @@ export default function Chat({ > {ticket.sessionData?.ticketId && messages.map((message) => { - const isFileVideo = () => { - if (message.files) { - return ACCEPT_SEND_MEDIA_TYPES_MAP.video.some( - (fileType) => - message.files[0].toLowerCase().endsWith(fileType), - ); - } - }; - const isFileImage = () => { - if (message.files) { - return ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some( - (fileType) => - message.files[0].toLowerCase().endsWith(fileType), - ); - } - }; - const isFileDocument = () => { - if (message.files) { - return ACCEPT_SEND_MEDIA_TYPES_MAP.document.some( - (fileType) => - message.files[0].toLowerCase().endsWith(fileType), - ); - } - }; - if (message.files.length > 0 && isFileImage()) { - return ( - - ); - } - if (message.files.length > 0 && isFileVideo()) { - return ( - - ); - } - if (message.files.length > 0 && isFileDocument()) { - return ( - - ); - } + const isSelf = useMemo(() => + (ticket.sessionData?.sessionId || user) === message.user_id, + [ticket.sessionData?.sessionId, user, message.user_id] + ); + return ( - ); })} {!ticket.sessionData?.ticketId && ( - + (ticket.sessionData?.sessionId || user) === greetingMessage.user_id, + [ticket.sessionData?.sessionId, user, greetingMessage.user_id] + )} /> )} - - setMessageField(e.target.value)} - endAdornment={ - - { - if (!disableFileButton) fileInputRef.current?.click(); - }} - > - - - { - if (e.target.files?.[0]) - sendFileHC(e.target.files?.[0]); - }} - style={{ display: "none" }} - type="file" - /> - - - - - } - /> - + )} diff --git a/src/ui_kit/FloatingSupportChat/ChatInput.tsx b/src/ui_kit/FloatingSupportChat/ChatInput.tsx new file mode 100644 index 00000000..ba17b903 --- /dev/null +++ b/src/ui_kit/FloatingSupportChat/ChatInput.tsx @@ -0,0 +1,137 @@ +import { useCallback, useRef, useState } from "react"; +import { + FormControl, + IconButton, + InputAdornment, + InputBase, + useMediaQuery, + useTheme, +} from "@mui/material"; +import SendIcon from "@icons/SendIcon"; +import AttachFileIcon from "@mui/icons-material/AttachFile"; +import { checkAcceptableMediaType } from "@utils/checkAcceptableMediaType"; +import { enqueueSnackbar } from "notistack"; + +interface ChatInputProps { + sendMessage: (message: string) => Promise; + sendFile: (file: File | undefined) => Promise; + isMessageSending: boolean; +} + +const ChatInput = ({ sendMessage, sendFile, isMessageSending }: ChatInputProps) => { + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const [messageField, setMessageField] = useState(""); + const [disableFileButton, setDisableFileButton] = useState(false); + const fileInputRef = useRef(null); + + const handleSendMessage = useCallback(async () => { + const successful = await sendMessage(messageField); + if (successful) { + setMessageField(""); + } + }, [sendMessage, messageField]); + + const handleSendFile = useCallback(async (file: File) => { + const check = checkAcceptableMediaType(file); + if (check.length > 0) { + enqueueSnackbar(check); + return; + } + setDisableFileButton(true); + await sendFile(file); + setDisableFileButton(false); + }, [sendFile]); + + const handleFileInputChange = useCallback((e: React.ChangeEvent) => { + if (e.target.files?.[0]) { + handleSendFile(e.target.files[0]); + } + }, [handleSendFile]); + + const handleFileButtonClick = useCallback(() => { + if (!disableFileButton) { + fileInputRef.current?.click(); + } + }, [disableFileButton]); + + const handleTextfieldKeyPress: React.KeyboardEventHandler< + HTMLInputElement | HTMLTextAreaElement + > = useCallback((e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }, [handleSendMessage]); + + const handleInputChange = useCallback((e: React.ChangeEvent) => { + setMessageField(e.target.value); + }, []); + + return ( + + + + + + + + + + + } + /> + + ); +}; + +export default ChatInput; \ No newline at end of file diff --git a/src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx b/src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx new file mode 100644 index 00000000..4955915e --- /dev/null +++ b/src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx @@ -0,0 +1,70 @@ +import { memo, useMemo } from "react"; +import { TicketMessage } from "@frontend/kitui"; +import ChatMessage from "./ChatMessage"; +import ChatImage from "./ChatImage"; +import ChatVideo from "./ChatVideo"; +import ChatDocument from "./ChatDocument"; +import { ACCEPT_SEND_MEDIA_TYPES_MAP } from "@utils/checkAcceptableMediaType"; + +interface ChatMessageRendererProps { + message: TicketMessage; + isSelf: boolean; +} + +const ChatMessageRenderer = memo(({ message, isSelf }: ChatMessageRendererProps) => { + const fileType = useMemo(() => { + if (!message.files?.length) return null; + + const fileName = message.files[0].toLowerCase(); + + if (ACCEPT_SEND_MEDIA_TYPES_MAP.video.some(fileType => fileName.endsWith(fileType))) { + return 'video'; + } + + if (ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some(fileType => fileName.endsWith(fileType))) { + return 'image'; + } + + if (ACCEPT_SEND_MEDIA_TYPES_MAP.document.some(fileType => fileName.endsWith(fileType))) { + return 'document'; + } + + return null; + }, [message.files]); + + // Если есть файлы и определён тип + if (message.files?.length > 0 && fileType) { + const commonProps = { + unAuthenticated: true, + key: message.id, + file: message.files[0], + createdAt: message.created_at, + isSelf, + }; + + switch (fileType) { + case 'image': + return ; + case 'video': + return ; + case 'document': + return ; + default: + break; + } + } + + // Текстовое сообщение + return ( + + ); +}); + +ChatMessageRenderer.displayName = 'ChatMessageRenderer'; + +export default ChatMessageRenderer; \ No newline at end of file diff --git a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx index 1ee0ba3e..2c7f7419 100644 --- a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx +++ b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx @@ -47,7 +47,6 @@ interface Props { sendFile: (a: File | undefined) => Promise; modalWarningType: string | null; setModalWarningType: any; - greetingMessage: TicketMessage; } export default function FloatingSupportChat({ @@ -59,7 +58,6 @@ export default function FloatingSupportChat({ sendFile, modalWarningType, setModalWarningType, - greetingMessage, }: Props) { const [monitorType, setMonitorType] = useState<"desktop" | "mobile" | "">(""); const theme = useTheme(); @@ -108,7 +106,6 @@ export default function FloatingSupportChat({ sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }} sendMessage={sendMessage} sendFile={sendFile} - greetingMessage={greetingMessage} /> { setIsChatOpened((state) => !state); }; - const getGreetingMessage: TicketMessage = useMemo(() => { - const workingHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; - const offHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; - const date = new Date(); - const currentHourUTC = date.getUTCHours(); - const MscTime = 3; // Москва UTC+3; - const moscowHour = (currentHourUTC + MscTime) % 24; - const greetingMessage = - moscowHour >= 3 && moscowHour < 10 - ? offHoursMessage - : workingHoursMessage; - - return { - created_at: new Date().toISOString(), - files: [], - id: "111", - message: greetingMessage, - request_screenshot: "", - session_id: "greetingMessage", - shown: { me: 1 }, - ticket_id: "111", - user_id: "greetingMessage", - }; - }, [isChatOpened]); - useTicketsFetcher({ url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`, ticketsPerPage: 10, @@ -157,7 +130,6 @@ export default () => { ); if (isTicketClosed) { cleanAuthTicketData(); - addOrUpdateUnauthMessages([getGreetingMessage]); if (!user) { cleanUnauthTicketData(); localStorage.removeItem("unauth-ticket"); @@ -185,8 +157,8 @@ export default () => { ({ shown }) => shown?.me !== 1, ); - newMessages.map(async ({ id }) => { - await shownMessage(id); + newMessages.forEach(({ id, user_id }) => { + if ((ticket.sessionData?.sessionId || user) === user_id) shownMessage(id); }); } }, [isChatOpened, ticket.messages]); @@ -248,7 +220,6 @@ export default () => { sendFile={sendFile} modalWarningType={modalWarningType} setModalWarningType={setModalWarningType} - greetingMessage={getGreetingMessage} /> ); }; diff --git a/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts b/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts deleted file mode 100644 index e9f42cbe..00000000 --- a/src/ui_kit/FloatingSupportChat/useTechnicalSupport.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { useSSETab } from "@/utils/hooks/useSSETab"; -import { parseAxiosError } from "@/utils/parse-error"; -import { TicketMessage, createTicket, useSSESubscription, useTicketMessages, useTicketsFetcher, sendFile as sf, sendTicketMessage, shownMessage } from "@frontend/kitui"; - -import { - addOrUpdateUnauthMessages, - cleanAuthTicketData, - cleanUnauthTicketData, - setIsMessageSending, - setTicketData, - setUnauthIsPreventAutoscroll, - setUnauthTicketMessageFetchState, - useTicketStore, -} from "@root/ticket"; -import { enqueueSnackbar } from "notistack"; -import { useCallback, useEffect, useMemo, useState } from "react"; - -interface Props { - userId?: string; - -} - -type ModalWarningType = - | "errorType" - | "errorSize" - | "picture" - | "video" - | "audio" - | "document" - | null; -const MAX_FILE_SIZE = 419430400; -const ACCEPT_SEND_FILE_TYPES_MAP = [ - ".jpeg", - ".jpg", - ".png", - ".mp4", - ".doc", - ".docx", - ".pdf", - ".txt", - ".xlsx", - ".csv", -] as const; -export default ({ userId }: Props) => { - const ticket = useTicketStore((state) => state[userId ? "authData" : "unauthData"]); - - const { isActiveSSETab, updateSSEValue } = useSSETab( - "ticket", - addOrUpdateUnauthMessages, - ); - - const [modalWarningType, setModalWarningType] = - useState(null); - const [isChatOpened, setIsChatOpened] = useState(false); - const [sseEnabled, setSseEnabled] = useState(true); - - const handleChatClickOpen = () => { - setIsChatOpened(true); - }; - const handleChatClickClose = () => { - setIsChatOpened(false); - }; - const handleChatClickSwitch = () => { - setIsChatOpened((state) => !state); - }; - - const getGreetingMessage: TicketMessage = useMemo(() => { - const workingHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; - const offHoursMessage = - "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут"; - const date = new Date(); - const currentHourUTC = date.getUTCHours(); - const MscTime = 3; // Москва UTC+3; - const moscowHour = (currentHourUTC + MscTime) % 24; - const greetingMessage = - moscowHour >= 3 && moscowHour < 10 - ? offHoursMessage - : workingHoursMessage; - - return { - created_at: new Date().toISOString(), - files: [], - id: "111", - message: greetingMessage, - request_screenshot: "", - session_id: "greetingMessage", - shown: { me: 1 }, - ticket_id: "111", - user_id: "greetingMessage", - }; - }, [isChatOpened]); - - 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(userId), - }); - - useTicketMessages({ - url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getMessages`, - isUnauth: true, - ticketId: ticket.sessionData?.ticketId, - messagesPerPage: ticket.messagesPerPage, - messageApiPage: ticket.apiPage, - onSuccess: useCallback((messages) => { - addOrUpdateUnauthMessages(messages); - }, []), - onError: useCallback((error: Error) => { - if (error.name === "CanceledError") { - return; - } - - const [message] = parseAxiosError(error); - if (message) enqueueSnackbar(message); - }, []), - onFetchStateChange: setUnauthTicketMessageFetchState, - }); - - useSSESubscription({ - enabled: - sseEnabled && isActiveSSETab && Boolean(ticket.sessionData?.sessionId), - url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/ticket?ticket=${ticket.sessionData?.ticketId}&s=${ticket.sessionData?.sessionId}`, - onNewData: (ticketMessages) => { - const isTicketClosed = ticketMessages.some( - (message) => message.session_id === "close", - ); - if (isTicketClosed) { - cleanAuthTicketData(); - addOrUpdateUnauthMessages([getGreetingMessage]); - if (!userId) { - cleanUnauthTicketData(); - localStorage.removeItem("unauth-ticket"); - } - return; - } - updateSSEValue(ticketMessages); - addOrUpdateUnauthMessages(ticketMessages); - }, - onDisconnect: useCallback(() => { - setUnauthIsPreventAutoscroll(false); - setSseEnabled(false); - }, []), - marker: "ticket", - }); - - useEffect(() => { - cleanAuthTicketData(); - setSseEnabled(true); - }, [userId]); - - useEffect(() => { - if (isChatOpened) { - const newMessages = ticket.messages.filter( - ({ shown }) => shown?.me !== 1, - ); - - newMessages.map(async ({ id }) => { - await shownMessage(id); - }); - } - }, [isChatOpened, ticket.messages]); - - const sendMessage = async (messageField: string) => { - if (!messageField || ticket.isMessageSending) return false; - setSseEnabled(true); - let successful = false; - setIsMessageSending(true); - if (!ticket.sessionData?.ticketId) { - const [data, createError] = await createTicket({ - message: messageField, - useToken: Boolean(userId), - systemError: false - }); - - if (createError || !data) { - successful = false; - - enqueueSnackbar(`Не удалось создать чат ${(createError)}`); - } else { - successful = true; - - setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); - } - - setIsMessageSending(false); - } else { - const [_, sendTicketMessageError] = await sendTicketMessage({ - ticketId: ticket.sessionData?.ticketId, - message: messageField, - systemError: false - }); - successful = true; - - if (sendTicketMessageError) { - successful = false; - enqueueSnackbar(`Ошибка отправки сообщения ${parseAxiosError(sendTicketMessageError)}`); - } - setIsMessageSending(false); - } - - return successful; - }; - const sendFile = async (file: File) => { - if (file === undefined) return true; - - let ticketId = ticket.sessionData?.ticketId; - if (!ticket.sessionData?.ticketId) { - const [data, createError] = await createTicket({ - message: "", - useToken: Boolean(userId), - systemError: false - }); - ticketId = data?.Ticket; - - if (createError || !data) { - enqueueSnackbar(`Не удалось создать диалог ${parseAxiosError(createError)}`); - } else { - setTicketData({ ticketId: data.Ticket, sessionId: data.sess }); - } - - setIsMessageSending(false); - } - - if (ticketId !== undefined) { - if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize"); - - const [_, sendFileError] = await sf({ - ticketId, - file - }); - - if (sendFileError) { - enqueueSnackbar(sendFileError); - } - - return true; - } - }; - - return { - isChatOpened, - handleChatClickOpen, - handleChatClickClose, - handleChatClickSwitch, - sendMessage, - sendFile, - modalWarningType, - setModalWarningType, - getGreetingMessage - }; -}; \ No newline at end of file diff --git a/src/utils/hooks/usePipeSubscriber.ts b/src/utils/hooks/usePipeSubscriber.ts index 3c450e6b..8a9c2303 100644 --- a/src/utils/hooks/usePipeSubscriber.ts +++ b/src/utils/hooks/usePipeSubscriber.ts @@ -4,7 +4,7 @@ import { useSSETab } from "./useSSETab"; import { cancelPayCartProcess } from "@/stores/notEnoughMoneyAmount"; import { setCash } from "@/stores/cash"; import { currencyFormatter } from "@/pages/Tariffs/tariffsUtils/currencyFormatter"; -import { inCart } from "@/pages/Tariffs/Tariffs"; +import { inCart } from "@/pages/Tariffs/utils"; type Ping = [{ event: "ping" }] From acfc9727ed656571cd3ba59038be2b90f1f4fc5b Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 14 Jul 2025 22:44:09 +0300 Subject: [PATCH 03/18] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=20useAfetPay=20=D0=BD=D0=B0=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D1=83=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Tariffs/Tariffs.tsx | 4 +- src/utils/hooks/useAutoPay.ts | 76 ++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index ed8fc735..2d88db90 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -45,8 +45,8 @@ function TariffPage() { const navigate = useNavigate(); const user = useUserStore((state) => state.customerAccount); const userWithWallet = useUserStore((state) => state.customerAccount); //c wallet - console.log("________________34563875693785692576_____________USERRRRRRR") - // console.log(userWithWallet) + console.log("________________userWithWallet_____________USERRRRRRR") + console.log(userWithWallet) const { data: discounts } = useDiscounts(userId); const [isRequestCreate, setIsRequestCreate] = useState(false); const [openModal, setOpenModal] = useState({}); diff --git a/src/utils/hooks/useAutoPay.ts b/src/utils/hooks/useAutoPay.ts index cc46fde5..7c1d9956 100644 --- a/src/utils/hooks/useAutoPay.ts +++ b/src/utils/hooks/useAutoPay.ts @@ -1,44 +1,41 @@ import { cartApi } from "@api/cart"; import { useUserStore } from "@/stores/user"; -import moment from "moment"; import { enqueueSnackbar } from "notistack"; import { useEffect } from "react"; -import { redirect, useNavigate, useSearchParams } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { calcTimeOfReadyPayCart, cancelPayCartProcess, startPayCartProcess, useNotEnoughMoneyAmount } from "@/stores/notEnoughMoneyAmount"; import { startCC } from "@/stores/cc"; import { setEditQuizId, setCurrentStep } from "@root/quizes/actions"; +/* +Есть три пути по которому мы ходили из квиза в хаб. Нам нехватило денег при: +1)Покупке обычного тарифа +2)Покупке тарифа-заказ-квиза +3)Покупке тарифа в настройке квиза в вкладке ИИ +*/ + export const useAfterPay = () => { const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); + const userId = useUserStore(store => store.userId) const userAccount = useUserStore(state => state.userAccount); + const userWithWallet = useUserStore((state) => state.customerAccount); //c wallet const siteReadyPayCart = useNotEnoughMoneyAmount(state => state.siteReadyPayCart); - const purpose = searchParams.get("purpose"); - const paymentUserId = searchParams.get("userid"); - const currentCC = searchParams.get("cc"); - const wayback = searchParams.get("wayback"); - - // Обработка wayback параметра - useEffect(() => { - if (wayback) { - const quizId = wayback.split("_")[1]; - if (quizId) { - setEditQuizId(Number(quizId)); - setCurrentStep(17); // Шаг для персонализации AI - navigate("/personalization-ai"); - } - } - }, [wayback, navigate]); + let URLaction = searchParams.get("action");//что мы, собсна, хотим: оплатить, пополнить, заказать квиз + let URLuserId = searchParams.get("userid");//тот кто начал всё это действо + let URLadditionalinformation = searchParams.get("additionalinformation");//его токен useEffect(() => { - //Звёзды сошлись, будем оплачивать корзину - if (paymentUserId && paymentUserId === userId) { + setSearchParams({}, { replace: true }); - if (purpose === "paycart") { - setSearchParams({}, { replace: true }); - if (currentCC) startCC() + if (userId && URLuserId && userId === URLuserId) { + + if (URLaction === "buy") startPayCartProcess(URLuserId); + + if (URLaction === "createquizcc") { + startCC(); (async () => { //Проверяем можем ли мы оплатить корзину здесь и сейчас @@ -46,19 +43,32 @@ export const useAfterPay = () => { if (payCartError) { //Не получилось купить корзину. Ставим флаг, что сайт в состоянии ожидания пополнения счёта для оплаты - startPayCartProcess(paymentUserId) + startPayCartProcess(URLuserId); } else { - if (currentCC) navigate("/tariffs") - cancelPayCartProcess() + navigate("/tariffs"); + cancelPayCartProcess(); } })() } - } - }, [purpose, paymentUserId]) + + //Покупка ИИ тарифа из настройки квиза ИИ вкладки + if (location.pathname === "/personalization-ai") { + const quizId = Number(URLadditionalinformation); + if (quizId) { + setEditQuizId(Number(quizId)); //Выбираем квиз + setCurrentStep(17); // Шаг для персонализации AI + navigate("/personalization-ai"); //Непосредственно переход + } + } + + } + }, []); + + //Обработка необходимости купить после пополнения useEffect(() => { if (userId !== null && siteReadyPayCart !== null && siteReadyPayCart[userId] !== undefined) { - const deadline = siteReadyPayCart[userId] + const deadline = siteReadyPayCart[userId]; if (calcTimeOfReadyPayCart(deadline)) { //Время ещё не вышло. У нас стоит флаг покупать корзину если время не вышло. @@ -66,11 +76,13 @@ export const useAfterPay = () => { const [, payCartError] = await cartApi.pay(); if (!payCartError) { - enqueueSnackbar("Товары успешно приобретены") - cancelPayCartProcess() + enqueueSnackbar("Товары успешно приобретены"); + cancelPayCartProcess(); } })() } } - }, [userAccount, userId, siteReadyPayCart]) + }, [userAccount, userId, siteReadyPayCart, userWithWallet]) + + } \ No newline at end of file From 0d0acfd7b9a32b9f0db53c93e289c038e3bb28a0 Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 16 Jul 2025 09:21:03 +0300 Subject: [PATCH 04/18] =?UTF-8?q?=D1=81=D1=86=D0=B5=D0=BD=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=83=D1=80=D0=BB=D0=B0=20=D0=BD=D0=B0=20=D1=85=D0=B0?= =?UTF-8?q?=D0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersonalizationAI/PersonalizationAI.tsx | 15 +++++++++--- src/pages/Tariffs/Tariffs.tsx | 12 +++++++--- src/utils/generateHubWalletRequest.ts | 24 +++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/utils/generateHubWalletRequest.ts diff --git a/src/pages/PersonalizationAI/PersonalizationAI.tsx b/src/pages/PersonalizationAI/PersonalizationAI.tsx index 0aacd104..84ca429f 100644 --- a/src/pages/PersonalizationAI/PersonalizationAI.tsx +++ b/src/pages/PersonalizationAI/PersonalizationAI.tsx @@ -21,6 +21,7 @@ import { setUserAccount, setCustomerAccount } from "@/stores/user"; import { quizApi } from "@api/quiz"; import { setQuizes } from "@root/quizes/actions"; import TooltipClickInfo from "@/ui_kit/Toolbars/TooltipClickInfo"; +import { generateHubWalletRequestURL } from "@/utils/generateHubWalletRequest"; const tariff = isTestServer ? "6844b8858258f5cc35791ef7" : "6851db40acfb4d3e5fcd9b19"; export default function PersonalizationAI() { @@ -240,7 +241,15 @@ export default function PersonalizationAI() { //если денег не хватило if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) { var link = document.createElement("a"); - link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=50000&data=${token}&userid=${userId}&from=AI&wayback=ai_${quiz?.backendId}`; + link.href = generateHubWalletRequestURL({ + wayback: "edit", + action: "buy", + dif: "50000", + userid: userId, + additionalinformation: quiz?.backendId.toString(), + token + }); + //link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=50000&data=${token}&userid=${userId}&from=AI&wayback=ai_${quiz?.backendId}`; document.body.appendChild(link); link.click(); return; @@ -289,7 +298,7 @@ export default function PersonalizationAI() { lineHeight: "21.4px" }}> Данный раздел позволяет вам создавать персонализированный опрос под каждую целевую аудиторию отдельно, наш AI перефразирует ваши вопросы согласно настройкам. -
Для этого нужно выбрать пол и возраст вашей аудитории и получите персональную ссылку с нужными настройками в списке ниже. +
Для этого нужно выбрать пол и возраст вашей аудитории и получите персональную ссылку с нужными настройками в списке ниже. - Так же вы можете обогатить свою ссылку UTM метками в поле "вставьте свою ссылку" и эти метки применятся ко всем вашим ссылкам. + Так же вы можете обогатить свою ссылку UTM метками в поле "вставьте свою ссылку" и эти метки применятся ко всем вашим ссылкам. = { day: "Тарифы на время", @@ -70,7 +71,6 @@ console.log(tariffs) setCash(cs, cc, cr); } }, [userWithWallet]); - useEffect(() => { if (cc) { setIsRequestCreate(true) @@ -104,8 +104,14 @@ console.log(tariffs) if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) { let cashDif = Number(payError.split(":")[1]); var link = document.createElement("a"); - link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=${cashDif}&data=${token}&userid=${userId}`; - if (cc) link.href = link.href + "&cc=true"//после покупки тарифа и возвращения будем знать что надо открыть модалку + link.href = generateHubWalletRequestURL({ + action: cc ? "createquizcc" : "buy", + dif: cashDif.toString(), + userid: userId, + token + }); + // link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=${cashDif}&data=${token}&userid=${userId}`; + // if (cc) link.href = link.href + "&cc=true"//после покупки тарифа и возвращения будем знать что надо открыть модалку document.body.appendChild(link); link.click(); return; diff --git a/src/utils/generateHubWalletRequest.ts b/src/utils/generateHubWalletRequest.ts new file mode 100644 index 00000000..597bb2c0 --- /dev/null +++ b/src/utils/generateHubWalletRequest.ts @@ -0,0 +1,24 @@ +import { isTestServer } from "./hooks/useDomainDefine"; + +export const generateHubWalletRequestURL = ({ + wayback, + action, + dif, + userid, + additionalinformation, + token +}:{ + wayback?: string; + action: "topupwallet" | "createquizcc" | "buy"; + dif: string; + userid: string; + additionalinformation?: string; + token: string; +}) => { + let currentDomain = window.location.host; + if (currentDomain === "localhost") currentDomain += ":3000"; + let url = `https://${isTestServer ? "s" : ""}hub.pena.digital/payment?fromdomain=${currentDomain}&action=${action}&dif=${dif}&userid=${userid}&sec=${token}`; + if (additionalinformation) url += `&additionalinformation=${additionalinformation}`; + if (wayback) url += `&wayback=${wayback}`; + return url; +} \ No newline at end of file From 33b695ef13946208f4d50491bd95e7a8b596b87d Mon Sep 17 00:00:00 2001 From: Nastya Date: Sun, 20 Jul 2025 15:17:39 +0300 Subject: [PATCH 05/18] =?UTF-8?q?fix=20recover=20and=20fix=20customer=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20=D0=B2=20=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 11 ++- src/pages/Payment/Payment.tsx | 12 +++ src/pages/Tariffs/Tabs.tsx | 8 +- src/pages/Tariffs/Tariffs.tsx | 98 ++++++++++++++++++++---- src/pages/auth/Signup.tsx | 2 +- src/stores/user.ts | 4 +- src/utils/generateHubWalletRequest.ts | 22 +++++- src/utils/hooks/useAuthRedirect.ts | 80 +++++++++++++++++++ src/utils/hooks/useUserAccountFetcher.ts | 44 +++++++++-- 9 files changed, 252 insertions(+), 29 deletions(-) create mode 100644 src/pages/Payment/Payment.tsx create mode 100644 src/utils/hooks/useAuthRedirect.ts diff --git a/src/App.tsx b/src/App.tsx index d6195646..e86c5fea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -97,8 +97,17 @@ export default function App() { useUserAccountFetcher({ url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/account`, userId, - onNewUserAccount: setCustomerAccount, + onNewUserAccount: (account) => { + console.log("App: Setting customerAccount:", account); + console.log("App: customerAccount structure:", { + userId: account?.userId, + wallet: account?.wallet, + cart: account?.cart + }); + setCustomerAccount(account); + }, onError: (error) => { + console.log("App: Error in customerAccount fetcher:", error); const errorMessage = getMessageFromFetchError(error); if (errorMessage) { enqueueSnackbar(errorMessage); diff --git a/src/pages/Payment/Payment.tsx b/src/pages/Payment/Payment.tsx new file mode 100644 index 00000000..5a410641 --- /dev/null +++ b/src/pages/Payment/Payment.tsx @@ -0,0 +1,12 @@ +import { useAuthRedirect } from "../../utils/hooks/useAuthRedirect"; + +export default function Payment() { + // Используем хук авторизации + const { isProcessing } = useAuthRedirect(); + + // Если идет обработка авторизации, показываем загрузку + if (isProcessing) { + return
Идёт загрузка...
; + } + + // ... existing component code ... \ No newline at end of file diff --git a/src/pages/Tariffs/Tabs.tsx b/src/pages/Tariffs/Tabs.tsx index bcfeec11..1cae19d1 100644 --- a/src/pages/Tariffs/Tabs.tsx +++ b/src/pages/Tariffs/Tabs.tsx @@ -1,11 +1,13 @@ import { Tabs as MuiTabs } from "@mui/material"; import { CustomTab } from "./CustomTab"; +import { TypePages } from "./types"; type TabsProps = { names: string[]; items: string[]; - selectedItem: "day" | "count" | "dop" | "hide" | "create"; - setSelectedItem: (num: "day" | "count" | "dop") => void; + selectedItem: TypePages; + setSelectedItem: (num: TypePages) => void; + toDop: () => void; }; export const Tabs = ({ @@ -18,7 +20,7 @@ export const Tabs = ({ sx={{ m: "25px" }} TabIndicatorProps={{ sx: { display: "none" } }} value={selectedItem} - onChange={(event, newValue: "day" | "count" | "dop") => { + onChange={(event, newValue: TypePages) => { setSelectedItem(newValue); }} variant="scrollable" diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index 3d8321c6..318c8f46 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -46,11 +46,16 @@ function TariffPage() { const navigate = useNavigate(); const user = useUserStore((state) => state.customerAccount); const userWithWallet = useUserStore((state) => state.customerAccount); //c wallet + const userAccount = useUserStore((state) => state.userAccount); console.log("________________userWithWallet_____________USERRRRRRR") console.log(userWithWallet) - const { data: discounts } = useDiscounts(userId); + console.log("________________userAccount_____________") + console.log(userAccount) + console.log("________________customerAccount_____________") + console.log(user) + const { data: discounts, error: discountsError, isLoading: discountsLoading } = useDiscounts(userId); const [isRequestCreate, setIsRequestCreate] = useState(false); - const [openModal, setOpenModal] = useState({}); + const [openModal, setOpenModal] = useState<{ id?: string; price?: number }>({}); const { cashString, cashCop, cashRub } = useWallet(); const [selectedItem, setSelectedItem] = useState("day"); const { isTestServer } = useDomainDefine(); @@ -64,20 +69,70 @@ console.log("________34563875693785692576_____ TARIFFS") console.log(tariffs) useEffect(() => { - if (userWithWallet) { + if (userWithWallet && user) { let cs = currencyFormatter.format(Number(user.wallet.cash) / 100); let cc = Number(user.wallet.cash); let cr = Number(user.wallet.cash) / 100; setCash(cs, cc, cr); } - }, [userWithWallet]); + }, [userWithWallet, user]); + useEffect(() => { if (cc) { setIsRequestCreate(true) cancelCC() } }, []) - if (!user || !tariffs || !discounts) return ; + + // Добавляем логирование для диагностики + console.log("Tariffs loading state:", { + user: !!user, + userWithWallet: !!userWithWallet, + userId: userId, + tariffs: !!tariffs, + discounts: !!discounts, + tariffsLoading, + tariffsError, + discountsLoading, + discountsError, + userDetails: user ? { + id: user._id, + wallet: user.wallet, + cart: user.cart?.length + } : null + }); + + // Проверяем, что все данные загружены и нет ошибок + const isDataLoading = tariffsLoading || (userId && discountsLoading); + const hasErrors = tariffsError || discountsError; + + // Если userId есть, но customerAccount еще не загружен, показываем загрузку + const isCustomerAccountLoading = userId && !user; + const hasAllData = user && tariffs && (userId ? discounts : true); + + if (isDataLoading || isCustomerAccountLoading) { + console.log("Showing loading page because:", { + dataLoading: isDataLoading, + customerAccountLoading: isCustomerAccountLoading, + userId, + user: !!user + }); + return ; + } + + if (hasErrors) { + console.log("Showing loading page because of errors:", { tariffsError, discountsError }); + return ; + } + + if (!hasAllData) { + console.log("Showing loading page because:", { + noUser: !user, + noTariffs: !tariffs, + noDiscounts: userId && !discounts + }); + return ; + } const openModalHC = (tariffInfo: any) => setOpenModal(tariffInfo); const tryBuy = async ({ id, price }: { id: string; price: number }) => { @@ -99,21 +154,30 @@ console.log(tariffs) //Если нам хватает денежек - покупаем тариф const [data, payError] = await cartApi.pay(); + console.log("payError || !data") + console.log("payError", payError) + console.log("data", data) if (payError || !data) { + console.log("прошли 1 проверку_______") //если денег не хватило if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) { + console.log("прошли 2 проверку_______") let cashDif = Number(payError.split(":")[1]); - var link = document.createElement("a"); - link.href = generateHubWalletRequestURL({ + + if (!userId) { + enqueueSnackbar("Ошибка: ID пользователя не найден"); + return; + } + + const l = generateHubWalletRequestURL({ action: cc ? "createquizcc" : "buy", dif: cashDif.toString(), userid: userId, + wayback: "list", token }); - // link.href = `https://${isTestServer ? "s" : ""}hub.pena.digital/quizpayment?action=squizpay&dif=${cashDif}&data=${token}&userid=${userId}`; - // if (cc) link.href = link.href + "&cc=true"//после покупки тарифа и возвращения будем знать что надо открыть модалку - document.body.appendChild(link); - link.click(); + console.log(l) + window.location.href = l; return; } @@ -257,7 +321,7 @@ console.log(tariffs) tariffs={tariffs} user={user} - discounts={discounts} + discounts={discounts || []} openModalHC={openModalHC} userPrivilegies={userPrivilegies} startRequestCreate={startRequestCreate} @@ -304,7 +368,7 @@ console.log(tariffs) tariffs={tariffs} user={user} - discounts={discounts} + discounts={discounts || []} openModalHC={openModalHC} userPrivilegies={userPrivilegies} startRequestCreate={startRequestCreate} @@ -314,8 +378,12 @@ console.log(tariffs) 0} onClose={() => setOpenModal({})} - onConfirm={() => tryBuy(openModal)} - price={openModal.price} + onConfirm={() => { + if (openModal.id && openModal.price !== undefined) { + tryBuy({ id: openModal.id, price: openModal.price }); + } + }} + price={openModal.price || 0} /> setIsRequestCreate(false)} /> diff --git a/src/pages/auth/Signup.tsx b/src/pages/auth/Signup.tsx index 97cc5d12..204fbe88 100644 --- a/src/pages/auth/Signup.tsx +++ b/src/pages/auth/Signup.tsx @@ -242,7 +242,7 @@ export default function SignupDialog() { diff --git a/src/stores/user.ts b/src/stores/user.ts index 20352de0..99354f16 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -71,5 +71,7 @@ export const clearUserData = () => useUserStore.setState({ ...initialState }); export const setUserAccount = (userAccount: OriginalUserAccount) => useUserStore.setState({ userAccount }); -export const setCustomerAccount = (customerAccount: UserAccount) => +export const setCustomerAccount = (customerAccount: UserAccount) => { + console.log("setCustomerAccount called with:", customerAccount); useUserStore.setState({ customerAccount }); +}; diff --git a/src/utils/generateHubWalletRequest.ts b/src/utils/generateHubWalletRequest.ts index 597bb2c0..bfffc794 100644 --- a/src/utils/generateHubWalletRequest.ts +++ b/src/utils/generateHubWalletRequest.ts @@ -17,8 +17,24 @@ export const generateHubWalletRequestURL = ({ }) => { let currentDomain = window.location.host; if (currentDomain === "localhost") currentDomain += ":3000"; - let url = `https://${isTestServer ? "s" : ""}hub.pena.digital/payment?fromdomain=${currentDomain}&action=${action}&dif=${dif}&userid=${userid}&sec=${token}`; - if (additionalinformation) url += `&additionalinformation=${additionalinformation}`; - if (wayback) url += `&wayback=${wayback}`; + + // Используем более надежный способ генерации URL + const baseUrl = `http://localhost:3001/anyservicepayment`; + const params = new URLSearchParams({ + fromdomain: currentDomain, + action: action, + dif: dif, + userid: userid, + sec: token + }); + + if (additionalinformation) params.append('additionalinformation', additionalinformation); + if (wayback) params.append('wayback', wayback); + + let url = `${baseUrl}?${params.toString()}`; + + // Для продакшена раскомментировать эту строку: + // let url = `https://${isTestServer ? "s" : ""}hub.pena.digital/payment?${params.toString()}`; + return url; } \ No newline at end of file diff --git a/src/utils/hooks/useAuthRedirect.ts b/src/utils/hooks/useAuthRedirect.ts new file mode 100644 index 00000000..7f9ff030 --- /dev/null +++ b/src/utils/hooks/useAuthRedirect.ts @@ -0,0 +1,80 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { + clearAuthToken, + getMessageFromFetchError, + setAuthToken, + getAuthToken, +} from "@frontend/kitui"; +import { + clearUserData, + setUser, + setUserAccount, + setUserId, + useUserStore, +} from "@root/stores/user"; +import { logout } from "@root/api/auth"; +import { clearCustomTariffs } from "@root/stores/customTariffs"; +import { clearTickets } from "@root/stores/tickets"; +import { setNotEnoughMoneyAmount } from "@stores/cart"; + +export const useAuthRedirect = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const [isProcessing, setIsProcessing] = useState(false); + const user = useUserStore((state) => state.user); + + const action = searchParams.get("action"); + const dif = searchParams.get("dif"); + const token = searchParams.get("data"); + const userId = searchParams.get("userid"); + const wayback = searchParams.get("wayback"); + + useEffect(() => { + if (isProcessing) return; + + // Если пользователь уже авторизован и это тот же пользователь + if (user?._id === userId) { + let returnUrl = `/payment?action=${action}&dif=${dif}&user=${userId}`; + if (wayback) returnUrl += `&wayback=${wayback}`; + navigate(returnUrl, { replace: true }); + return; + } + + // Если есть все необходимые параметры для авторизации + if (action && dif && token && userId) { + setIsProcessing(true); + + (async () => { + try { + // Очищаем старые данные если есть токен + if (getAuthToken()) { + clearAuthToken(); + clearUserData(); + clearCustomTariffs(); + clearTickets(); + setNotEnoughMoneyAmount(0); + await logout(); + } + + // Устанавливаем новый токен и ID пользователя + setAuthToken(token); + setUserId(userId); + + // Перенаправляем на страницу оплаты + let returnUrl = `/payment?action=${action}&dif=${dif}&user=${userId}`; + if (wayback) returnUrl += `&wayback=${wayback}`; + navigate(returnUrl, { replace: true }); + } catch (error) { + console.error("Auth redirect error:", error); + // В случае ошибки перенаправляем на главную страницу тарифов + navigate("/tariffs", { replace: true }); + } finally { + setIsProcessing(false); + } + })(); + } + }, [user, action, dif, token, userId, wayback, navigate, isProcessing]); + + return { isProcessing }; +}; \ No newline at end of file diff --git a/src/utils/hooks/useUserAccountFetcher.ts b/src/utils/hooks/useUserAccountFetcher.ts index f057114f..819e35fb 100644 --- a/src/utils/hooks/useUserAccountFetcher.ts +++ b/src/utils/hooks/useUserAccountFetcher.ts @@ -26,6 +26,7 @@ export const useUserAccountFetcher = ({ useEffect(() => { if (!userId) return; + console.log("useUserAccountFetcher: Starting request for userId:", userId, "url:", url); const controller = new AbortController(); makeRequest({ url, @@ -37,23 +38,56 @@ export const useUserAccountFetcher = ({ }) .then((result) => { devlog("User account", result); - console.log(result) + console.log("useUserAccountFetcher: Success for userId:", userId, "result:", result); if (result) onNewUserAccountRef.current(result); }) .catch((error) => { devlog("Error fetching user account", error); + console.log("useUserAccountFetcher: Error for userId:", userId, "error:", error); if (error.response?.status === 409) return; if (isAxiosError(error) && error.response?.status === 404) { - createUserAccount(controller.signal, url.replace("get", "create")) + console.log("useUserAccountFetcher: Creating user account for userId:", userId); + + // Формируем правильный URL для создания аккаунта + let createUrl = url; + if (url.includes("/customer/v1.0.1/account")) { + // Для customerAccount используем тот же URL (POST запрос) + createUrl = url; + } else if (url.includes("/squiz/account/get")) { + // Для userAccount заменяем get на create + createUrl = url.replace("get", "create"); + } + + console.log("useUserAccountFetcher: Create URL:", createUrl); + + createUserAccount(controller.signal, createUrl) .then((result) => { devlog("Created user account", result); - console.log("это пойдёт в стор: ") - console.log(result) - if (result) onNewUserAccountRef.current(result.created_account as T); + console.log("useUserAccountFetcher: Account created successfully:", result); + + // Проверяем структуру ответа и записываем в стор + if (result) { + // Если результат содержит created_account, используем его + if (result.created_account) { + console.log("useUserAccountFetcher: Using result.created_account"); + onNewUserAccountRef.current(result.created_account as T); + } + // Если результат сам является аккаунтом (для customerAccount) + else if (result.userId && result.wallet) { + console.log("useUserAccountFetcher: Using result directly as customerAccount"); + onNewUserAccountRef.current(result as T); + } + // Если ничего не подходит, логируем для диагностики + else { + console.log("useUserAccountFetcher: Unknown result structure:", result); + onNewUserAccountRef.current(result as T); + } + } }) .catch((error) => { if (error.response?.status === 409) return; devlog("Error creating user account", error); + console.log("useUserAccountFetcher: Error creating account:", error); onErrorRef.current?.(error); }); } else { From 05f2fc33f93845eaae0e5837a2605ecf07030b6f Mon Sep 17 00:00:00 2001 From: Nastya Date: Sun, 20 Jul 2025 15:25:48 +0300 Subject: [PATCH 06/18] =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20=D0=90=D0=98=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D0=BA=D1=83=20=D0=BA=D0=B2=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE=D0=BF=D0=BB=D0=B0?= =?UTF-8?q?=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PersonalizationAI/PersonalizationAI.tsx | 2 +- src/utils/hooks/useAutoPay.ts | 32 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/pages/PersonalizationAI/PersonalizationAI.tsx b/src/pages/PersonalizationAI/PersonalizationAI.tsx index 84ca429f..1476a6b3 100644 --- a/src/pages/PersonalizationAI/PersonalizationAI.tsx +++ b/src/pages/PersonalizationAI/PersonalizationAI.tsx @@ -242,7 +242,7 @@ export default function PersonalizationAI() { if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) { var link = document.createElement("a"); link.href = generateHubWalletRequestURL({ - wayback: "edit", + wayback: "personalization-ai", action: "buy", dif: "50000", userid: userId, diff --git a/src/utils/hooks/useAutoPay.ts b/src/utils/hooks/useAutoPay.ts index 7c1d9956..116fb66e 100644 --- a/src/utils/hooks/useAutoPay.ts +++ b/src/utils/hooks/useAutoPay.ts @@ -28,6 +28,14 @@ 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 }); if (userId && URLuserId && userId === URLuserId) { @@ -53,12 +61,32 @@ export const useAfterPay = () => { //Покупка ИИ тарифа из настройки квиза ИИ вкладки - if (location.pathname === "/personalization-ai") { + if (URLaction === "buy" && URLadditionalinformation) { const quizId = Number(URLadditionalinformation); + console.log("useAutoPay: Processing AI tariff purchase", { + quizId, + wayback: searchParams.get("wayback") + }); + if (quizId) { setEditQuizId(Number(quizId)); //Выбираем квиз setCurrentStep(17); // Шаг для персонализации AI - navigate("/personalization-ai"); //Непосредственно переход + + // Проверяем wayback параметр для определения куда переходить + const wayback = searchParams.get("wayback"); + if (wayback === "edit") { + console.log("useAutoPay: Navigating to /edit first, then /personalization-ai"); + // Сначала переходим на /edit, затем на /personalization-ai + navigate("/edit"); + // Используем setTimeout чтобы дать время для загрузки /edit + setTimeout(() => { + navigate("/personalization-ai"); + }, 100); + } else { + console.log("useAutoPay: Direct navigation to /personalization-ai"); + // Прямой переход на /personalization-ai + navigate("/personalization-ai"); + } } } From ac692fafd32de0814358847c14124f60d2f78e79 Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 23 Jul 2025 08:06:30 +0300 Subject: [PATCH 07/18] =?UTF-8?q?=D0=B2=D0=BD=D0=B5=D1=81=D1=91=D0=BD=20?= =?UTF-8?q?=D0=B1=D0=B0=D0=B3=D1=80=D0=B5=D0=BF=D0=BE=D1=80=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/App.tsx | 58 ++++++- src/api/auth.ts | 2 +- src/api/cart.ts | 2 +- src/api/contactForm.ts | 2 +- src/api/integration.ts | 2 +- src/api/makeRequest.ts | 55 ------- src/api/promocode.ts | 2 +- src/api/question.ts | 2 +- src/api/quiz.ts | 2 +- src/api/result.ts | 2 +- src/api/statistic.ts | 2 +- src/api/tariff.ts | 2 +- src/api/ticket.ts | 2 +- src/api/user.ts | 2 +- src/index.tsx | 8 +- src/pages/Debug.tsx | 149 ++++++++++++++++++ .../Amo/useAmoIntegration.ts | 2 +- .../PersonalizationAI/PersonalizationAI.tsx | 2 +- src/pages/auth/RecoverPassword.tsx | 2 +- src/pages/createQuize/QuizCard.tsx | 2 +- src/stores/ticket.ts | 17 +- src/ui_kit/CheckFastlink.tsx | 2 +- src/ui_kit/FloatingSupportChat/index.tsx | 27 ---- src/utils/handleComponentError.ts | 47 ------ src/utils/hooks/useUserAccountFetcher.ts | 2 +- yarn.lock | 8 +- 27 files changed, 240 insertions(+), 167 deletions(-) delete mode 100644 src/api/makeRequest.ts create mode 100644 src/pages/Debug.tsx delete mode 100644 src/utils/handleComponentError.ts 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" From 2fecf6ca14891d38c82916591971dbd82d3b6f86 Mon Sep 17 00:00:00 2001 From: Nastya Date: Wed, 23 Jul 2025 12:05:33 +0300 Subject: [PATCH 08/18] fix floating support chat --- package.json | 2 +- src/App.tsx | 11 ++- src/ui_kit/FloatingSupportChat/Chat.tsx | 23 +++-- src/ui_kit/FloatingSupportChat/index.tsx | 12 ++- src/utils/handleComponentError.ts | 109 +++++++++++++++++++++++ src/utils/hooks/useAutoPay.ts | 7 -- yarn.lock | 6 +- 7 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 src/utils/handleComponentError.ts 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" From e9b7f1b0a97d5cd8a7a7f0a5bf79c134331a92ce Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 24 Jul 2025 02:41:58 +0300 Subject: [PATCH 09/18] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D1=87=D1=91=D1=82=D0=B0=20=D0=BA=D0=BE=D0=BB-?= =?UTF-8?q?=D0=B2=D0=B0=20=D0=BD=D0=B5=D0=BF=D1=80=D0=BE=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- .../FloatingSupportChat.tsx | 48 +++++++++++++++++-- src/utils/handleComponentError.ts | 26 +++++----- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 059ccfd3..f1e5ae16 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -195,7 +195,7 @@ export default function App() { onError={(error, info) => handleComponentError(error, info, () => useTicketStore.getState().tickets)} > {amoAccount && } - + {!isTest && } {location.state?.backgroundLocation && ( diff --git a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx index 2c7f7419..e133464f 100644 --- a/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx +++ b/src/ui_kit/FloatingSupportChat/FloatingSupportChat.tsx @@ -1,5 +1,5 @@ import type { ReactNode, Ref } from "react"; -import { forwardRef, useEffect, useState } from "react"; +import { forwardRef, useEffect, useState, useMemo } from "react"; import { Badge, Box, @@ -70,6 +70,48 @@ export default function FloatingSupportChat({ (state) => state[user ? "authData" : "unauthData"], ); + const ticket = useTicketStore( + (state) => state[user ? "authData" : "unauthData"], + ); + + // Функция для подсчёта непрочитанных сообщений согласно новой логике + const unreadCount = useMemo(() => { + if (messages.length === 0) return 0; + + const currentUserId = user || ticket?.sessionData?.sessionId; + if (!currentUserId) return 0; + + // Если последнее сообщение моё - не показываем количество + const lastMessage = messages[messages.length - 1]; + if (lastMessage.user_id === currentUserId) { + return 0; + } + + // Если последнее сообщение не моё и оно не прочитано - считаем его как +1 + if (lastMessage.shown?.me !== 1) { + let count = 1; + + // Считаем все последующие сообщения до тех пор пока не воткнёмся в моё сообщение + for (let i = messages.length - 2; i >= 0; i--) { + const message = messages[i]; + + // Если встретили моё сообщение - останавливаемся + if (message.user_id === currentUserId) { + break; + } + + // Если сообщение не прочитано - добавляем к счётчику + if (message.shown?.me !== 1) { + count++; + } + } + + return count; + } + + return 0; + }, [messages, user, ticket?.sessionData?.sessionId]); + useEffect(() => { const onResize = () => { if (document.fullscreenElement) { @@ -158,9 +200,7 @@ export default function FloatingSupportChat({ /> )} shown?.me !== 1).length || 0 - } + badgeContent={unreadCount} sx={{ "& .MuiBadge-badge": { display: isChatOpened ? "none" : "flex", diff --git a/src/utils/handleComponentError.ts b/src/utils/handleComponentError.ts index fd08857a..2687d868 100644 --- a/src/utils/handleComponentError.ts +++ b/src/utils/handleComponentError.ts @@ -5,10 +5,10 @@ let errorsQueue: ComponentError[] = []; let timeoutId: ReturnType; interface ComponentError { - timestamp: number; - message: string; - callStack: string | undefined; - componentStack: string | null | undefined; + timestamp: number; + message: string; + callStack: string | undefined; + componentStack: string | null | undefined; } function isErrorReportingAllowed(error?: Error): boolean { @@ -31,22 +31,22 @@ export function handleComponentError(error: Error, info: ErrorInfo, getTickets: 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, + 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(() => { + errorsQueue.push(error); + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { sendErrorsToServer(getTickets); - }, 1000); + }, 1000); } // Отправляет накопленные ошибки в тикеты, ищет существующий тикет с system: true или создает новый From 083ff081e84c6c969a962978997a0a795ce2a720 Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 28 Jul 2025 12:55:43 +0300 Subject: [PATCH 10/18] =?UTF-8?q?AI=20=D0=BF=D0=B5=D1=80=D1=81=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D1=80=D0=B0=D1=81=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=D0=B7=D1=8B=D0=B2=D0=B5=D1=82=20=D1=87=D1=82=D0=BE=20=D0=BD?= =?UTF-8?q?=D1=83=D0=B6=D0=BD=D0=BE=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B8=D1=81=D0=B0=D0=B1=D0=BB=D0=B8=D1=82=D1=8C=20=D0=BA?= =?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateButtonWithTooltip.tsx | 112 ++++++++++++++++++ .../GenderAndAgeSelector.tsx | 24 ++-- 2 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 src/pages/PersonalizationAI/CreateButtonWithTooltip.tsx diff --git a/src/pages/PersonalizationAI/CreateButtonWithTooltip.tsx b/src/pages/PersonalizationAI/CreateButtonWithTooltip.tsx new file mode 100644 index 00000000..c92c20d1 --- /dev/null +++ b/src/pages/PersonalizationAI/CreateButtonWithTooltip.tsx @@ -0,0 +1,112 @@ +import { Button, ClickAwayListener, Tooltip, useTheme } from "@mui/material"; +import { useState } from "react"; + +interface CreateButtonWithTooltipProps { + gender: string; + age: string; + ageError: boolean; + onClick: () => void; +} + +export default function CreateButtonWithTooltip({ + gender, + age, + ageError, + onClick +}: CreateButtonWithTooltipProps) { + const theme = useTheme(); + const [open, setOpen] = useState(false); + + const handleTooltipClose = () => { + setOpen(false); + }; + + const handleTooltipOpen = () => { + setOpen(true); + }; + + // Определяем причины неактивности кнопки + const getDisabledReasons = () => { + const reasons: string[] = []; + + if (!gender) { + reasons.push("выберите пол"); + } + + if (!age) { + reasons.push("заполните поле возраста"); + } + + if (ageError) { + reasons.push("исправьте ошибку в поле возраста"); + } + + return reasons; + }; + + const disabledReasons = getDisabledReasons(); + const isDisabled = !gender || !age || ageError; + const tooltipText = isDisabled + ? disabledReasons.length === 2 + ? disabledReasons.join(' и ') + : disabledReasons.join('\n') + : ''; + + return ( + +
+ {isDisabled && ( +
+ )} + + + +
+ + ); +} \ No newline at end of file diff --git a/src/pages/PersonalizationAI/GenderAndAgeSelector.tsx b/src/pages/PersonalizationAI/GenderAndAgeSelector.tsx index 720022f3..765afb3d 100644 --- a/src/pages/PersonalizationAI/GenderAndAgeSelector.tsx +++ b/src/pages/PersonalizationAI/GenderAndAgeSelector.tsx @@ -1,7 +1,8 @@ -import { Box, FormControl, FormLabel, Checkbox, FormControlLabel, useTheme, Button, useMediaQuery } from "@mui/material"; +import { Box, FormControl, FormLabel, Checkbox, FormControlLabel, useTheme, useMediaQuery } from "@mui/material"; import CheckboxIcon from "@icons/Checkbox"; import AgeInputWithSelect from "./AgeInputWithSelect"; import { useState, useEffect } from "react"; +import CreateButtonWithTooltip from "./CreateButtonWithTooltip"; interface GenderAndAgeSelectorProps { gender: string; @@ -190,23 +191,12 @@ export default function GenderAndAgeSelector({ - + /> ); } \ No newline at end of file From 2882bb7e740cab730581a8c7a7e22f0b30ad7a58 Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 28 Jul 2025 16:36:16 +0300 Subject: [PATCH 11/18] =?UTF-8?q?=D1=81=D0=B5=D0=B3=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D1=8F=D1=88=D0=BD=D0=B8=D0=B9=20=D0=B4=D0=B5=D0=BD=D1=8C=20?= =?UTF-8?q?=D1=83=D1=87=D0=B8=D1=82=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Analytics/General.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pages/Analytics/General.tsx b/src/pages/Analytics/General.tsx index be9b7b88..33f6d581 100644 --- a/src/pages/Analytics/General.tsx +++ b/src/pages/Analytics/General.tsx @@ -131,11 +131,21 @@ export const General: FC = ({ data, day }) => { const generalResponse = Object.entries(data).reduce( (total, [fatherKey, values]) => { const value = Object.keys(values).reduce((totalValue, key) => { - if (Number(key) - currentDate < 0) { + const keyTimestamp = Number(key); + const todayStart = moment().startOf('day').unix(); + const todayEnd = moment().endOf('day').unix(); + + // Включаем данные за сегодня и прошлые дни, исключаем будущие дни + if (keyTimestamp >= todayStart && keyTimestamp <= todayEnd) { + // Сегодняшний день - включаем return { ...totalValue, [key]: values[key] }; + } else if (keyTimestamp < todayStart) { + // Прошлые дни - включаем + return { ...totalValue, [key]: values[key] }; + } else { + // Будущие дни - исключаем + return totalValue; } - - return totalValue; }, {}); return { ...total, [fatherKey]: value }; From c785b9eb9551ae6cc40627f77a100f80bc4ec30f Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 28 Jul 2025 16:45:40 +0300 Subject: [PATCH 12/18] =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B8=D0=BF=D0=B0=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=BC=D0=BE=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/parse-error.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/utils/parse-error.ts b/src/utils/parse-error.ts index 9d689731..e644b623 100644 --- a/src/utils/parse-error.ts +++ b/src/utils/parse-error.ts @@ -29,7 +29,7 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => { if (error.message === "Failed to fetch") return ["Ошибка сети"]; //ДЛЯ ОПЛАТЫ ТАРИФА - if(error.response.status === 402) { + if(error.response?.status === 402) { console.error(error.response?.data.message) return error.response?.data.message } @@ -40,7 +40,8 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => { const status = error.response.status; if(status === 409 || status === 401 || status === 404) { - const serverErrorMessage = error.response.data.message + const responseData = error.response.data as any; + const serverErrorMessage = responseData?.message || responseData?.error; console.log(serverErrorMessage) const translatedMessage = translateMessage[serverErrorMessage?.toLowerCase() || ""] return [translatedMessage || "", serverError.statusCode]; From 3df3eb243217140e7918f62166d9e949574b30e8 Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 28 Jul 2025 19:12:54 +0300 Subject: [PATCH 13/18] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=BE=D0=B2=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D0=BE=D0=B2=20(=D1=87=D1=82=D0=BE=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D1=88=D0=BB=D0=BE=20=D1=82=D0=BE=20=D0=B8=20=D0=B2=D1=8B=D0=B2?= =?UTF-8?q?=D0=B5=D0=BB=D0=BE=D1=81=D1=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Analytics/General.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pages/Analytics/General.tsx b/src/pages/Analytics/General.tsx index 33f6d581..78ce3a9f 100644 --- a/src/pages/Analytics/General.tsx +++ b/src/pages/Analytics/General.tsx @@ -42,16 +42,9 @@ const GeneralItem = ({ ([nextValue], [currentValue]) => Number(nextValue) - Number(currentValue), ); const days = data.map(([value]) => value); - const { time } = data.reduce( - (total, [_, value]) => ({ - defaultValue: value > 0 ? value : total.defaultValue, - time: [...total.time, value > 0 ? value : total.defaultValue], - }), - { defaultValue: 0, time: [] as number[] }, - ); const numberValue = calculateTime - ? time.reduce((total, value) => total + value, 0) / days.length + ? Object.values(general).reduce((total, value) => total + value, 0) / Object.values(general).length : conversionValue ? conversionValue : Object.values(general).reduce((total, item) => total + item, 0); @@ -82,17 +75,17 @@ const GeneralItem = ({ { const timestamp = Number(value); if (isNaN(timestamp)) return 'Invalid Date'; - return moment.unix(timestamp).format(statiscticsResult ? "DD/MM/YYYY" : "DD/MM/YYYY HH") + (statiscticsResult ? "" : "ч"); + return moment.unix(timestamp).format("DD/MM/YYYY"); }, }, ]} series={[ { - data: Object.values(statiscticsResult ? time : general), + data: Object.values(general), valueFormatter: (value) => calculateTime ? getCalculatedTime(value) From 84f1011de05a69ac584d4d102b819b7f955752ce Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 7 Aug 2025 01:24:00 +0300 Subject: [PATCH 14/18] =?UTF-8?q?=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 2 +- src/App.tsx | 8 +-- .../Amo/Questions/AmoQuestions.tsx | 4 -- .../IntegrationsModal/Amo/SwitchPages.tsx | 8 --- src/pages/PersonalizationAI/AuditoryList.tsx | 2 - .../PersonalizationAI/PersonalizationAI.tsx | 13 ++--- .../Branching/QuestionSwitchWindowTool.tsx | 26 ---------- .../DraggableList/QuestionPageCardTitle.tsx | 1 - src/pages/Questions/DraggableList/index.tsx | 3 -- .../EmojiAnswerItem/VariantAdornment.tsx | 1 - .../SliderOptions/SliderOptions.tsx | 5 -- src/pages/Tariffs/Tariffs.tsx | 51 +++---------------- .../tariffsUtils/createTariffElements.tsx | 4 -- src/pages/createQuize/AvailablePrivilege.tsx | 2 - src/stores/ticket.ts | 1 - src/stores/user.ts | 1 - src/ui_kit/FloatingSupportChat/utils.ts | 4 -- .../Modal/CropModal/NavigationPanel.tsx | 3 -- src/ui_kit/Modal/CropModal/WorkSpace.tsx | 2 - src/ui_kit/crutchFunctionAI.tsx | 1 - src/utils/hooks/useAutoPay.ts | 6 --- src/utils/hooks/usePipeSubscriber.ts | 2 - src/utils/hooks/useUserAccountFetcher.ts | 13 +---- src/utils/parse-error.ts | 1 - 24 files changed, 13 insertions(+), 151 deletions(-) diff --git a/public/index.html b/public/index.html index 0df06af1..005d6ef6 100755 --- a/public/index.html +++ b/public/index.html @@ -165,7 +165,7 @@ console.log(params.get("debug")) if (params.get("debug")) { console.log( - "mhgfhdhfjhffhfhjfghjgf" + "params.get(debug) is true" ) let scriptTag = document.createElement('script'); scriptTag.setAttribute('src', "https://markknol.github.io/console-log-viewer/console-log-viewer.js"); diff --git a/src/App.tsx b/src/App.tsx index f1e5ae16..78bf452b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -112,16 +112,10 @@ export default function App() { url: `${process.env.REACT_APP_DOMAIN}/customer/v1.0.1/account`, userId, onNewUserAccount: (account) => { - console.log("App: Setting customerAccount:", account); - console.log("App: customerAccount structure:", { - userId: account?.userId, - wallet: account?.wallet, - cart: account?.cart - }); setCustomerAccount(account); }, onError: (error) => { - console.log("App: Error in customerAccount fetcher:", error); + console.error("App: Error in customerAccount fetcher:", error); const errorMessage = getMessageFromFetchError(error); if (errorMessage) { enqueueSnackbar(errorMessage); diff --git a/src/pages/IntegrationsPage/IntegrationsModal/Amo/Questions/AmoQuestions.tsx b/src/pages/IntegrationsPage/IntegrationsModal/Amo/Questions/AmoQuestions.tsx index e73e45ac..084fc813 100644 --- a/src/pages/IntegrationsPage/IntegrationsModal/Amo/Questions/AmoQuestions.tsx +++ b/src/pages/IntegrationsPage/IntegrationsModal/Amo/Questions/AmoQuestions.tsx @@ -150,10 +150,6 @@ export const AmoQuestions: FC = ({ }, [activeScope]) const [blockButton, setBlockButton] = useState(false) - console.log("selectedQuestions") - console.log(selectedQuestions) - console.log("SCFworld") - console.log(SCFworld) return ( <> (accountInfo ? "accountInfo" : "amoLogin") const [openDelete, setOpenDelete] = useState(null); - console.log("--") - console.log(selectedQuestions) const startDeleteTagQuestion = (itemForDelete) => { setOpenDelete(itemForDelete) @@ -135,9 +133,6 @@ export const SwitchPages = ({ if (type === "tag") { setSelectedTags((prevState) => { - console.log(prevState) - console.log(scope) - console.log(id) return({ ...prevState, [scope]: [...prevState[scope as TagKeys], id], @@ -147,9 +142,6 @@ export const SwitchPages = ({ if (type === "question") { const q = questions.find(e => e.backendId === Number(id)) setSelectedQuestions((prevState) => { - console.log(prevState) - console.log(scope) - console.log(id) return ({ ...prevState, [scope]: [...prevState[scope as QuestionKeys], { diff --git a/src/pages/PersonalizationAI/AuditoryList.tsx b/src/pages/PersonalizationAI/AuditoryList.tsx index a84c7e2a..1050535a 100644 --- a/src/pages/PersonalizationAI/AuditoryList.tsx +++ b/src/pages/PersonalizationAI/AuditoryList.tsx @@ -11,8 +11,6 @@ export const AuditoryList = ({utmParams, auditory, onDelete}:{utmParams:string,a const { isTestServer } = useDomainDefine(); const [linksOpen, setLinksOpen] = useState(true); - console.log("auditory-___---_auditory__---__-__auditory_------__---__-__---_------__---__-__---_------__---__-____--__") - console.log(auditory) return ( <> diff --git a/src/pages/PersonalizationAI/PersonalizationAI.tsx b/src/pages/PersonalizationAI/PersonalizationAI.tsx index 661b4879..a78e8e49 100644 --- a/src/pages/PersonalizationAI/PersonalizationAI.tsx +++ b/src/pages/PersonalizationAI/PersonalizationAI.tsx @@ -93,7 +93,7 @@ export default function PersonalizationAI() { useToken: true, withCredentials: false, }).catch(error => { - console.log(error) + console.error(error) enqueueSnackbar("Ошибка при обновлении данных пользователя", { variant: "error" }); return null; }), @@ -103,7 +103,7 @@ export default function PersonalizationAI() { useToken: true, withCredentials: false, }).catch(error => { - console.log(error) + console.error(error) enqueueSnackbar("Ошибка при обновлении данных клиента", { variant: "error" }); return null; }) @@ -116,7 +116,7 @@ export default function PersonalizationAI() { setCustomerAccount(customerAccountResult); } } catch (error) { - console.log(error) + console.error(error) enqueueSnackbar("Ошибка при обновлении данных", { variant: "error" }); } } @@ -129,8 +129,6 @@ export default function PersonalizationAI() { (async () => { if (quiz?.backendId) { const [result, error] = await auditoryGet({ quizId: quiz.backendId }); - console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__") - console.log(result) if (result) { setAuditory(result); } @@ -210,8 +208,6 @@ export default function PersonalizationAI() { setUtmParams(paramString ? `&${paramString}` : ""); }; - console.log("______----giga_chat-----__--_---_--_----__--__-__--_--__--__--_---_______-quiz") - console.log(quiz?.giga_chat) const startCreate = async () => { if (quiz?.giga_chat) { createNewLink(); @@ -272,12 +268,9 @@ export default function PersonalizationAI() { // Обновляем данные квиза после успешной оплаты - console.log("Обновляем данные квиза после оплаты"); const [quizes, quizesError] = await quizApi.getList(); - console.log("Получены данные квизов:", quizes); if (!quizesError) { setQuizes(quizes); - console.log("Данные квизов обновлены в сторе"); } else { console.error("Ошибка при получении данных квизов:", quizesError); } diff --git a/src/pages/Questions/Branching/QuestionSwitchWindowTool.tsx b/src/pages/Questions/Branching/QuestionSwitchWindowTool.tsx index 345f8de2..4de3c711 100644 --- a/src/pages/Questions/Branching/QuestionSwitchWindowTool.tsx +++ b/src/pages/Questions/Branching/QuestionSwitchWindowTool.tsx @@ -8,7 +8,6 @@ import { uploadQuestionImage } from "@/stores/questions/actions"; import { useCurrentQuiz } from "@/stores/quizes/hooks"; import { updateQuestion } from "@root/questions/actions"; let params = (new URL(document.location)).searchParams; -console.log(params.get("data")); const BranchingMap = lazy(() => import("./BranchingMap").then((module) => ({ default: module.BranchingMap })), ); @@ -55,31 +54,6 @@ export const QuestionSwitchWindowTool = ({ }} /> - // { - // console.log(e) - // Array.from(e.target.files).forEach((element, i) => { - // setTimeout(() => { - // console.log(i) - // console.log(Number(element.name.replace(/[^0-9,\s]/g, ""))) - // const q = questions.find((q) => q.page + 1 === Number(element.name.replace(/[^0-9,\s]/g, ""))) - // if (q !== undefined) { - // console.log(q) - // console.log("-----------------") - // uploadQuestionImage( - // q.id, - // quiz.qid, - // e.target.files[i], - // (question, url) => { - // question.content.back = url; - // question.content.originalBack = url; - // }, - // ); - // } - // }, 1000); - // }); - // }} - // /> } {openBranchingPage ? ( diff --git a/src/pages/Questions/DraggableList/QuestionPageCardTitle.tsx b/src/pages/Questions/DraggableList/QuestionPageCardTitle.tsx index 6bcfe86b..b424dd35 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCardTitle.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCardTitle.tsx @@ -126,7 +126,6 @@ const QuestionPageCardTitle = memo(function ({ value={title} placeholder={"Заголовок вопроса"} onChange={({ target }) => { - console.log(target.value.length) if (target.value.length > maxLengthTextField) { enqueueSnackbar("Превышена длина вводимого текста") } else { diff --git a/src/pages/Questions/DraggableList/index.tsx b/src/pages/Questions/DraggableList/index.tsx index 4b9cf1f4..03346b3f 100644 --- a/src/pages/Questions/DraggableList/index.tsx +++ b/src/pages/Questions/DraggableList/index.tsx @@ -31,9 +31,6 @@ export const DraggableList = ({ createUntypedQuestion(Number(quiz.backendId)); } }, [quiz, filteredQuestions]); - console.log(quiz) - console.log(questions) - // if () {}uploadQuestionImage return ( diff --git a/src/pages/Questions/Emoji/EmojiAnswerItem/VariantAdornment.tsx b/src/pages/Questions/Emoji/EmojiAnswerItem/VariantAdornment.tsx index 15e3a3f9..6dd6c494 100644 --- a/src/pages/Questions/Emoji/EmojiAnswerItem/VariantAdornment.tsx +++ b/src/pages/Questions/Emoji/EmojiAnswerItem/VariantAdornment.tsx @@ -15,7 +15,6 @@ export default function VariantAdornment({ }) { const theme = useTheme(); - console.log("VariantAdornment extendedText", extendedText) return ( diff --git a/src/pages/Questions/QuestionOptions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/QuestionOptions/SliderOptions/SliderOptions.tsx index 3535cfbb..92ba114a 100644 --- a/src/pages/Questions/QuestionOptions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/QuestionOptions/SliderOptions/SliderOptions.tsx @@ -44,11 +44,6 @@ export default function SliderOptions({ question, openBranchingPage, setOpenBran }); }, 5000); const updateStepsDebounced = useDebouncedCallback((value: string) => { - console.log("value") - console.log(value) - console.log(value.toString()) - console.log("ReplaceToNotStartZero(Number(value)) _____________________________________") - console.log(ReplaceToNotStartZero(Number(value))) updateQuestion(question.id, (question) => { question.content.step = ReplaceToNotStartZero(Number(value)); }); diff --git a/src/pages/Tariffs/Tariffs.tsx b/src/pages/Tariffs/Tariffs.tsx index 318c8f46..7ac558e4 100644 --- a/src/pages/Tariffs/Tariffs.tsx +++ b/src/pages/Tariffs/Tariffs.tsx @@ -47,12 +47,12 @@ function TariffPage() { const user = useUserStore((state) => state.customerAccount); const userWithWallet = useUserStore((state) => state.customerAccount); //c wallet const userAccount = useUserStore((state) => state.userAccount); - console.log("________________userWithWallet_____________USERRRRRRR") - console.log(userWithWallet) - console.log("________________userAccount_____________") - console.log(userAccount) - console.log("________________customerAccount_____________") - console.log(user) + // console.info("________________userWithWallet_____________USERRRRRRR") + // console.info(userWithWallet) + // console.info("________________userAccount_____________") + // console.info(userAccount) + // console.info("________________customerAccount_____________") + // console.info(user) const { data: discounts, error: discountsError, isLoading: discountsLoading } = useDiscounts(userId); const [isRequestCreate, setIsRequestCreate] = useState(false); const [openModal, setOpenModal] = useState<{ id?: string; price?: number }>({}); @@ -65,9 +65,6 @@ function TariffPage() { const { data: tariffs, error: tariffsError, isLoading: tariffsLoading } = useTariffs(); -console.log("________34563875693785692576_____ TARIFFS") -console.log(tariffs) - useEffect(() => { if (userWithWallet && user) { let cs = currencyFormatter.format(Number(user.wallet.cash) / 100); @@ -84,24 +81,6 @@ console.log(tariffs) } }, []) - // Добавляем логирование для диагностики - console.log("Tariffs loading state:", { - user: !!user, - userWithWallet: !!userWithWallet, - userId: userId, - tariffs: !!tariffs, - discounts: !!discounts, - tariffsLoading, - tariffsError, - discountsLoading, - discountsError, - userDetails: user ? { - id: user._id, - wallet: user.wallet, - cart: user.cart?.length - } : null - }); - // Проверяем, что все данные загружены и нет ошибок const isDataLoading = tariffsLoading || (userId && discountsLoading); const hasErrors = tariffsError || discountsError; @@ -111,26 +90,14 @@ console.log(tariffs) const hasAllData = user && tariffs && (userId ? discounts : true); if (isDataLoading || isCustomerAccountLoading) { - console.log("Showing loading page because:", { - dataLoading: isDataLoading, - customerAccountLoading: isCustomerAccountLoading, - userId, - user: !!user - }); return ; } if (hasErrors) { - console.log("Showing loading page because of errors:", { tariffsError, discountsError }); return ; } if (!hasAllData) { - console.log("Showing loading page because:", { - noUser: !user, - noTariffs: !tariffs, - noDiscounts: userId && !discounts - }); return ; } @@ -154,14 +121,9 @@ console.log(tariffs) //Если нам хватает денежек - покупаем тариф const [data, payError] = await cartApi.pay(); - console.log("payError || !data") - console.log("payError", payError) - console.log("data", data) if (payError || !data) { - console.log("прошли 1 проверку_______") //если денег не хватило if (payError?.includes("insufficient funds") || payError?.includes("Payment Required")) { - console.log("прошли 2 проверку_______") let cashDif = Number(payError.split(":")[1]); if (!userId) { @@ -176,7 +138,6 @@ console.log(tariffs) wayback: "list", token }); - console.log(l) window.location.href = l; return; } diff --git a/src/pages/Tariffs/tariffsUtils/createTariffElements.tsx b/src/pages/Tariffs/tariffsUtils/createTariffElements.tsx index 7999e51f..1511e49c 100644 --- a/src/pages/Tariffs/tariffsUtils/createTariffElements.tsx +++ b/src/pages/Tariffs/tariffsUtils/createTariffElements.tsx @@ -17,10 +17,6 @@ export const createTariffElements = ( cc?: boolean, icon?: ReactNode ) => { - console.log("start work createTariffElements") - console.log("filteredTariffs ", filteredTariffs) - console.log("user ", user) - console.log("user.isUserNko, ", user.isUserNko) const tariffElements = filteredTariffs .filter((tariff) => tariff.privileges.length > 0) .map((tariff, index) => { diff --git a/src/pages/createQuize/AvailablePrivilege.tsx b/src/pages/createQuize/AvailablePrivilege.tsx index 7b7cd5b0..dbca4762 100644 --- a/src/pages/createQuize/AvailablePrivilege.tsx +++ b/src/pages/createQuize/AvailablePrivilege.tsx @@ -53,8 +53,6 @@ export default function AvailablePrivilege() { } const quizUnlimDays = getCramps(quizUnlimTime, userPrivileges?.quizUnlimTime?.created_at || ""); const squizBadgeDays = getCramps(squizHideBadge, userPrivileges?.squizHideBadge?.created_at || ""); - console.log(userPrivileges) - console.log(quizUnlimTime) return ( // if (Array.isArray(sortedMessages)) ticket.lastMessageId = sortedMessages?.at(-1)?.id || ""; if (Array.isArray(sortedMessages)) ticket.lastMessageId = sortedMessages?.[sortedMessages.length - 1]?.id || ""; - console.log("ticketStudy, ", sortedMessages) }); export const incrementUnauthMessage = () => diff --git a/src/stores/user.ts b/src/stores/user.ts index 99354f16..4de52d83 100644 --- a/src/stores/user.ts +++ b/src/stores/user.ts @@ -72,6 +72,5 @@ export const setUserAccount = (userAccount: OriginalUserAccount) => useUserStore.setState({ userAccount }); export const setCustomerAccount = (customerAccount: UserAccount) => { - console.log("setCustomerAccount called with:", customerAccount); useUserStore.setState({ customerAccount }); }; diff --git a/src/ui_kit/FloatingSupportChat/utils.ts b/src/ui_kit/FloatingSupportChat/utils.ts index 87d80ca6..430030ac 100644 --- a/src/ui_kit/FloatingSupportChat/utils.ts +++ b/src/ui_kit/FloatingSupportChat/utils.ts @@ -11,16 +11,12 @@ interface SelectSendingMethod { } export const selectSendingMethod = async ({ messageField, isSnackbar = true, systemError = false }: SelectSendingMethod) => { - console.log("click") const user = useUserStore.getState().user?._id; const ticket = useTicketStore.getState()[user ? "authData" : "unauthData"]; - console.log(ticket) - console.log("click 2") let successful = false; if (!(window.location.hostname == 'localhost' && systemError)) { //предупреждать о системных ошибках вне локалхост if (!ticket.sessionData?.ticketId) { - console.log("autorisated 2") const [data, createError] = await createTicket({ message: messageField, useToken: Boolean(user), diff --git a/src/ui_kit/Modal/CropModal/NavigationPanel.tsx b/src/ui_kit/Modal/CropModal/NavigationPanel.tsx index 580a8a0a..95872caa 100644 --- a/src/ui_kit/Modal/CropModal/NavigationPanel.tsx +++ b/src/ui_kit/Modal/CropModal/NavigationPanel.tsx @@ -29,9 +29,6 @@ export const NavigationPanel: FC = ({ const isMobile = useMediaQuery(theme.breakpoints.down(786)); const lastStep = currentStep + 1 === totalSteps; - console.log("nextStepName") - console.log(nextStepName) - const handlePrevStep = () => { if (currentStep === 0) return; setCurrentStep(currentStep - 1); diff --git a/src/ui_kit/Modal/CropModal/WorkSpace.tsx b/src/ui_kit/Modal/CropModal/WorkSpace.tsx index 057d5975..e9f1a483 100644 --- a/src/ui_kit/Modal/CropModal/WorkSpace.tsx +++ b/src/ui_kit/Modal/CropModal/WorkSpace.tsx @@ -46,8 +46,6 @@ export default function WorkSpace({ modalModels[currentStepName] ), [currentStepName]); - // console.log(" промежуточный рендер которому должно быть похуй") - return ( <> { const [open, setOpen] = useState(false); const quiz = useCurrentQuiz(); const account = useUserStore() - console.log(account.userId) if (account.userId === "6755b1ddd5802e9f13663f56") { return ( <> diff --git a/src/utils/hooks/useAutoPay.ts b/src/utils/hooks/useAutoPay.ts index c1a79df3..a78d88d8 100644 --- a/src/utils/hooks/useAutoPay.ts +++ b/src/utils/hooks/useAutoPay.ts @@ -56,10 +56,6 @@ export const useAfterPay = () => { //Покупка ИИ тарифа из настройки квиза ИИ вкладки if (URLaction === "buy" && URLadditionalinformation) { const quizId = Number(URLadditionalinformation); - console.log("useAutoPay: Processing AI tariff purchase", { - quizId, - wayback: searchParams.get("wayback") - }); if (quizId) { setEditQuizId(Number(quizId)); //Выбираем квиз @@ -68,7 +64,6 @@ export const useAfterPay = () => { // Проверяем wayback параметр для определения куда переходить const wayback = searchParams.get("wayback"); if (wayback === "edit") { - console.log("useAutoPay: Navigating to /edit first, then /personalization-ai"); // Сначала переходим на /edit, затем на /personalization-ai navigate("/edit"); // Используем setTimeout чтобы дать время для загрузки /edit @@ -76,7 +71,6 @@ export const useAfterPay = () => { navigate("/personalization-ai"); }, 100); } else { - console.log("useAutoPay: Direct navigation to /personalization-ai"); // Прямой переход на /personalization-ai navigate("/personalization-ai"); } diff --git a/src/utils/hooks/usePipeSubscriber.ts b/src/utils/hooks/usePipeSubscriber.ts index 8a9c2303..b6b58d63 100644 --- a/src/utils/hooks/usePipeSubscriber.ts +++ b/src/utils/hooks/usePipeSubscriber.ts @@ -43,8 +43,6 @@ export const usePipeSubscriber = () => { `/customer/v1.0.1/account/pipe?Authorization=${token}`, onNewData: (data) => { let message = data[0] as PipeMessage - console.log("truba") - console.log(message) updateSSEValue(message) //Пропускаем пингование diff --git a/src/utils/hooks/useUserAccountFetcher.ts b/src/utils/hooks/useUserAccountFetcher.ts index 5785cdc4..948c5aa4 100644 --- a/src/utils/hooks/useUserAccountFetcher.ts +++ b/src/utils/hooks/useUserAccountFetcher.ts @@ -26,7 +26,6 @@ export const useUserAccountFetcher = ({ useEffect(() => { if (!userId) return; - console.log("useUserAccountFetcher: Starting request for userId:", userId, "url:", url); const controller = new AbortController(); makeRequest({ url, @@ -38,15 +37,12 @@ export const useUserAccountFetcher = ({ }) .then((result) => { devlog("User account", result); - console.log("useUserAccountFetcher: Success for userId:", userId, "result:", result); if (result) onNewUserAccountRef.current(result); }) .catch((error) => { devlog("Error fetching user account", error); - console.log("useUserAccountFetcher: Error for userId:", userId, "error:", error); if (error.response?.status === 409) return; if (isAxiosError(error) && error.response?.status === 404) { - console.log("useUserAccountFetcher: Creating user account for userId:", userId); // Формируем правильный URL для создания аккаунта let createUrl = url; @@ -58,28 +54,23 @@ export const useUserAccountFetcher = ({ createUrl = url.replace("get", "create"); } - console.log("useUserAccountFetcher: Create URL:", createUrl); createUserAccount(controller.signal, createUrl) .then((result) => { devlog("Created user account", result); - console.log("useUserAccountFetcher: Account created successfully:", result); // Проверяем структуру ответа и записываем в стор if (result) { // Если результат содержит created_account, используем его if (result.created_account) { - console.log("useUserAccountFetcher: Using result.created_account"); onNewUserAccountRef.current(result.created_account as T); } // Если результат сам является аккаунтом (для customerAccount) else if (result.userId && result.wallet) { - console.log("useUserAccountFetcher: Using result directly as customerAccount"); onNewUserAccountRef.current(result as T); } // Если ничего не подходит, логируем для диагностики else { - console.log("useUserAccountFetcher: Unknown result structure:", result); onNewUserAccountRef.current(result as T); } } @@ -87,11 +78,11 @@ export const useUserAccountFetcher = ({ .catch((error) => { if (error.response?.status === 409) return; devlog("Error creating user account", error); - console.log("useUserAccountFetcher: Error creating account:", error); + console.error("useUserAccountFetcher: Error creating account:", error); onErrorRef.current?.(error); }); } else { - console.log(error) + console.error(error) onErrorRef.current?.(error); } }); diff --git a/src/utils/parse-error.ts b/src/utils/parse-error.ts index e644b623..fd2bb26a 100644 --- a/src/utils/parse-error.ts +++ b/src/utils/parse-error.ts @@ -42,7 +42,6 @@ export const parseAxiosError = (nativeError: unknown): [string, number?] => { if(status === 409 || status === 401 || status === 404) { const responseData = error.response.data as any; const serverErrorMessage = responseData?.message || responseData?.error; - console.log(serverErrorMessage) const translatedMessage = translateMessage[serverErrorMessage?.toLowerCase() || ""] return [translatedMessage || "", serverError.statusCode]; } From e2ba7218e8a452a5eeea4b6cd3b43521cfd75707 Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 7 Aug 2025 01:28:49 +0300 Subject: [PATCH 15/18] update deploy --- .gitea/workflows/deployStaging.yml | 37 ++++++++++++++----------- Containerfile | 13 --------- Dockerfile | 13 +++++++++ deployments/staging/docker-compose.yaml | 2 +- package.json | 1 + 5 files changed, 36 insertions(+), 30 deletions(-) delete mode 100644 Containerfile diff --git a/.gitea/workflows/deployStaging.yml b/.gitea/workflows/deployStaging.yml index 88de5f30..97c15694 100644 --- a/.gitea/workflows/deployStaging.yml +++ b/.gitea/workflows/deployStaging.yml @@ -2,25 +2,30 @@ name: Deploy run-name: ${{ gitea.actor }} build image and push to container registry on: - push: - branches: - - 'staging' + registry_package: + types: [published] jobs: - CreateImage: - runs-on: [skeris] - uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p - with: - runner: skeris - secrets: - REGISTRY_USER: ${{ secrets.REGISTRY_USER }} - REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + # CreateImage: + # runs-on: [skeris] + # uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p + # with: + # runner: skeris + # secrets: + # REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + # REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} DeployService: + if: contains(github.event.package.name, 'staging') runs-on: [frontstaging] - needs: CreateImage - uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.4-p7 - with: - runner: frontstaging - actionid: ${{ gitea.run_id }} + container: + image: gitea.pena:3000/penadevops/container-images/node-compose:main + env: + GITHUB_RUN_NUMBER: "${{ inputs.actionid }}" + volumes: + - /run/user/1000/docker/docker.sock:/run/user/1000/docker/docker.sock + steps: + - name: Check out repository code + uses: http://gitea.pena:3000/PenaDevops/actions.git/checkout@v1 + - run: compose -f deployments/staging/docker-compose.yaml up -d diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 95c38239..00000000 --- a/Containerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM gitea.pena/penadevops/container-images/node:main as build - -WORKDIR /usr/app -COPY . . - - -RUN npm install --force && yarn cache clean -RUN psstat.sh "npm run build" - -FROM gitea.pena/penadevops/container-images/nginx:main as result -WORKDIR /usr/share/nginx/html -COPY --from=build /usr/app/build/ /usr/share/nginx/html -COPY hub.conf /etc/nginx/conf.d/default.conf diff --git a/Dockerfile b/Dockerfile index e69de29b..95c38239 100644 --- a/Dockerfile +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM gitea.pena/penadevops/container-images/node:main as build + +WORKDIR /usr/app +COPY . . + + +RUN npm install --force && yarn cache clean +RUN psstat.sh "npm run build" + +FROM gitea.pena/penadevops/container-images/nginx:main as result +WORKDIR /usr/share/nginx/html +COPY --from=build /usr/app/build/ /usr/share/nginx/html +COPY hub.conf /etc/nginx/conf.d/default.conf diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 77b80c51..1549c906 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -2,6 +2,6 @@ services: squiz: container_name: squiz restart: unless-stopped - image: gitea.pena/squiz/frontpanel/staging:$GITHUB_RUN_NUMBER + image: gitea.pena/squiz/frontpanel/staging:latest hostname: squiz tty: true diff --git a/package.json b/package.json index d3cde959..62f67d00 100755 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "test": "craco test", "eject": "craco eject", "code:format": "prettier --write --ignore-unknown", + "deploy": "docker login gitea.pena && docker build -t gitea.pena/squiz/frontanswerer/$(git branch --show-current):latest . && docker push gitea.pena/squiz/frontanswerer/$(git branch --show-current):latest", "prepare": "husky install", "cypress:open": "cypress open", "cypress:run": "cypress run" From 91a290dc6c65bf4726ea900f152a2b1c3273a2c2 Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 7 Aug 2025 15:49:44 +0300 Subject: [PATCH 16/18] =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B5=D0=BD=20=D1=85=D0=B0=D0=B1=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/deployProd.yml | 5 ++--- package.json | 2 +- src/utils/generateHubWalletRequest.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/deployProd.yml b/.gitea/workflows/deployProd.yml index ad853873..cd705f0c 100644 --- a/.gitea/workflows/deployProd.yml +++ b/.gitea/workflows/deployProd.yml @@ -2,9 +2,8 @@ name: Deploy run-name: ${{ gitea.actor }} build image and push to container registry on: - push: - branches: - - 'main' + registry_package: + types: [published] jobs: CreateImage: diff --git a/package.json b/package.json index 62f67d00..6246d353 100755 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "test": "craco test", "eject": "craco eject", "code:format": "prettier --write --ignore-unknown", - "deploy": "docker login gitea.pena && docker build -t gitea.pena/squiz/frontanswerer/$(git branch --show-current):latest . && docker push gitea.pena/squiz/frontanswerer/$(git branch --show-current):latest", + "deploy": "docker login gitea.pena && docker build -t gitea.pena/squiz/frontpanel/$(git branch --show-current):latest . && docker push gitea.pena/squiz/frontpanel/$(git branch --show-current):latest", "prepare": "husky install", "cypress:open": "cypress open", "cypress:run": "cypress run" diff --git a/src/utils/generateHubWalletRequest.ts b/src/utils/generateHubWalletRequest.ts index bfffc794..d56cf67a 100644 --- a/src/utils/generateHubWalletRequest.ts +++ b/src/utils/generateHubWalletRequest.ts @@ -19,7 +19,7 @@ export const generateHubWalletRequestURL = ({ if (currentDomain === "localhost") currentDomain += ":3000"; // Используем более надежный способ генерации URL - const baseUrl = `http://localhost:3001/anyservicepayment`; + const baseUrl = `http://${isTestServer ? "s" : ""}hub.pena.digital/anyservicepayment`; const params = new URLSearchParams({ fromdomain: currentDomain, action: action, From 5e18401762c4d6d64eb5d573fccfef45aa94a7af Mon Sep 17 00:00:00 2001 From: Nastya Date: Thu, 7 Aug 2025 16:44:22 +0300 Subject: [PATCH 17/18] update deploy --- deployments/staging/docker-compose.yaml | 1 + src/utils/generateHubWalletRequest.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 1549c906..e9efd2cc 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -5,3 +5,4 @@ services: image: gitea.pena/squiz/frontpanel/staging:latest hostname: squiz tty: true + pull_policy: always diff --git a/src/utils/generateHubWalletRequest.ts b/src/utils/generateHubWalletRequest.ts index d56cf67a..b7f6bfdb 100644 --- a/src/utils/generateHubWalletRequest.ts +++ b/src/utils/generateHubWalletRequest.ts @@ -18,6 +18,8 @@ export const generateHubWalletRequestURL = ({ let currentDomain = window.location.host; if (currentDomain === "localhost") currentDomain += ":3000"; +console.log("Я здесь для отладки и спешу сообщить, что деплой был успешно завершен!") + // Используем более надежный способ генерации URL const baseUrl = `http://${isTestServer ? "s" : ""}hub.pena.digital/anyservicepayment`; const params = new URLSearchParams({ From 80de8be2879490b0d8212377edd1a9e6b02623b4 Mon Sep 17 00:00:00 2001 From: Nastya Date: Sat, 9 Aug 2025 02:26:20 +0300 Subject: [PATCH 18/18] =?UTF-8?q?=D0=90=D0=B2=D1=82=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B2=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/logo/Postback.tsx | 15 ++ src/assets/icons/logo/PostbackPC.tsx | 19 +++ src/assets/icons/logo/zapier.png | Bin 0 -> 3475 bytes src/model/quizSettings.ts | 2 + .../IntegrationsModal/Postback/index.tsx | 75 ++++++++++ .../IntegrationsModal/Zapier/index.tsx | 75 ++++++++++ .../IntegrationsPage/IntegrationsPage.tsx | 16 +- .../PartnersBoard/PartnersBoard.tsx | 140 ++++++++++++------ .../ServiceButton/ServiceButton.tsx | 57 ------- .../buttons/IntegrationButton.tsx | 53 +++++++ .../PartnersBoard/buttons/PostbackButton.tsx | 32 ++++ .../PartnersBoard/buttons/ServiceButton.tsx | 40 +++++ .../PartnersBoard/buttons/ZapierButton.tsx | 35 +++++ 13 files changed, 456 insertions(+), 103 deletions(-) create mode 100644 src/assets/icons/logo/Postback.tsx create mode 100644 src/assets/icons/logo/PostbackPC.tsx create mode 100644 src/assets/icons/logo/zapier.png create mode 100644 src/pages/IntegrationsPage/IntegrationsModal/Postback/index.tsx create mode 100644 src/pages/IntegrationsPage/IntegrationsModal/Zapier/index.tsx delete mode 100644 src/pages/IntegrationsPage/PartnersBoard/ServiceButton/ServiceButton.tsx create mode 100644 src/pages/IntegrationsPage/PartnersBoard/buttons/IntegrationButton.tsx create mode 100644 src/pages/IntegrationsPage/PartnersBoard/buttons/PostbackButton.tsx create mode 100644 src/pages/IntegrationsPage/PartnersBoard/buttons/ServiceButton.tsx create mode 100644 src/pages/IntegrationsPage/PartnersBoard/buttons/ZapierButton.tsx diff --git a/src/assets/icons/logo/Postback.tsx b/src/assets/icons/logo/Postback.tsx new file mode 100644 index 00000000..23956784 --- /dev/null +++ b/src/assets/icons/logo/Postback.tsx @@ -0,0 +1,15 @@ +import { Box, SxProps } from "@mui/material"; + +export default (sx: SxProps) => ( + + + +); \ No newline at end of file diff --git a/src/assets/icons/logo/PostbackPC.tsx b/src/assets/icons/logo/PostbackPC.tsx new file mode 100644 index 00000000..1dff7213 --- /dev/null +++ b/src/assets/icons/logo/PostbackPC.tsx @@ -0,0 +1,19 @@ +import { Box, SxProps } from "@mui/material"; + +export default (sx: SxProps) => ( + + + + + +); \ No newline at end of file diff --git a/src/assets/icons/logo/zapier.png b/src/assets/icons/logo/zapier.png new file mode 100644 index 0000000000000000000000000000000000000000..53f20b391cbfa2a7f7c99ffcae71d17928b6a133 GIT binary patch literal 3475 zcmV;E4Q%p>P)-k9qFH>&JT6UfXe;w5FsDr63U?MQsIYN)faTp#Dke3n38XPYd*kS`;3w+Vlq~ z;zwH{wFQYvs1ZnjQs|GQP}-_WgD6pwHjW>09j_nj-Ss|Z=HAP>cV_JMdTrOKTbJS! zO*}Ju@44rm$9K;;gU|+T(Eku&v_T(8qVm61+wN&GVwVK(q_GHE2iowGd6EX;ed+Ym zR3m$^zz9<^aB_I$Bkm)k%-E%;lnHz|i$(`>D;v-2s=+2z2r_)h0gowiTqXKtU)m;QtT zC1f(0h~;^YaEx!%HJT7Ys7+u(gyXui1fv(HE6%rbx!fQMHXe;ew`UTG=i!VP2W{Ax zi^FHK+1us3OfvZq)71Z>W3riZ&?|;a42f#R@g5z{X75LVfxr)XlhMc#SYra7xyQUs z6p?QYPfi{T#P#Kq{0!{?7GftM)I!1!Dv%GbkWE%Odwk?v_MTvlGG<3h%Xg!ebvNT| z8b+o(;eCH(eDWDN01}aFV-fpDl;TYzley1EA zK`)F`@PXz_deYgk@YsTYkbGM;LhkPG?z*|i#Ra3Iqh+-*&Np$vyFtEgj0IR;Tnn$C+1k_f zQ|z&OUBaEQ z42nc7@|c<=mWKIsI=x*m?+&i!Vgh3sKpKdJUoDhM2dW->3L>qD0|$V;GB9FiM_b!> z>UjYVWNB2}($VpQDny_xckw*h%-LiuVGWNJ3RjsXGsMQb81=;#@YL|Fsm#(CE!o$5o04%|Sg#-Y%aV-8zbpf`*osmQC#u`>l%W$}>VC zqDJx6jbs7Rz?q+QUG@?;wJ!j?NXMX;B@(yHMC>PA@K&tO8jL{6{fR_EI>?d$$UaRHHg2i*TknxvK=7x-evy&a~mBameP z3|636wAtWuVW_U@nUl}wr|Mp*Sf7Re`ckRGpbu#nr2vprivy138o| zM`vc9?A6GnMA!EUUtfSMO#Zi>fRa$uuHS%)Fi_$S$CYZsSXQT=$9^%C%^s{@wp^I~ zq#cW9LE&nmsp-;Cwpf_0LG17(@XVJA#h%eZVWN@V*3vljb-*0HU)bGs!g1USK_plB zXr)04`ud5%q00h6WF%CE#>aPslV#=<)%DKiSnMiIW=?95GY;Vrs|8~)5k(E)Swc>3 zw)jw;d`iX{CLT+gbg#qxKw*%0G}(6FWT|wbo)3ex8&dET$Zn`3Pez20jv;pRrc%$r zvl))XM2_d+V=PQOU@SKi2w58nEFyc$+@PHQ$u0xp!IiU1_7!v z!q+bdrD+yp&mSKgx*E+JP>q!n+gqD&i(8RLpgeB|Gm-nY|{(Zlvz^6by^4^tey|iaD_736dc&$!2kz1-cf%*$rJ{7 z$U}cY&BYry55%7+O-;QS@N870y(h*|DQA0odn}64Xw~)NU>K$u_={^ciyXATv3s*Xas+Jlok5vqXSPEleF^cNs#B%R(SgcT3vh%Rk#!-g3AQo$Mm+$aaR~%oT(t23Nn9W2_k+B zh2&vDQBydU2$`NU?pDztwCEDVpe+PpyI3raN1B_=1wu%S6w5aO*3vl2FwkP(N(}u} zsa%q9pu)itpqE(W#X7%yQyMA(gf7_nfl2cgKY>+mYFkAM$G`#EKV^hbIiKf?P z%Vmyz%lJy>e9|GtzV3FbAKosiBLK3Jj>m5Uh5x4pzoCd^ZJWC~exJ(~_Q@CVc>FUu zMh74dItt@1!z;9iWToVBb`+xQiQe}1TjBYTz_i!0jGM}(GEoD9jsw8UIp}8%8HRB| zJs%P50}9Q1n&Q#J5RoczbUaVkhVk#M-I-rb7iM3Cs+2UvW4pQMeVs!i$n_NODADw= z9kc2$04mf>jcjc-Nw2|H{{!6)|Ak));>`-5kowB-s zuCefuQ=tZf)(b6_%KHOhm+Y8i3xy-8Wb%;fLS-Yds=ejTLX z6L|3$bVjP<*&BjguDZ#rgaKEm%7V;2k4q+%z@=7Gxc)xy&W4w2Qc|3l%;#SQco`2e z9a&=P@>lp|EFcxCEWZ);W zIU0{A28)begu;p(Qf5PwS+j2>3C~!kAuP#v^z`WH$2XRS7RxGBZA?V0xdF(Y1 zKz__x!TS~w7*mUt%Q^UhCVO8hl)BT=?d4f|1*XC#8a<_%kwZamOge!B-)>|Ze=Zd* zl}c~hw*9HD*5;QX(iC6F+)=(zyl$dU_=9}zh{d*fl-}Bsh<4_t@>5{dKaS7LJdfrh zPuUarbcDsItZx7YWd&bB^L0vC&C|9-;tCMra;W3d>K(=-;A<}c3cMPO4L{OJ{!!qa zkh}xAv6LGv^M!S;=Y-xpy<0vj&95m#7mr)PI7@vgUGUZQ9jsmZjFy>K|ND9AMnh|3 zm|8*JtNG#`j%!4GmG75Yd#UGlJH8>kSG~3MAk|_CT~>t(A!}8`tqXlremy002ovPDHLkV1mu# Bog)AM literal 0 HcmV?d00001 diff --git a/src/model/quizSettings.ts b/src/model/quizSettings.ts index 50050301..f04e8151 100644 --- a/src/model/quizSettings.ts +++ b/src/model/quizSettings.ts @@ -69,6 +69,8 @@ export type QuizTheme = export enum QuizMetricType { yandex = "yandexMetricsNumber", vk = "vkMetricsNumber", + zapier = "zapierIntegration", + postback = "postbackIntegration", } export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address"; diff --git a/src/pages/IntegrationsPage/IntegrationsModal/Postback/index.tsx b/src/pages/IntegrationsPage/IntegrationsModal/Postback/index.tsx new file mode 100644 index 00000000..3b7af294 --- /dev/null +++ b/src/pages/IntegrationsPage/IntegrationsModal/Postback/index.tsx @@ -0,0 +1,75 @@ +import { FC } from "react"; +import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { Quiz } from "@/model/quiz/quiz"; + +type PostbackModalProps = { + isModalOpen: boolean; + handleCloseModal: () => void; + companyName: string | null; + quiz: Quiz; +}; + +export const PostbackModal: FC = ({ + isModalOpen, + handleCloseModal, + companyName, + quiz +}) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + + return ( + + + + + Интеграция с {companyName ? companyName : "Postback"} + + + + + + + + Интеграция с Postback находится в разработке. + + + + + ); +}; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/IntegrationsModal/Zapier/index.tsx b/src/pages/IntegrationsPage/IntegrationsModal/Zapier/index.tsx new file mode 100644 index 00000000..39483a96 --- /dev/null +++ b/src/pages/IntegrationsPage/IntegrationsModal/Zapier/index.tsx @@ -0,0 +1,75 @@ +import { FC } from "react"; +import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box } from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { Quiz } from "@/model/quiz/quiz"; + +type ZapierModalProps = { + isModalOpen: boolean; + handleCloseModal: () => void; + companyName: string | null; + quiz: Quiz; +}; + +export const ZapierModal: FC = ({ + isModalOpen, + handleCloseModal, + companyName, + quiz +}) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + + return ( + + + + + Интеграция с {companyName ? companyName : "Zapier"} + + + + + + + + Интеграция с Zapier находится в разработке. + + + + + ); +}; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/IntegrationsPage.tsx b/src/pages/IntegrationsPage/IntegrationsPage.tsx index b74e3650..ce2af29a 100644 --- a/src/pages/IntegrationsPage/IntegrationsPage.tsx +++ b/src/pages/IntegrationsPage/IntegrationsPage.tsx @@ -27,6 +27,8 @@ export const IntegrationsPage = ({ >(null); const [isAmoCrmModalOpen, setIsAmoCrmModalOpen] = useState(false); + const [isZapierModalOpen, setIsZapierModalOpen] = useState(false); + const [isPostbackModalOpen, setIsPostbackModalOpen] = useState(false); useEffect(() => { if (editQuizId === null) navigate("/list"); @@ -44,6 +46,12 @@ export const IntegrationsPage = ({ const handleCloseAmoSRMModal = () => { setIsAmoCrmModalOpen(false); }; + const handleCloseZapierModal = () => { + setIsZapierModalOpen(false); + }; + const handleClosePostbackModal = () => { + setIsPostbackModalOpen(false); + }; return ( <> @@ -60,7 +68,7 @@ export const IntegrationsPage = ({ > Интеграции @@ -73,6 +81,12 @@ export const IntegrationsPage = ({ setIsAmoCrmModalOpen={setIsAmoCrmModalOpen} isAmoCrmModalOpen={isAmoCrmModalOpen} handleCloseAmoSRMModal={handleCloseAmoSRMModal} + setIsZapierModalOpen={setIsZapierModalOpen} + isZapierModalOpen={isZapierModalOpen} + handleCloseZapierModal={handleCloseZapierModal} + setIsPostbackModalOpen={setIsPostbackModalOpen} + isPostbackModalOpen={isPostbackModalOpen} + handleClosePostbackModal={handleClosePostbackModal} /> diff --git a/src/pages/IntegrationsPage/PartnersBoard/PartnersBoard.tsx b/src/pages/IntegrationsPage/PartnersBoard/PartnersBoard.tsx index 6612b444..6cb28b6d 100644 --- a/src/pages/IntegrationsPage/PartnersBoard/PartnersBoard.tsx +++ b/src/pages/IntegrationsPage/PartnersBoard/PartnersBoard.tsx @@ -1,13 +1,13 @@ -import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Typography, useTheme } from "@mui/material"; import React, { FC, lazy, Suspense } from "react"; -import { ServiceButton } from "./ServiceButton/ServiceButton"; +import { ServiceButton } from "./buttons/ServiceButton"; +import { ZapierButton } from "./buttons/ZapierButton"; +import { PostbackButton } from "./buttons/PostbackButton"; import { YandexMetricaLogo } from "../mocks/YandexMetricaLogo"; -// import AnalyticsModal from "./AnalyticsModal/AnalyticsModal"; import { VKPixelLogo } from "../mocks/VKPixelLogo"; import { QuizMetricType } from "@model/quizSettings"; import { AmoCRMLogo } from "../mocks/AmoCRMLogo"; import { useCurrentQuiz } from "@/stores/quizes/hooks"; -import { useUserStore } from "@/stores/user"; const AnalyticsModal = lazy(() => import("./AnalyticsModal/AnalyticsModal").then((module) => ({ @@ -21,6 +21,18 @@ const AmoCRMModal = lazy(() => })) ); +const ZapierModal = lazy(() => + import("../IntegrationsModal/Zapier").then((module) => ({ + default: module.ZapierModal, + })) +); + +const PostbackModal = lazy(() => + import("../IntegrationsModal/Postback").then((module) => ({ + default: module.PostbackModal, + })) +); + type PartnersBoardProps = { setIsModalOpen: (value: boolean) => void; companyName: keyof typeof QuizMetricType | null; @@ -30,6 +42,12 @@ type PartnersBoardProps = { setIsAmoCrmModalOpen: (value: boolean) => void; isAmoCrmModalOpen: boolean; handleCloseAmoSRMModal: () => void; + setIsZapierModalOpen: (value: boolean) => void; + isZapierModalOpen: boolean; + handleCloseZapierModal: () => void; + setIsPostbackModalOpen: (value: boolean) => void; + isPostbackModalOpen: boolean; + handleClosePostbackModal: () => void; }; export const PartnersBoard: FC = ({ @@ -41,13 +59,31 @@ export const PartnersBoard: FC = ({ setIsAmoCrmModalOpen, isAmoCrmModalOpen, handleCloseAmoSRMModal, + setIsZapierModalOpen, + isZapierModalOpen, + handleCloseZapierModal, + setIsPostbackModalOpen, + isPostbackModalOpen, + handleClosePostbackModal, }) => { const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(600)); - const quiz = useCurrentQuiz(); - const user = useUserStore(); + const sectionTitleStyles = { + textAlign: { xs: "start", sm: "start", md: "start" } as const, + lineHeight: "1", + marginBottom: "12px", + marginTop: "20px", + color: theme.palette.grey3.main, + fontSize: "18px", + }; + + const containerStyles = { + display: "flex", + flexWrap: "wrap", + justifyContent: { xs: "start", sm: "start", md: "start" }, + gap: { xs: "15px", md: "20px" }, + }; return ( = ({ }} > - <> - - CRM - - - } - setIsModalOpen={setIsAmoCrmModalOpen} - setCompanyName={setCompanyName} - name={"amoCRM"} - /> - - + CRM + + + } + setIsModalOpen={setIsAmoCrmModalOpen} + setCompanyName={setCompanyName} + name={"amoCRM"} + /> + + + Аналитика - + } setIsModalOpen={setIsModalOpen} @@ -114,9 +129,24 @@ export const PartnersBoard: FC = ({ name={"vk"} setIsModalOpen={setIsModalOpen} setCompanyName={setCompanyName} - > + /> + + + + Автоматизация + + + + + {companyName && ( = ({ isModalOpen={isAmoCrmModalOpen} handleCloseModal={handleCloseAmoSRMModal} companyName={companyName} - quiz={quiz} + quiz={quiz!} + /> + + )} + {companyName && isZapierModalOpen && ( + + + + )} + {companyName && isPostbackModalOpen && ( + + )} diff --git a/src/pages/IntegrationsPage/PartnersBoard/ServiceButton/ServiceButton.tsx b/src/pages/IntegrationsPage/PartnersBoard/ServiceButton/ServiceButton.tsx deleted file mode 100644 index 4773e19f..00000000 --- a/src/pages/IntegrationsPage/PartnersBoard/ServiceButton/ServiceButton.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Box, Typography, useTheme } from "@mui/material"; -import { FC } from "react"; -import { QuizMetricType } from "@model/quizSettings"; - -type PartnerItemProps = { - setIsModalOpen: (value: boolean) => void; - setCompanyName: (value: keyof typeof QuizMetricType) => void; - logo?: JSX.Element; - title?: string; - name: string; -}; - -export const ServiceButton: FC = ({ - setIsModalOpen, - logo, - title, - name, - setCompanyName, -}) => { - const theme = useTheme(); - - const handleClick = () => { - setCompanyName(name as keyof typeof QuizMetricType); - setIsModalOpen(true); - }; - - return ( - <> - - {logo && logo} - - {title && title} - - - - ); -}; diff --git a/src/pages/IntegrationsPage/PartnersBoard/buttons/IntegrationButton.tsx b/src/pages/IntegrationsPage/PartnersBoard/buttons/IntegrationButton.tsx new file mode 100644 index 00000000..fe22cfd0 --- /dev/null +++ b/src/pages/IntegrationsPage/PartnersBoard/buttons/IntegrationButton.tsx @@ -0,0 +1,53 @@ +import { Box } from "@mui/material"; +import { FC, useState, ReactNode } from "react"; + +type IntegrationButtonProps = { + children: ReactNode; + onClick: () => void; + padding?: string; +}; + +export const IntegrationButton: FC = ({ + children, + onClick, + padding = "0 20px", +}) => { + const [isPressed, setIsPressed] = useState(false); + + const handleMouseDown = () => setIsPressed(true); + const handleMouseUp = () => setIsPressed(false); + const handleMouseLeave = () => setIsPressed(false); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/PartnersBoard/buttons/PostbackButton.tsx b/src/pages/IntegrationsPage/PartnersBoard/buttons/PostbackButton.tsx new file mode 100644 index 00000000..faf90d05 --- /dev/null +++ b/src/pages/IntegrationsPage/PartnersBoard/buttons/PostbackButton.tsx @@ -0,0 +1,32 @@ +import { Box } from "@mui/material"; +import { FC } from "react"; +import { QuizMetricType } from "@model/quizSettings"; +import PostbackDefault from "@/assets/icons/logo/Postback"; +import PostbackPC from "@/assets/icons/logo/PostbackPC"; +import { IntegrationButton } from "./IntegrationButton"; + +type PostbackButtonProps = { + setIsModalOpen: (value: boolean) => void; + setCompanyName: (value: keyof typeof QuizMetricType) => void; +}; + +export const PostbackButton: FC = ({ + setIsModalOpen, + setCompanyName, +}) => { + const handleClick = () => { + setCompanyName("postback" as keyof typeof QuizMetricType); + setIsModalOpen(true); + }; + + return ( + + <> + {/* Иконка монитора */} + + {/* Текст Postback */} + + + + ); +}; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/PartnersBoard/buttons/ServiceButton.tsx b/src/pages/IntegrationsPage/PartnersBoard/buttons/ServiceButton.tsx new file mode 100644 index 00000000..88a92fce --- /dev/null +++ b/src/pages/IntegrationsPage/PartnersBoard/buttons/ServiceButton.tsx @@ -0,0 +1,40 @@ +import { Typography } from "@mui/material"; +import { FC } from "react"; +import { QuizMetricType } from "@model/quizSettings"; +import { IntegrationButton } from "./IntegrationButton"; + +type PartnerItemProps = { + setIsModalOpen: (value: boolean) => void; + setCompanyName: (value: keyof typeof QuizMetricType) => void; + logo?: JSX.Element; + title?: string; + name: string; +}; + +export const ServiceButton: FC = ({ + setIsModalOpen, + logo, + title, + name, + setCompanyName, +}) => { + const handleClick = () => { + setCompanyName(name as keyof typeof QuizMetricType); + setIsModalOpen(true); + }; + + return ( + + {logo && logo} + + {title && title} + + + ); +}; \ No newline at end of file diff --git a/src/pages/IntegrationsPage/PartnersBoard/buttons/ZapierButton.tsx b/src/pages/IntegrationsPage/PartnersBoard/buttons/ZapierButton.tsx new file mode 100644 index 00000000..8abcc70a --- /dev/null +++ b/src/pages/IntegrationsPage/PartnersBoard/buttons/ZapierButton.tsx @@ -0,0 +1,35 @@ +import { Box } from "@mui/material"; +import { FC } from "react"; +import { QuizMetricType } from "@model/quizSettings"; +import zapierLogo from "@/assets/icons/logo/zapier.png"; +import { IntegrationButton } from "./IntegrationButton"; + +type ZapierButtonProps = { + setIsModalOpen: (value: boolean) => void; + setCompanyName: (value: keyof typeof QuizMetricType) => void; +}; + +export const ZapierButton: FC = ({ + setIsModalOpen, + setCompanyName, +}) => { + const handleClick = () => { + setCompanyName("zapier" as keyof typeof QuizMetricType); + setIsModalOpen(true); + }; + + return ( + + + + ); +}; \ No newline at end of file