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" }]