рефакторинг тарифов
This commit is contained in:
parent
572dfae016
commit
66456bac31
@ -11,8 +11,8 @@ import { useSnackbar } from "notistack";
|
|||||||
import { PayModal } from "./PayModal";
|
import { PayModal } from "./PayModal";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import { cartApi } from "@/api/cart";
|
import { cartApi } from "@/api/cart";
|
||||||
import { outCart } from "../Tariffs/Tariffs";
|
import { outCart } from "../Tariffs/utils";
|
||||||
import { inCart } from "../Tariffs/Tariffs";
|
import { inCart } from "../Tariffs/utils";
|
||||||
import { isTestServer } from "@/utils/hooks/useDomainDefine";
|
import { isTestServer } from "@/utils/hooks/useDomainDefine";
|
||||||
import { useToken } from "@frontend/kitui";
|
import { useToken } from "@frontend/kitui";
|
||||||
import { useSWRConfig } from "swr";
|
import { useSWRConfig } from "swr";
|
||||||
|
147
src/pages/Tariffs/TariffCardDisplaySelector.tsx
Normal file
147
src/pages/Tariffs/TariffCardDisplaySelector.tsx
Normal file
@ -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 <Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
width: "100%"
|
||||||
|
}}>
|
||||||
|
{content.map(data => <NavCard {...data} key={data.title} />)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
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,
|
||||||
|
<SmallIconPena />
|
||||||
|
)
|
||||||
|
|
||||||
|
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 <Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
width: "100%"
|
||||||
|
}}>
|
||||||
|
{content.map(data => <NavCard {...data} key={data.title} />)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
}
|
@ -1,39 +1,33 @@
|
|||||||
import { activatePromocode } from "@api/promocode";
|
import { activatePromocode } from "@api/promocode";
|
||||||
import { useToken } from "@frontend/kitui";
|
import { useToken } from "@frontend/kitui";
|
||||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
|
||||||
Container,
|
|
||||||
IconButton,
|
|
||||||
Modal,
|
|
||||||
Paper,
|
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useUserStore } from "@root/user";
|
import { useUserStore } from "@root/user";
|
||||||
import { LogoutButton } from "@ui_kit/LogoutButton";
|
|
||||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { withErrorBoundary } from "react-error-boundary";
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Logotip from "../../pages/Landing/images/icons/QuizLogo";
|
|
||||||
import CollapsiblePromocodeField from "./CollapsiblePromocodeField";
|
import CollapsiblePromocodeField from "./CollapsiblePromocodeField";
|
||||||
import { Tabs } from "./Tabs";
|
import { Tabs } from "./Tabs";
|
||||||
import { createTariffElements } from "./tariffsUtils/createTariffElements";
|
import { createTariffElements } from "./tariffsUtils/createTariffElements";
|
||||||
import { currencyFormatter } from "./tariffsUtils/currencyFormatter";
|
import { currencyFormatter } from "./tariffsUtils/currencyFormatter";
|
||||||
import { useWallet, setCash } from "@root/cash";
|
import { useWallet, setCash } from "@root/cash";
|
||||||
import { handleLogoutClick } from "@utils/HandleLogoutClick";
|
|
||||||
import { cartApi } from "@api/cart";
|
import { cartApi } from "@api/cart";
|
||||||
|
|
||||||
import { Other } from "./pages/Other";
|
import { TariffCardDisplaySelector } from "./TariffCardDisplaySelector";
|
||||||
import { ModalRequestCreate } from "./ModalRequestCreate";
|
import { ModalRequestCreate } from "./ModalRequestCreate";
|
||||||
import { cancelCC, useCC } from "@/stores/cc";
|
import { cancelCC, useCC } from "@/stores/cc";
|
||||||
import { NavSelect } from "./NavSelect";
|
import { NavSelect } from "./NavSelect";
|
||||||
import { useTariffs } from '@utils/hooks/useTariffs';
|
import { useTariffs } from '@utils/hooks/useTariffs';
|
||||||
import { useDiscounts } from '@utils/hooks/useDiscounts';
|
import { useDiscounts } from '@utils/hooks/useDiscounts';
|
||||||
|
import { PaymentConfirmationModal } from "./components/PaymentConfirmationModal";
|
||||||
|
import { TariffsHeader } from "./components/TariffsHeader";
|
||||||
|
import { inCart, outCart } from "./utils";
|
||||||
|
|
||||||
const StepperText: Record<string, string> = {
|
const StepperText: Record<string, string> = {
|
||||||
day: "Тарифы на время",
|
day: "Тарифы на время",
|
||||||
@ -50,9 +44,9 @@ function TariffPage() {
|
|||||||
const userId = useUserStore((state) => state.userId);
|
const userId = useUserStore((state) => state.userId);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const user = useUserStore((state) => state.customerAccount);
|
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("________________34563875693785692576_____________USERRRRRRR")
|
||||||
console.log(a)
|
// console.log(userWithWallet)
|
||||||
const { data: discounts } = useDiscounts(userId);
|
const { data: discounts } = useDiscounts(userId);
|
||||||
const [isRequestCreate, setIsRequestCreate] = useState(false);
|
const [isRequestCreate, setIsRequestCreate] = useState(false);
|
||||||
const [openModal, setOpenModal] = useState({});
|
const [openModal, setOpenModal] = useState({});
|
||||||
@ -69,13 +63,13 @@ console.log("________34563875693785692576_____ TARIFFS")
|
|||||||
console.log(tariffs)
|
console.log(tariffs)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (a) {
|
if (userWithWallet) {
|
||||||
let cs = currencyFormatter.format(Number(user.wallet.cash) / 100);
|
let cs = currencyFormatter.format(Number(user.wallet.cash) / 100);
|
||||||
let cc = Number(user.wallet.cash);
|
let cc = Number(user.wallet.cash);
|
||||||
let cr = Number(user.wallet.cash) / 100;
|
let cr = Number(user.wallet.cash) / 100;
|
||||||
setCash(cs, cc, cr);
|
setCash(cs, cc, cr);
|
||||||
}
|
}
|
||||||
}, [a]);
|
}, [userWithWallet]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cc) {
|
if (cc) {
|
||||||
@ -169,63 +163,10 @@ console.log(tariffs)
|
|||||||
setIsRequestCreate(true)
|
setIsRequestCreate(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!a) return null;
|
if (!userWithWallet) return null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container
|
<TariffsHeader cashString={cashString} />
|
||||||
component="nav"
|
|
||||||
disableGutters
|
|
||||||
maxWidth={false}
|
|
||||||
sx={{
|
|
||||||
px: "16px",
|
|
||||||
display: "flex",
|
|
||||||
height: "80px",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: isMobile ? "7px" : isTablet ? "20px" : "60px",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
bgcolor: "white",
|
|
||||||
borderBottom: "1px solid #E3E3E3",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link to="/">
|
|
||||||
<Logotip width={124} />
|
|
||||||
</Link>
|
|
||||||
<IconButton onClick={() => navigate("/list")}>
|
|
||||||
<ArrowLeft color="black" />
|
|
||||||
</IconButton>
|
|
||||||
<Box sx={{ display: "flex", ml: "auto" }}>
|
|
||||||
<Box sx={{ whiteSpace: "nowrap" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "12px",
|
|
||||||
lineHeight: "14px",
|
|
||||||
color: "gray",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Мой баланс
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color={"#7e2aea"}
|
|
||||||
fontSize={
|
|
||||||
isMobile ? (cashString.length > 9 ? "13px" : "16px") : "16px"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{cashString}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<LogoutButton
|
|
||||||
onClick={() => {
|
|
||||||
navigate("/");
|
|
||||||
handleLogoutClick();
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
ml: "20px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Container>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
p: "25px",
|
p: "25px",
|
||||||
@ -281,9 +222,9 @@ console.log(tariffs)
|
|||||||
discounts,
|
discounts,
|
||||||
openModalHC,
|
openModalHC,
|
||||||
)}
|
)}
|
||||||
{(selectedItem === "dop" || selectedItem === "hide" || selectedItem === "create")
|
{(selectedItem === "hide" || selectedItem === "create" || selectedItem === "premium" || selectedItem === "analytics" || selectedItem === "custom")
|
||||||
&& (
|
&& (
|
||||||
<Other
|
<TariffCardDisplaySelector
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
content={[
|
content={[
|
||||||
{
|
{
|
||||||
@ -294,8 +235,67 @@ console.log(tariffs)
|
|||||||
title: "Создать квиз на заказ",
|
title: "Создать квиз на заказ",
|
||||||
onClick: () => setSelectedItem("create")
|
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}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedItem === "dop" && (
|
||||||
|
<TariffCardDisplaySelector
|
||||||
|
selectedItem={selectedItem}
|
||||||
|
content={
|
||||||
|
selectedItem === "dop"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: `Убрать логотип "PenaQuiz"`,
|
||||||
|
onClick: () => 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}
|
tariffs={tariffs}
|
||||||
user={user}
|
user={user}
|
||||||
discounts={discounts}
|
discounts={discounts}
|
||||||
@ -305,37 +305,12 @@ console.log(tariffs)
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Modal
|
<PaymentConfirmationModal
|
||||||
open={Object.values(openModal).length > 0}
|
open={Object.values(openModal).length > 0}
|
||||||
onClose={() => setOpenModal({})}
|
onClose={() => setOpenModal({})}
|
||||||
>
|
onConfirm={() => tryBuy(openModal)}
|
||||||
<Paper
|
price={openModal.price}
|
||||||
sx={{
|
/>
|
||||||
position: "absolute" as "absolute",
|
|
||||||
top: "50%",
|
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
|
||||||
boxShadow: 24,
|
|
||||||
p: 4,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
id="modal-modal-title"
|
|
||||||
variant="h6"
|
|
||||||
component="h2"
|
|
||||||
mb="20px"
|
|
||||||
>
|
|
||||||
Вы подтверждаете платёж в сумму{" "}
|
|
||||||
{openModal.price ? openModal.price.toFixed(2) : 0} ₽
|
|
||||||
</Typography>
|
|
||||||
<Button variant="contained" onClick={() => tryBuy(openModal)}>
|
|
||||||
купить
|
|
||||||
</Button>
|
|
||||||
</Paper>
|
|
||||||
</Modal>
|
|
||||||
<ModalRequestCreate open={isRequestCreate} onClose={() => setIsRequestCreate(false)} />
|
<ModalRequestCreate open={isRequestCreate} onClose={() => setIsRequestCreate(false)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -364,47 +339,3 @@ const LoadingPage = () => (
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
51
src/pages/Tariffs/components/PaymentConfirmationModal.tsx
Normal file
51
src/pages/Tariffs/components/PaymentConfirmationModal.tsx
Normal file
@ -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 (
|
||||||
|
<Modal open={open} onClose={onClose}>
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
position: "absolute" as "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
id="modal-modal-title"
|
||||||
|
variant="h6"
|
||||||
|
component="h2"
|
||||||
|
mb="20px"
|
||||||
|
>
|
||||||
|
Вы подтверждаете платёж в сумму{" "}
|
||||||
|
{price ? price.toFixed(2) : 0} ₽
|
||||||
|
</Typography>
|
||||||
|
<Button variant="contained" onClick={onConfirm}>
|
||||||
|
купить
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
83
src/pages/Tariffs/components/TariffsHeader.tsx
Normal file
83
src/pages/Tariffs/components/TariffsHeader.tsx
Normal file
@ -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 (
|
||||||
|
<Container
|
||||||
|
component="nav"
|
||||||
|
disableGutters
|
||||||
|
maxWidth={false}
|
||||||
|
sx={{
|
||||||
|
px: "16px",
|
||||||
|
display: "flex",
|
||||||
|
height: "80px",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: isMobile ? "7px" : isTablet ? "20px" : "60px",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
bgcolor: "white",
|
||||||
|
borderBottom: "1px solid #E3E3E3",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link to="/">
|
||||||
|
<Logotip width={124} />
|
||||||
|
</Link>
|
||||||
|
<IconButton onClick={() => navigate("/list")}>
|
||||||
|
<ArrowLeft color="black" />
|
||||||
|
</IconButton>
|
||||||
|
<Box sx={{ display: "flex", ml: "auto" }}>
|
||||||
|
<Box sx={{ whiteSpace: "nowrap" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "12px",
|
||||||
|
lineHeight: "14px",
|
||||||
|
color: "gray",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Мой баланс
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color={"#7e2aea"}
|
||||||
|
fontSize={
|
||||||
|
isMobile ? (cashString.length > 9 ? "13px" : "16px") : "16px"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{cashString}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<LogoutButton
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/");
|
||||||
|
handleLogoutClick();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
ml: "20px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
@ -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 <Box
|
|
||||||
sx={{
|
|
||||||
justifyContent: "left",
|
|
||||||
display: "grid",
|
|
||||||
gap: "40px",
|
|
||||||
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${isTablet ? "436px" : "360px"
|
|
||||||
}))`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{createTariffElements(
|
|
||||||
filteredBadgeTariffs,
|
|
||||||
false,
|
|
||||||
user,
|
|
||||||
discounts,
|
|
||||||
openModalHC,
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
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 <Box
|
|
||||||
sx={{
|
|
||||||
justifyContent: "left",
|
|
||||||
display: "grid",
|
|
||||||
gap: "40px",
|
|
||||||
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${isTablet ? "436px" : "360px"
|
|
||||||
}))`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{createTariffElements(
|
|
||||||
filteredCreateTariffs,
|
|
||||||
false,
|
|
||||||
user,
|
|
||||||
discounts,
|
|
||||||
openModalHC,
|
|
||||||
sendRequest,
|
|
||||||
true,
|
|
||||||
<SmallIconPena/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
default:
|
|
||||||
return <Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
width: "100%"
|
|
||||||
}}>
|
|
||||||
{content.map(data => <NavCard {...data} key={data.title} />)}
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
type TypePages = "count" | "day" | "dop" | "hide" | "create"
|
type TypePages = "count" | "day" | "dop" | "hide" | "create" | "premium" | "analytics" | "custom"
|
46
src/pages/Tariffs/utils.ts
Normal file
46
src/pages/Tariffs/utils.ts
Normal file
@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -6,7 +6,7 @@ import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
|
|||||||
import { Box, Button, IconButton, Popover, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Button, IconButton, Popover, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
import { deleteQuiz, setEditQuizId } from "@root/quizes/actions";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
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 { makeRequest } from "@api/makeRequest";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
InputAdornment,
|
|
||||||
InputBase,
|
|
||||||
SxProps,
|
SxProps,
|
||||||
Theme,
|
Theme,
|
||||||
Typography,
|
Typography,
|
||||||
@ -17,23 +14,13 @@ import {
|
|||||||
useTicketStore,
|
useTicketStore,
|
||||||
} from "@root/ticket";
|
} from "@root/ticket";
|
||||||
import type { TouchEvent, WheelEvent } from "react";
|
import type { TouchEvent, WheelEvent } from "react";
|
||||||
import * as React from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import ChatMessageRenderer from "./ChatMessageRenderer";
|
||||||
import ChatMessage from "./ChatMessage";
|
import ChatInput from "./ChatInput";
|
||||||
import ChatVideo from "./ChatVideo";
|
|
||||||
import SendIcon from "@icons/SendIcon";
|
|
||||||
import UserCircleIcon from "./UserCircleIcon";
|
import UserCircleIcon from "./UserCircleIcon";
|
||||||
import { throttle, TicketMessage } from "@frontend/kitui";
|
import { throttle, TicketMessage } from "@frontend/kitui";
|
||||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||||
import { useUserStore } from "@root/user";
|
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 {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -41,22 +28,20 @@ interface Props {
|
|||||||
onclickArrow?: () => void;
|
onclickArrow?: () => void;
|
||||||
sendMessage: (a: string) => Promise<boolean>;
|
sendMessage: (a: string) => Promise<boolean>;
|
||||||
sendFile: (a: File | undefined) => Promise<void>;
|
sendFile: (a: File | undefined) => Promise<void>;
|
||||||
greetingMessage: TicketMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const greetingMessage = "Здравствуйте, задайте ваш вопрос и наш оператор вам ответит в течение 10 минут";
|
||||||
|
|
||||||
export default function Chat({
|
export default function Chat({
|
||||||
open = false,
|
open = false,
|
||||||
sx,
|
sx,
|
||||||
onclickArrow,
|
onclickArrow,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
sendFile,
|
sendFile,
|
||||||
greetingMessage,
|
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
||||||
const [messageField, setMessageField] = useState<string>("");
|
|
||||||
const [disableFileButton, setDisableFileButton] = useState(false);
|
|
||||||
|
|
||||||
const user = useUserStore((state) => state.user?._id);
|
const user = useUserStore((state) => state.user?._id);
|
||||||
const ticket = useTicketStore(
|
const ticket = useTicketStore(
|
||||||
@ -72,31 +57,11 @@ export default function Chat({
|
|||||||
const chatBoxRef = useRef<HTMLDivElement>(null);
|
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addOrUpdateUnauthMessages([greetingMessage]);
|
|
||||||
if (open) {
|
if (open) {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [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<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const throttledScrollHandler = useMemo(
|
const throttledScrollHandler = useMemo(
|
||||||
() =>
|
() =>
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
@ -152,14 +117,6 @@ export default function Chat({
|
|||||||
behavior,
|
behavior,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const handleTextfieldKeyPress: React.KeyboardEventHandler<
|
|
||||||
HTMLInputElement | HTMLTextAreaElement
|
|
||||||
> = (e) => {
|
|
||||||
if (e.key === "Enter" && !e.shiftKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
sendMessageHC();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -240,164 +197,34 @@ export default function Chat({
|
|||||||
>
|
>
|
||||||
{ticket.sessionData?.ticketId &&
|
{ticket.sessionData?.ticketId &&
|
||||||
messages.map((message) => {
|
messages.map((message) => {
|
||||||
const isFileVideo = () => {
|
const isSelf = useMemo(() =>
|
||||||
if (message.files) {
|
(ticket.sessionData?.sessionId || user) === message.user_id,
|
||||||
return ACCEPT_SEND_MEDIA_TYPES_MAP.video.some(
|
[ticket.sessionData?.sessionId, user, message.user_id]
|
||||||
(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 (
|
return (
|
||||||
<ChatImage
|
<ChatMessageRenderer
|
||||||
unAuthenticated
|
|
||||||
key={message.id}
|
key={message.id}
|
||||||
file={message.files[0]}
|
message={message}
|
||||||
createdAt={message.created_at}
|
isSelf={isSelf}
|
||||||
isSelf={
|
|
||||||
(ticket.sessionData?.sessionId || user) ===
|
|
||||||
message.user_id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (message.files.length > 0 && isFileVideo()) {
|
|
||||||
return (
|
|
||||||
<ChatVideo
|
|
||||||
unAuthenticated
|
|
||||||
key={message.id}
|
|
||||||
file={message.files[0]}
|
|
||||||
createdAt={message.created_at}
|
|
||||||
isSelf={
|
|
||||||
(ticket.sessionData?.sessionId || user) ===
|
|
||||||
message.user_id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (message.files.length > 0 && isFileDocument()) {
|
|
||||||
return (
|
|
||||||
<ChatDocument
|
|
||||||
unAuthenticated
|
|
||||||
key={message.id}
|
|
||||||
file={message.files[0]}
|
|
||||||
createdAt={message.created_at}
|
|
||||||
isSelf={
|
|
||||||
(ticket.sessionData?.sessionId || user) ===
|
|
||||||
message.user_id
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ChatMessage
|
|
||||||
unAuthenticated
|
|
||||||
key={message.id}
|
|
||||||
text={message.message}
|
|
||||||
createdAt={message.created_at}
|
|
||||||
isSelf={
|
|
||||||
(ticket.sessionData?.sessionId || user) ===
|
|
||||||
message.user_id
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!ticket.sessionData?.ticketId && (
|
{!ticket.sessionData?.ticketId && (
|
||||||
<ChatMessage
|
<ChatMessageRenderer
|
||||||
unAuthenticated
|
message={greetingMessage}
|
||||||
text={greetingMessage.message}
|
isSelf={useMemo(() =>
|
||||||
createdAt={greetingMessage.created_at}
|
(ticket.sessionData?.sessionId || user) === greetingMessage.user_id,
|
||||||
isSelf={
|
[ticket.sessionData?.sessionId, user, greetingMessage.user_id]
|
||||||
(ticket.sessionData?.sessionId || user) ===
|
)}
|
||||||
greetingMessage.user_id
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
<ChatInput
|
||||||
<InputBase
|
sendMessage={sendMessage}
|
||||||
value={messageField}
|
sendFile={sendFile}
|
||||||
fullWidth
|
isMessageSending={isMessageSending}
|
||||||
placeholder="Введите сообщение..."
|
|
||||||
id="message"
|
|
||||||
multiline
|
|
||||||
onKeyDown={handleTextfieldKeyPress}
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
p: 0,
|
|
||||||
}}
|
|
||||||
inputProps={{
|
|
||||||
sx: {
|
|
||||||
fontWeight: 400,
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
pt: upMd ? "30px" : "28px",
|
|
||||||
pb: upMd ? "30px" : "24px",
|
|
||||||
px: "19px",
|
|
||||||
maxHeight: "calc(19px * 5)",
|
|
||||||
color: "black",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
onChange={(e) => setMessageField(e.target.value)}
|
|
||||||
endAdornment={
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
disabled={disableFileButton}
|
|
||||||
onClick={() => {
|
|
||||||
if (!disableFileButton) fileInputRef.current?.click();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AttachFileIcon />
|
|
||||||
</IconButton>
|
|
||||||
<input
|
|
||||||
ref={fileInputRef}
|
|
||||||
id="fileinput"
|
|
||||||
onChange={(e) => {
|
|
||||||
if (e.target.files?.[0])
|
|
||||||
sendFileHC(e.target.files?.[0]);
|
|
||||||
}}
|
|
||||||
style={{ display: "none" }}
|
|
||||||
type="file"
|
|
||||||
/>
|
/>
|
||||||
<IconButton
|
|
||||||
disabled={isMessageSending}
|
|
||||||
onClick={sendMessageHC}
|
|
||||||
sx={{
|
|
||||||
height: "53px",
|
|
||||||
width: "53px",
|
|
||||||
mr: "13px",
|
|
||||||
p: 0,
|
|
||||||
opacity: isMessageSending ? 0.3 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SendIcon
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
137
src/ui_kit/FloatingSupportChat/ChatInput.tsx
Normal file
137
src/ui_kit/FloatingSupportChat/ChatInput.tsx
Normal file
@ -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<boolean>;
|
||||||
|
sendFile: (file: File | undefined) => Promise<void>;
|
||||||
|
isMessageSending: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatInput = ({ sendMessage, sendFile, isMessageSending }: ChatInputProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
|
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
|
||||||
|
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<HTMLInputElement>) => {
|
||||||
|
setMessageField(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
||||||
|
<InputBase
|
||||||
|
value={messageField}
|
||||||
|
fullWidth
|
||||||
|
placeholder="Введите сообщение..."
|
||||||
|
id="message"
|
||||||
|
multiline
|
||||||
|
onKeyDown={handleTextfieldKeyPress}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
p: 0,
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
sx: {
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "16px",
|
||||||
|
lineHeight: "19px",
|
||||||
|
pt: upMd ? "30px" : "28px",
|
||||||
|
pb: upMd ? "30px" : "24px",
|
||||||
|
px: "19px",
|
||||||
|
maxHeight: "calc(19px * 5)",
|
||||||
|
color: "black",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
disabled={disableFileButton}
|
||||||
|
onClick={handleFileButtonClick}
|
||||||
|
>
|
||||||
|
<AttachFileIcon />
|
||||||
|
</IconButton>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="fileinput"
|
||||||
|
onChange={handleFileInputChange}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
disabled={isMessageSending}
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
sx={{
|
||||||
|
height: "53px",
|
||||||
|
width: "53px",
|
||||||
|
mr: "13px",
|
||||||
|
p: 0,
|
||||||
|
opacity: isMessageSending ? 0.3 : 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SendIcon
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatInput;
|
70
src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx
Normal file
70
src/ui_kit/FloatingSupportChat/ChatMessageRenderer.tsx
Normal file
@ -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 <ChatImage {...commonProps} />;
|
||||||
|
case 'video':
|
||||||
|
return <ChatVideo {...commonProps} />;
|
||||||
|
case 'document':
|
||||||
|
return <ChatDocument {...commonProps} />;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Текстовое сообщение
|
||||||
|
return (
|
||||||
|
<ChatMessage
|
||||||
|
unAuthenticated
|
||||||
|
text={message.message}
|
||||||
|
createdAt={message.created_at}
|
||||||
|
isSelf={isSelf}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ChatMessageRenderer.displayName = 'ChatMessageRenderer';
|
||||||
|
|
||||||
|
export default ChatMessageRenderer;
|
@ -47,7 +47,6 @@ interface Props {
|
|||||||
sendFile: (a: File | undefined) => Promise<void>;
|
sendFile: (a: File | undefined) => Promise<void>;
|
||||||
modalWarningType: string | null;
|
modalWarningType: string | null;
|
||||||
setModalWarningType: any;
|
setModalWarningType: any;
|
||||||
greetingMessage: TicketMessage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FloatingSupportChat({
|
export default function FloatingSupportChat({
|
||||||
@ -59,7 +58,6 @@ export default function FloatingSupportChat({
|
|||||||
sendFile,
|
sendFile,
|
||||||
modalWarningType,
|
modalWarningType,
|
||||||
setModalWarningType,
|
setModalWarningType,
|
||||||
greetingMessage,
|
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [monitorType, setMonitorType] = useState<"desktop" | "mobile" | "">("");
|
const [monitorType, setMonitorType] = useState<"desktop" | "mobile" | "">("");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -108,7 +106,6 @@ export default function FloatingSupportChat({
|
|||||||
sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }}
|
sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
sendFile={sendFile}
|
sendFile={sendFile}
|
||||||
greetingMessage={greetingMessage}
|
|
||||||
/>
|
/>
|
||||||
<Dialog
|
<Dialog
|
||||||
fullScreen
|
fullScreen
|
||||||
@ -121,7 +118,6 @@ export default function FloatingSupportChat({
|
|||||||
onclickArrow={handleChatClickClose}
|
onclickArrow={handleChatClickClose}
|
||||||
sendMessage={sendMessage}
|
sendMessage={sendMessage}
|
||||||
sendFile={sendFile}
|
sendFile={sendFile}
|
||||||
greetingMessage={greetingMessage}
|
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Fab
|
<Fab
|
||||||
|
@ -72,33 +72,6 @@ export default () => {
|
|||||||
setIsChatOpened((state) => !state);
|
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({
|
useTicketsFetcher({
|
||||||
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`,
|
url: `${process.env.REACT_APP_DOMAIN}/heruvym/v1.0.0/getTickets`,
|
||||||
ticketsPerPage: 10,
|
ticketsPerPage: 10,
|
||||||
@ -157,7 +130,6 @@ export default () => {
|
|||||||
);
|
);
|
||||||
if (isTicketClosed) {
|
if (isTicketClosed) {
|
||||||
cleanAuthTicketData();
|
cleanAuthTicketData();
|
||||||
addOrUpdateUnauthMessages([getGreetingMessage]);
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
cleanUnauthTicketData();
|
cleanUnauthTicketData();
|
||||||
localStorage.removeItem("unauth-ticket");
|
localStorage.removeItem("unauth-ticket");
|
||||||
@ -185,8 +157,8 @@ export default () => {
|
|||||||
({ shown }) => shown?.me !== 1,
|
({ shown }) => shown?.me !== 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
newMessages.map(async ({ id }) => {
|
newMessages.forEach(({ id, user_id }) => {
|
||||||
await shownMessage(id);
|
if ((ticket.sessionData?.sessionId || user) === user_id) shownMessage(id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isChatOpened, ticket.messages]);
|
}, [isChatOpened, ticket.messages]);
|
||||||
@ -248,7 +220,6 @@ export default () => {
|
|||||||
sendFile={sendFile}
|
sendFile={sendFile}
|
||||||
modalWarningType={modalWarningType}
|
modalWarningType={modalWarningType}
|
||||||
setModalWarningType={setModalWarningType}
|
setModalWarningType={setModalWarningType}
|
||||||
greetingMessage={getGreetingMessage}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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<TicketMessage[]>(
|
|
||||||
"ticket",
|
|
||||||
addOrUpdateUnauthMessages,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [modalWarningType, setModalWarningType] =
|
|
||||||
useState<ModalWarningType>(null);
|
|
||||||
const [isChatOpened, setIsChatOpened] = useState<boolean>(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<TicketMessage>({
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
@ -4,7 +4,7 @@ import { useSSETab } from "./useSSETab";
|
|||||||
import { cancelPayCartProcess } from "@/stores/notEnoughMoneyAmount";
|
import { cancelPayCartProcess } from "@/stores/notEnoughMoneyAmount";
|
||||||
import { setCash } from "@/stores/cash";
|
import { setCash } from "@/stores/cash";
|
||||||
import { currencyFormatter } from "@/pages/Tariffs/tariffsUtils/currencyFormatter";
|
import { currencyFormatter } from "@/pages/Tariffs/tariffsUtils/currencyFormatter";
|
||||||
import { inCart } from "@/pages/Tariffs/Tariffs";
|
import { inCart } from "@/pages/Tariffs/utils";
|
||||||
|
|
||||||
type Ping = [{ event: "ping" }]
|
type Ping = [{ event: "ping" }]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user