Merge branch 'dev' into cart-calc
@ -9,26 +9,8 @@
|
|||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["@typescript-eslint", "react"],
|
||||||
"@typescript-eslint",
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"quotes": ["error", "double"]
|
||||||
"error",
|
|
||||||
"tab"
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^5.4.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
@ -17,6 +17,25 @@ export type HistoryRecord = {
|
|||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface GetHistoryResponse2 {
|
||||||
|
totalPages: number;
|
||||||
|
records: HistoryRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HistoryRecord2 = {
|
||||||
|
comment: string;
|
||||||
|
createdAt: string;
|
||||||
|
id: string;
|
||||||
|
isDeleted: boolean;
|
||||||
|
key: string;
|
||||||
|
rawDetails: {
|
||||||
|
price: number;
|
||||||
|
tariffs: Tariff[];
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
|
||||||
type RawDetails = { Key: string; Value: KeyValue[][] }
|
type RawDetails = { Key: string; Value: KeyValue[][] }
|
||||||
type KeyValue = { Key: string; Value: string | number };
|
type KeyValue = { Key: string; Value: string | number };
|
||||||
|
|
||||||
@ -25,7 +44,7 @@ const regList:Record<string, number> = {
|
|||||||
"price": 1
|
"price": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHistory(): Promise<[GetHistoryResponse | null, string?]> {
|
export async function getHistory(): Promise<[GetHistoryResponse | GetHistoryResponse2 | null, string?]> {
|
||||||
try {
|
try {
|
||||||
const historyResponse = await makeRequest<never, GetHistoryResponse>({
|
const historyResponse = await makeRequest<never, GetHistoryResponse>({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/customer/history?page=1&limit=100&type=payCart",
|
url: process.env.REACT_APP_DOMAIN + "/customer/history?page=1&limit=100&type=payCart",
|
||||||
@ -33,6 +52,10 @@ export async function getHistory(): Promise<[GetHistoryResponse | null, string?]
|
|||||||
useToken: true,
|
useToken: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!Array.isArray(historyResponse.records[0]?.rawDetails)) {
|
||||||
|
return [historyResponse] as [GetHistoryResponse2]
|
||||||
|
}
|
||||||
|
|
||||||
const checked = historyResponse.records.map((data) => {
|
const checked = historyResponse.records.map((data) => {
|
||||||
console.log(data.rawDetails)
|
console.log(data.rawDetails)
|
||||||
const buffer:KeyValue[] = []
|
const buffer:KeyValue[] = []
|
||||||
|
@ -6,7 +6,9 @@ import type { ServiceKeyToPrivilegesMap } from "@root/model/privilege";
|
|||||||
import type { GetTariffsResponse } from "@root/model/tariff";
|
import type { GetTariffsResponse } from "@root/model/tariff";
|
||||||
import { removeTariffFromCart } from "@root/stores/user";
|
import { removeTariffFromCart } from "@root/stores/user";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/strator";
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/strator"
|
||||||
|
console.log("домен с которого тарифы запрашиваются", process.env.REACT_APP_DOMAIN)
|
||||||
|
console.log("домен с которого тарифы запрашиваются", apiUrl)
|
||||||
|
|
||||||
export async function getTariffs(
|
export async function getTariffs(
|
||||||
apiPage: number,
|
apiPage: number,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { makeRequest } from "@frontend/kitui"
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { parseAxiosError } from "@root/utils/parse-error"
|
import { parseAxiosError } from "@root/utils/parse-error";
|
||||||
|
|
||||||
import { SendTicketMessageRequest } from "@frontend/kitui"
|
import { SendTicketMessageRequest } from "@frontend/kitui";
|
||||||
|
|
||||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym"
|
const apiUrl = process.env.REACT_APP_DOMAIN + "/heruvym";
|
||||||
|
|
||||||
export async function sendTicketMessage(
|
export async function sendTicketMessage(
|
||||||
ticketId: string,
|
ticketId: string,
|
||||||
@ -18,13 +18,13 @@ export async function sendTicketMessage(
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||||
})
|
});
|
||||||
|
|
||||||
return [sendTicketMessageResponse]
|
return [sendTicketMessageResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError)
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Не удалось отправить сообщение. ${error}`]
|
return [null, `Не удалось отправить сообщение. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +35,12 @@ export async function shownMessage(id: string): Promise<[null, string?]> {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
useToken: true,
|
useToken: true,
|
||||||
body: { id },
|
body: { id },
|
||||||
})
|
});
|
||||||
|
|
||||||
return [shownMessageResponse]
|
return [shownMessageResponse];
|
||||||
} catch (nativeError) {
|
} catch (nativeError) {
|
||||||
const [error] = parseAxiosError(nativeError)
|
const [error] = parseAxiosError(nativeError);
|
||||||
|
|
||||||
return [null, `Не удалось прочесть сообщение. ${error}`]
|
return [null, `Не удалось прочесть сообщение. ${error}`];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
src/assets/Icons/arrowLeft.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ArrowLeft({ color = "#7E2AEA" }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="25"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 25 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20.75 12H4.25"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11 5.25L4.25 12L11 18.75"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
55
src/assets/Icons/download.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color: string;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Download({ color, sx }: Props) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "38px",
|
||||||
|
width: "45px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
...sx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="47"
|
||||||
|
height="42"
|
||||||
|
viewBox="0 0 47 42"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M33.8846 26.8076H44.2692C44.7283 26.8076 45.1685 26.9551 45.4931 27.2177C45.8177 27.4802 46 27.8363 46 28.2076V39.4076C46 39.7789 45.8177 40.135 45.4931 40.3976C45.1685 40.6601 44.7283 40.8076 44.2692 40.8076H2.73077C2.27174 40.8076 1.83151 40.6601 1.50693 40.3976C1.18235 40.135 1 39.7789 1 39.4076V28.2076C1 27.8363 1.18235 27.4802 1.50693 27.2177C1.83151 26.9551 2.27174 26.8076 2.73077 26.8076H13.1154"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M23.5 27V1"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M13.1155 11.3846L23.5001 1L33.8847 11.3846"
|
||||||
|
stroke={color}
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M39.135 36.0776C40.3141 36.0776 41.27 35.1217 41.27 33.9426C41.27 32.7635 40.3141 31.8076 39.135 31.8076C37.9559 31.8076 37 32.7635 37 33.9426C37 35.1217 37.9559 36.0776 39.135 36.0776Z"
|
||||||
|
fill={color}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
BIN
src/assets/bank-logo/b2b.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/bank-logo/sberpay.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/bank-logo/spb.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/bank-logo/umaney.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
@ -18,8 +18,6 @@ export default function CustomAccordion({ header, text, divide = false, price, l
|
|||||||
const upXs = useMediaQuery(theme.breakpoints.up("xs")); //300
|
const upXs = useMediaQuery(theme.breakpoints.up("xs")); //300
|
||||||
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
||||||
|
|
||||||
console.log(upXs);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef } from "react";
|
||||||
import { Typography, Drawer, useMediaQuery, useTheme, Box, IconButton, Badge, Button } from "@mui/material";
|
import { Typography, Drawer, useMediaQuery, useTheme, Box, IconButton, Badge, Button, Alert } from "@mui/material";
|
||||||
import SectionWrapper from "./SectionWrapper";
|
import SectionWrapper from "./SectionWrapper";
|
||||||
import CustomWrapperDrawer from "./CustomWrapperDrawer";
|
import CustomWrapperDrawer from "./CustomWrapperDrawer";
|
||||||
import { NotificationsModal } from "./NotificationsModal";
|
import { NotificationsModal } from "./NotificationsModal";
|
||||||
@ -52,10 +52,8 @@ function Drawers() {
|
|||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
setIsDrawerOpen(false);
|
|
||||||
navigate("payment")
|
|
||||||
if (!payCartError.includes("insufficient funds: ")) enqueueSnackbar(payCartError);
|
if (!payCartError.includes("insufficient funds: ")) enqueueSnackbar(payCartError);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payCartResponse) {
|
if (payCartResponse) {
|
||||||
@ -63,10 +61,11 @@ function Drawers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIsDrawerOpen(false);;
|
setIsDrawerOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleReplenishWallet() {
|
function handleReplenishWallet() {
|
||||||
|
setIsDrawerOpen(false);
|
||||||
navigate("/payment", { state: { notEnoughMoneyAmount } });
|
navigate("/payment", { state: { notEnoughMoneyAmount } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +212,8 @@ function Drawers() {
|
|||||||
{cart.services.map((serviceData) => {
|
{cart.services.map((serviceData) => {
|
||||||
return (
|
return (
|
||||||
<CustomWrapperDrawer key={serviceData.serviceKey} serviceData={serviceData} />
|
<CustomWrapperDrawer key={serviceData.serviceKey} serviceData={serviceData} />
|
||||||
)})}
|
);
|
||||||
|
})}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mt: "40px",
|
mt: "40px",
|
||||||
@ -257,8 +257,7 @@ function Drawers() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
{`${
|
{`${((cart.priceBeforeDiscounts - cart.priceAfterDiscounts) / (cart.priceBeforeDiscounts / 100)).toFixed(0)
|
||||||
((cart.priceBeforeDiscounts - cart.priceAfterDiscounts) / (cart.priceBeforeDiscounts / 100)).toFixed(0)
|
|
||||||
}%`}
|
}%`}
|
||||||
</span>
|
</span>
|
||||||
) : null
|
) : null
|
||||||
@ -316,17 +315,32 @@ function Drawers() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
mt: "25px",
|
||||||
|
gap: "15px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{notEnoughMoneyAmount > 0 && (
|
||||||
|
<Alert severity="error" variant="filled">
|
||||||
|
Не хватает {currencyFormatter.format(notEnoughMoneyAmount / 100)}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
disabled = {cart.priceAfterDiscounts === 0}
|
disabled={cart.priceAfterDiscounts === 0}
|
||||||
variant="pena-contained-dark"
|
variant="pena-contained-dark"
|
||||||
onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())}
|
onClick={() => (notEnoughMoneyAmount === 0 ? !loading && handlePayClick() : handleReplenishWallet())}
|
||||||
sx={{ mt: "25px", display: "block" }}
|
sx={{ display: "block" }}
|
||||||
>
|
>
|
||||||
{loading ? <Loader size={24} /> : notEnoughMoneyAmount === 0 ? "Оплатить" : "Пополнить"}
|
{loading ? <Loader size={24} /> : notEnoughMoneyAmount === 0 ? "Оплатить" : "Пополнить"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -10,58 +10,85 @@ import {
|
|||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { TicketMessage } from "@frontend/kitui";
|
|
||||||
import {
|
import {
|
||||||
addOrUpdateUnauthMessages,
|
TicketMessage,
|
||||||
useUnauthTicketStore,
|
makeRequest,
|
||||||
incrementUnauthMessageApiPage,
|
useTicketsFetcher,
|
||||||
setUnauthIsPreventAutoscroll,
|
useTicketMessages,
|
||||||
setUnauthSessionData,
|
getMessageFromFetchError,
|
||||||
setIsMessageSending,
|
useSSESubscription,
|
||||||
setUnauthTicketMessageFetchState,
|
createTicket,
|
||||||
} from "@root/stores/unauthTicket";
|
} from "@frontend/kitui";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import ChatMessage from "../ChatMessage";
|
import ChatMessage from "../ChatMessage";
|
||||||
import SendIcon from "../icons/SendIcon";
|
import SendIcon from "../icons/SendIcon";
|
||||||
|
import ArrowLeft from "@root/assets/Icons/arrowLeft";
|
||||||
import UserCircleIcon from "./UserCircleIcon";
|
import UserCircleIcon from "./UserCircleIcon";
|
||||||
import { throttle } from "@frontend/kitui";
|
import { throttle } from "@frontend/kitui";
|
||||||
import {
|
|
||||||
useTicketMessages,
|
|
||||||
getMessageFromFetchError,
|
|
||||||
useSSESubscription,
|
|
||||||
useEventListener,
|
|
||||||
createTicket,
|
|
||||||
} from "@frontend/kitui";
|
|
||||||
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
||||||
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
import {
|
||||||
|
checkAcceptableMediaType,
|
||||||
|
MAX_FILE_SIZE,
|
||||||
|
ACCEPT_SEND_MEDIA_TYPES_MAP,
|
||||||
|
} from "@utils/checkAcceptableMediaType";
|
||||||
|
import {
|
||||||
|
useTicketStore,
|
||||||
|
setTicketData,
|
||||||
|
addOrUpdateUnauthMessages,
|
||||||
|
setUnauthTicketMessageFetchState,
|
||||||
|
setUnauthIsPreventAutoscroll,
|
||||||
|
incrementUnauthMessage,
|
||||||
|
setIsMessageSending,
|
||||||
|
} from "@root/stores/tickets";
|
||||||
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
|
import ChatDocument from "./ChatDocument";
|
||||||
|
import ChatImage from "./ChatImage";
|
||||||
|
import ChatVideo from "./ChatVideo";
|
||||||
|
|
||||||
|
import type { WheelEvent, TouchEvent } from "react";
|
||||||
|
|
||||||
|
type ModalWarningType =
|
||||||
|
| "errorType"
|
||||||
|
| "errorSize"
|
||||||
|
| "picture"
|
||||||
|
| "video"
|
||||||
|
| "audio"
|
||||||
|
| "document"
|
||||||
|
| null;
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
|
onclickArrow?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Chat({ open = false, sx }: Props) {
|
export default function Chat({ open = false, onclickArrow, sx }: 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 [messageField, setMessageField] = useState<string>("");
|
const [messageField, setMessageField] = useState<string>("");
|
||||||
const sessionData = useUnauthTicketStore((state) => state.sessionData);
|
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||||
const messages = useUnauthTicketStore((state) => state.messages);
|
const [modalWarningType, setModalWarningType] =
|
||||||
const messageApiPage = useUnauthTicketStore((state) => state.apiPage);
|
useState<ModalWarningType>(null);
|
||||||
const messagesPerPage = useUnauthTicketStore(
|
|
||||||
(state) => state.messagesPerPage
|
|
||||||
);
|
|
||||||
const isMessageSending = useUnauthTicketStore(
|
|
||||||
(state) => state.isMessageSending
|
|
||||||
);
|
|
||||||
const isPreventAutoscroll = useUnauthTicketStore(
|
|
||||||
(state) => state.isPreventAutoscroll
|
|
||||||
);
|
|
||||||
const lastMessageId = useUnauthTicketStore((state) => state.lastMessageId);
|
|
||||||
const fetchState = useUnauthTicketStore(
|
|
||||||
(state) => state.unauthTicketMessageFetchState
|
|
||||||
);
|
|
||||||
const chatBoxRef = useRef<HTMLDivElement>(null);
|
const chatBoxRef = useRef<HTMLDivElement>(null);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const user = useUserStore((state) => state.user?._id);
|
||||||
|
const ticket = useTicketStore(
|
||||||
|
(state) => state[user ? "authData" : "unauthData"]
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
messages,
|
||||||
|
sessionData,
|
||||||
|
isMessageSending,
|
||||||
|
isPreventAutoscroll,
|
||||||
|
lastMessageId,
|
||||||
|
messagesPerPage,
|
||||||
|
unauthTicketMessageFetchState: fetchState,
|
||||||
|
apiPage: messageApiPage,
|
||||||
|
} = ticket;
|
||||||
|
|
||||||
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
const { isActiveSSETab, updateSSEValue } = useSSETab<TicketMessage[]>(
|
||||||
"ticket",
|
"ticket",
|
||||||
addOrUpdateUnauthMessages
|
addOrUpdateUnauthMessages
|
||||||
@ -74,8 +101,10 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
messagesPerPage,
|
messagesPerPage,
|
||||||
messageApiPage,
|
messageApiPage,
|
||||||
onSuccess: useCallback((messages) => {
|
onSuccess: useCallback((messages) => {
|
||||||
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
|
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) {
|
||||||
chatBoxRef.current.scrollTop = 1;
|
chatBoxRef.current.scrollTop = 1;
|
||||||
|
}
|
||||||
|
|
||||||
addOrUpdateUnauthMessages(messages);
|
addOrUpdateUnauthMessages(messages);
|
||||||
}, []),
|
}, []),
|
||||||
onError: useCallback((error: Error) => {
|
onError: useCallback((error: Error) => {
|
||||||
@ -92,6 +121,7 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
`/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
`/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
||||||
onNewData: (ticketMessages) => {
|
onNewData: (ticketMessages) => {
|
||||||
updateSSEValue(ticketMessages);
|
updateSSEValue(ticketMessages);
|
||||||
|
|
||||||
addOrUpdateUnauthMessages(ticketMessages);
|
addOrUpdateUnauthMessages(ticketMessages);
|
||||||
},
|
},
|
||||||
onDisconnect: useCallback(() => {
|
onDisconnect: useCallback(() => {
|
||||||
@ -100,6 +130,34 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
marker: "ticket",
|
marker: "ticket",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useTicketsFetcher({
|
||||||
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/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 = getMessageFromFetchError(error);
|
||||||
|
if (message) enqueueSnackbar(message);
|
||||||
|
},
|
||||||
|
onFetchStateChange: () => {},
|
||||||
|
enabled: Boolean(user),
|
||||||
|
});
|
||||||
|
|
||||||
const throttledScrollHandler = useMemo(
|
const throttledScrollHandler = useMemo(
|
||||||
() =>
|
() =>
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
@ -114,14 +172,12 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
if (fetchState !== "idle") return;
|
if (fetchState !== "idle") return;
|
||||||
|
|
||||||
if (chatBox.scrollTop < chatBox.clientHeight) {
|
if (chatBox.scrollTop < chatBox.clientHeight) {
|
||||||
incrementUnauthMessageApiPage();
|
incrementUnauthMessage();
|
||||||
}
|
}
|
||||||
}, 200),
|
}, 200),
|
||||||
[fetchState]
|
[fetchState]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEventListener("scroll", throttledScrollHandler, chatBoxRef);
|
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
function scrollOnNewMessage() {
|
function scrollOnNewMessage() {
|
||||||
if (!chatBoxRef.current) return;
|
if (!chatBoxRef.current) return;
|
||||||
@ -131,7 +187,6 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
},
|
},
|
||||||
[lastMessageId]
|
[lastMessageId]
|
||||||
);
|
);
|
||||||
@ -146,10 +201,18 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
}
|
}
|
||||||
}, [open, messages]);
|
}, [open, messages]);
|
||||||
|
|
||||||
|
const loadNewMessages = (
|
||||||
|
event: WheelEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>
|
||||||
|
) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
throttledScrollHandler();
|
||||||
|
};
|
||||||
|
|
||||||
async function handleSendMessage() {
|
async function handleSendMessage() {
|
||||||
if (!messageField || isMessageSending) return;
|
if (!messageField || isMessageSending) return;
|
||||||
|
|
||||||
if (!sessionData) {
|
if (!sessionData?.ticketId) {
|
||||||
setIsMessageSending(true);
|
setIsMessageSending(true);
|
||||||
createTicket({
|
createTicket({
|
||||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
||||||
@ -157,10 +220,10 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
Title: "Unauth title",
|
Title: "Unauth title",
|
||||||
Message: messageField,
|
Message: messageField,
|
||||||
},
|
},
|
||||||
useToken: false,
|
useToken: Boolean(user),
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setUnauthSessionData({
|
setTicketData({
|
||||||
ticketId: response.Ticket,
|
ticketId: response.Ticket,
|
||||||
sessionId: response.sess,
|
sessionId: response.sess,
|
||||||
});
|
});
|
||||||
@ -210,6 +273,66 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendFile = async (file: File) => {
|
||||||
|
if (file === undefined) return true;
|
||||||
|
|
||||||
|
console.log("тут ошибка", modalWarningType);
|
||||||
|
let data;
|
||||||
|
if (!ticket.sessionData?.ticketId) {
|
||||||
|
try {
|
||||||
|
data = await createTicket({
|
||||||
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
||||||
|
body: {
|
||||||
|
Title: "Unauth title",
|
||||||
|
Message: "",
|
||||||
|
},
|
||||||
|
useToken: Boolean(user),
|
||||||
|
});
|
||||||
|
setTicketData({
|
||||||
|
ticketId: data.Ticket,
|
||||||
|
sessionId: data.sess,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
|
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||||
|
}
|
||||||
|
setIsMessageSending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticketId = ticket.sessionData?.ticketId || data?.Ticket;
|
||||||
|
if (ticketId !== undefined) {
|
||||||
|
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
|
||||||
|
try {
|
||||||
|
const body = new FormData();
|
||||||
|
|
||||||
|
body.append(file.name, file);
|
||||||
|
body.append("ticket", ticketId);
|
||||||
|
await makeRequest({
|
||||||
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
|
||||||
|
body: body,
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
|
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendFileHC = async (file: File) => {
|
||||||
|
console.log(file);
|
||||||
|
const check = checkAcceptableMediaType(file);
|
||||||
|
if (check.length > 0) {
|
||||||
|
enqueueSnackbar(check);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDisableFileButton(true);
|
||||||
|
await sendFile(file);
|
||||||
|
setDisableFileButton(false);
|
||||||
|
console.log(disableFileButton);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{open && (
|
{open && (
|
||||||
@ -217,7 +340,9 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "clamp(250px, calc(100vh - 90px), 600px)",
|
height: isMobile
|
||||||
|
? "100%"
|
||||||
|
: "clamp(250px, calc(100vh - 90px), 600px)",
|
||||||
backgroundColor: "#944FEE",
|
backgroundColor: "#944FEE",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
...sx,
|
...sx,
|
||||||
@ -233,6 +358,11 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
|
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{isMobile && (
|
||||||
|
<IconButton onClick={onclickArrow}>
|
||||||
|
<ArrowLeft color="white" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
<UserCircleIcon />
|
<UserCircleIcon />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -263,6 +393,8 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
onWheel={loadNewMessages}
|
||||||
|
onTouchMove={loadNewMessages}
|
||||||
ref={chatBoxRef}
|
ref={chatBoxRef}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -276,16 +408,87 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sessionData &&
|
{ticket.sessionData?.ticketId &&
|
||||||
messages.map((message) => (
|
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 (
|
||||||
|
<ChatImage
|
||||||
|
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 && 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
|
<ChatMessage
|
||||||
unAuthenticated
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
text={message.message}
|
text={message.message}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={sessionData.sessionId === message.user_id}
|
isSelf={
|
||||||
|
(ticket.sessionData?.sessionId || user) ===
|
||||||
|
message.user_id
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
||||||
<InputBase
|
<InputBase
|
||||||
@ -314,6 +517,26 @@ export default function Chat({ open = false, sx }: Props) {
|
|||||||
onChange={(e) => setMessageField(e.target.value)}
|
onChange={(e) => setMessageField(e.target.value)}
|
||||||
endAdornment={
|
endAdornment={
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
disabled={disableFileButton}
|
||||||
|
onClick={() => {
|
||||||
|
console.log(disableFileButton);
|
||||||
|
if (!disableFileButton) fileInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AttachFileIcon />
|
||||||
|
</IconButton>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="fileinput"
|
||||||
|
onChange={({ target }) => {
|
||||||
|
if (target.files?.[0]) {
|
||||||
|
sendFileHC(target.files?.[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={isMessageSending}
|
disabled={isMessageSending}
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
|
113
src/components/FloatingSupportChat/ChatDocument.tsx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import { isDateToday } from "../../utils/date";
|
||||||
|
import Download from "@root/assets/Icons/download";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
unAuthenticated?: boolean;
|
||||||
|
isSelf: boolean;
|
||||||
|
file: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatDocument({
|
||||||
|
unAuthenticated = false,
|
||||||
|
isSelf,
|
||||||
|
file,
|
||||||
|
createdAt,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
|
||||||
|
const messageBackgroundColor = isSelf
|
||||||
|
? "white"
|
||||||
|
: unAuthenticated
|
||||||
|
? "#EFF0F5"
|
||||||
|
: "#434657";
|
||||||
|
|
||||||
|
const date = new Date(createdAt);
|
||||||
|
const today = isDateToday(date);
|
||||||
|
const time = date.toLocaleString([], {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
...(!today && {
|
||||||
|
year: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
alignSelf: "end",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "14px",
|
||||||
|
lineHeight: "17px",
|
||||||
|
order: isSelf ? 1 : 2,
|
||||||
|
margin: isSelf ? "0 0 0 auto" : "0 auto 0 0",
|
||||||
|
color: "#434657",
|
||||||
|
mb: "-4px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{time}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: messageBackgroundColor,
|
||||||
|
border: unAuthenticated
|
||||||
|
? "1px solid #E3E3E3"
|
||||||
|
: `1px solid ${"#434657"}`,
|
||||||
|
order: isSelf ? 2 : 1,
|
||||||
|
p: upMd ? "18px" : "12px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
color: isSelf || unAuthenticated ? "#434657" : "white",
|
||||||
|
position: "relative",
|
||||||
|
maxWidth: `calc(100% - ${today ? 45 : 110}px)`,
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-1px",
|
||||||
|
right: isSelf ? "-8px" : undefined,
|
||||||
|
left: isSelf ? undefined : "-8px",
|
||||||
|
transform: isSelf ? undefined : "scale(-1, 1)",
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 16 8"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0.5 0.5L15.5 0.500007
|
||||||
|
C10 0.500006 7.5 8 7.5 7.5H7.5H0.5V0.5Z"
|
||||||
|
fill={messageBackgroundColor}
|
||||||
|
stroke={unAuthenticated ? "#E3E3E3" : theme.palette.gray.main}
|
||||||
|
/>
|
||||||
|
<rect y="1" width="8" height="8" fill={messageBackgroundColor} />
|
||||||
|
</svg>
|
||||||
|
<Link
|
||||||
|
download=""
|
||||||
|
href={`https://storage.yandexcloud.net/pair/${file}`}
|
||||||
|
style={{
|
||||||
|
color: "#7E2AEA",
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download color={theme.palette.purple.main} />
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
119
src/components/FloatingSupportChat/ChatImage.tsx
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ButtonBase,
|
||||||
|
Link,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { isDateToday } from "../../utils/date";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
unAuthenticated?: boolean;
|
||||||
|
isSelf: boolean;
|
||||||
|
file: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatImage({
|
||||||
|
unAuthenticated = false,
|
||||||
|
isSelf,
|
||||||
|
file,
|
||||||
|
createdAt,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const messageBackgroundColor = isSelf
|
||||||
|
? "white"
|
||||||
|
: unAuthenticated
|
||||||
|
? "#EFF0F5"
|
||||||
|
: "#434657";
|
||||||
|
|
||||||
|
const date = new Date(createdAt);
|
||||||
|
const today = isDateToday(date);
|
||||||
|
const time = date.toLocaleString([], {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
...(!today && {
|
||||||
|
year: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
alignSelf: "end",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "14px",
|
||||||
|
lineHeight: "17px",
|
||||||
|
order: isSelf ? 1 : 2,
|
||||||
|
margin: isSelf ? "0 0 0 auto" : "0 auto 0 0",
|
||||||
|
color: "#434657",
|
||||||
|
mb: "-4px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{time}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: messageBackgroundColor,
|
||||||
|
border: unAuthenticated
|
||||||
|
? "1px solid #E3E3E3"
|
||||||
|
: `1px solid ${"#434657"}`,
|
||||||
|
order: isSelf ? 2 : 1,
|
||||||
|
p: upMd ? "18px" : "12px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
color: isSelf || unAuthenticated ? "#434657" : "white",
|
||||||
|
position: "relative",
|
||||||
|
maxWidth: `calc(100% - ${today ? 45 : 110}px)`,
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-1px",
|
||||||
|
right: isSelf ? "-8px" : undefined,
|
||||||
|
left: isSelf ? undefined : "-8px",
|
||||||
|
transform: isSelf ? undefined : "scale(-1, 1)",
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 16 8"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0.5 0.5L15.5 0.500007
|
||||||
|
C10 0.500006 7.5 8 7.5 7.5H7.5H0.5V0.5Z"
|
||||||
|
fill={messageBackgroundColor}
|
||||||
|
stroke={unAuthenticated ? "#E3E3E3" : "#434657"}
|
||||||
|
/>
|
||||||
|
<rect y="1" width="8" height="8" fill={messageBackgroundColor} />
|
||||||
|
</svg>
|
||||||
|
<ButtonBase target="_blank" href={`/image/${file}`}>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
sx={{
|
||||||
|
height: "217px",
|
||||||
|
width: "217px",
|
||||||
|
}}
|
||||||
|
src={`https://storage.yandexcloud.net/pair/${file}`}
|
||||||
|
/>
|
||||||
|
</ButtonBase>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
125
src/components/FloatingSupportChat/ChatVideo.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ButtonBase,
|
||||||
|
Link,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { isDateToday } from "../../utils/date";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
unAuthenticated?: boolean;
|
||||||
|
isSelf: boolean;
|
||||||
|
file: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatImage({
|
||||||
|
unAuthenticated = false,
|
||||||
|
isSelf,
|
||||||
|
file,
|
||||||
|
createdAt,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
const navigate = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
() => console.log("delete");
|
||||||
|
});
|
||||||
|
const messageBackgroundColor = isSelf
|
||||||
|
? "white"
|
||||||
|
: unAuthenticated
|
||||||
|
? "#EFF0F5"
|
||||||
|
: "#434657";
|
||||||
|
|
||||||
|
const date = new Date(createdAt);
|
||||||
|
const today = isDateToday(date);
|
||||||
|
const time = date.toLocaleString([], {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
...(!today && {
|
||||||
|
year: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
alignSelf: "end",
|
||||||
|
fontWeight: 400,
|
||||||
|
fontSize: "14px",
|
||||||
|
lineHeight: "17px",
|
||||||
|
order: isSelf ? 1 : 2,
|
||||||
|
margin: isSelf ? "0 0 0 auto" : "0 auto 0 0",
|
||||||
|
color: "#434657",
|
||||||
|
mb: "-4px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{time}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: messageBackgroundColor,
|
||||||
|
border: unAuthenticated
|
||||||
|
? "1px solid #E3E3E3"
|
||||||
|
: `1px solid ${"#434657"}`,
|
||||||
|
order: isSelf ? 2 : 1,
|
||||||
|
p: upMd ? "18px" : "12px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
color: isSelf || unAuthenticated ? "#434657" : "white",
|
||||||
|
position: "relative",
|
||||||
|
maxWidth: `calc(100% - ${today ? 45 : 110}px)`,
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "-1px",
|
||||||
|
right: isSelf ? "-8px" : undefined,
|
||||||
|
left: isSelf ? undefined : "-8px",
|
||||||
|
transform: isSelf ? undefined : "scale(-1, 1)",
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="8"
|
||||||
|
viewBox="0 0 16 8"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0.5 0.5L15.5 0.500007
|
||||||
|
C10 0.500006 7.5 8 7.5 7.5H7.5H0.5V0.5Z"
|
||||||
|
fill={messageBackgroundColor}
|
||||||
|
stroke={unAuthenticated ? "#E3E3E3" : "#434657"}
|
||||||
|
/>
|
||||||
|
<rect y="1" width="8" height="8" fill={messageBackgroundColor} />
|
||||||
|
</svg>
|
||||||
|
<Box
|
||||||
|
component="video"
|
||||||
|
sx={{
|
||||||
|
pointerEvents: "auto",
|
||||||
|
height: "217px",
|
||||||
|
width: "auto",
|
||||||
|
minWidth: "217px",
|
||||||
|
}}
|
||||||
|
controls
|
||||||
|
>
|
||||||
|
<source src={`https://storage.yandexcloud.net/pair/${file}`} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -1,15 +1,51 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect, forwardRef } from "react";
|
||||||
import { Box, Fab, Typography, Badge, useTheme } from "@mui/material";
|
import {
|
||||||
|
Box,
|
||||||
|
Fab,
|
||||||
|
Typography,
|
||||||
|
Badge,
|
||||||
|
Dialog,
|
||||||
|
Slide,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
import CircleDoubleDown from "./CircleDoubleDownIcon";
|
import CircleDoubleDown from "./CircleDoubleDownIcon";
|
||||||
import Chat from "./Chat";
|
import Chat from "./Chat";
|
||||||
|
|
||||||
import { useUnauthTicketStore } from "@root/stores/unauthTicket";
|
import { useUserStore } from "@root/stores/user";
|
||||||
|
import { useTicketStore } from "@root/stores/tickets";
|
||||||
|
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { TransitionProps } from "@mui/material/transitions";
|
||||||
|
|
||||||
|
const Transition = forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: ReactNode;
|
||||||
|
},
|
||||||
|
ref: React.Ref<unknown>
|
||||||
|
) {
|
||||||
|
console.log(props.children);
|
||||||
|
return (
|
||||||
|
<Slide direction="up" ref={ref} {...props}>
|
||||||
|
{props.children ? (
|
||||||
|
<Box sx={{ height: "100%" }}>{props.children}</Box>
|
||||||
|
) : (
|
||||||
|
<Box />
|
||||||
|
)}
|
||||||
|
</Slide>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default function FloatingSupportChat() {
|
export default function FloatingSupportChat() {
|
||||||
|
const [monitorType, setMonitorType] = useState<"desktop" | "mobile" | "">("");
|
||||||
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
|
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { messages } = useUnauthTicketStore((state) => state);
|
const isMobile = useMediaQuery(theme.breakpoints.down(800));
|
||||||
|
const user = useUserStore((state) => state.user?._id);
|
||||||
|
const { messages } = useTicketStore(
|
||||||
|
(state) => state[user ? "authData" : "unauthData"]
|
||||||
|
);
|
||||||
|
|
||||||
const animation = {
|
const animation = {
|
||||||
"@keyframes runningStripe": {
|
"@keyframes runningStripe": {
|
||||||
@ -35,6 +71,25 @@ export default function FloatingSupportChat() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onResize = () => {
|
||||||
|
if (document.fullscreenElement) {
|
||||||
|
setMonitorType(isMobile ? "mobile" : "desktop");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonitorType("");
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", onResize);
|
||||||
|
};
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -49,10 +104,20 @@ export default function FloatingSupportChat() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Chat
|
<Chat
|
||||||
open={isChatOpened}
|
open={isChatOpened && (monitorType === "desktop" || !isMobile)}
|
||||||
sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }}
|
sx={{ alignSelf: "start", width: "clamp(200px, 100%, 400px)" }}
|
||||||
/>
|
/>
|
||||||
|
<Dialog
|
||||||
|
fullScreen
|
||||||
|
open={isChatOpened && (monitorType === "mobile" || isMobile)}
|
||||||
|
onClose={() => setIsChatOpened(false)}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
>
|
||||||
|
<Chat
|
||||||
|
open={isChatOpened && (monitorType === "mobile" || isMobile)}
|
||||||
|
onclickArrow={() => setIsChatOpened(false)}
|
||||||
|
/>
|
||||||
|
</Dialog>
|
||||||
<Fab
|
<Fab
|
||||||
disableRipple
|
disableRipple
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -228,7 +228,7 @@ export default function DialogMenu({ open, handleClose }: DialogMenuProps) {
|
|||||||
variant="pena-contained-dark"
|
variant="pena-contained-dark"
|
||||||
sx={{ px: "30px", ml: "40px", width: "245px", mt: "50px" }}
|
sx={{ px: "30px", ml: "40px", width: "245px", mt: "50px" }}
|
||||||
>
|
>
|
||||||
Регистрация / Войти
|
{user ? "Личный кабинет" : "Регистрация / Войти"}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Box
|
<Box
|
||||||
|
@ -21,7 +21,7 @@ export default function NavbarCollapsed({ isLoggedIn }: Props) {
|
|||||||
component="nav"
|
component="nav"
|
||||||
maxWidth="lg"
|
maxWidth="lg"
|
||||||
outerContainerSx={{
|
outerContainerSx={{
|
||||||
zIndex: "111111111111",
|
zIndex: "999",
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: "0",
|
top: "0",
|
||||||
backgroundColor: theme.palette.bg.main,
|
backgroundColor: theme.palette.bg.main,
|
||||||
|
@ -7,6 +7,7 @@ import Wallet from "./pages/Wallet"
|
|||||||
import Payment from "./pages/Payment/Payment"
|
import Payment from "./pages/Payment/Payment"
|
||||||
import QuizPayment from "./pages/QuizPayment/QuizPayment"
|
import QuizPayment from "./pages/QuizPayment/QuizPayment"
|
||||||
import Support from "./pages/Support/Support"
|
import Support from "./pages/Support/Support"
|
||||||
|
import ChatImageNewWindow from "./pages/Support/ChatImageNewWindow"
|
||||||
import AccountSettings from "./pages/AccountSettings/AccountSettings"
|
import AccountSettings from "./pages/AccountSettings/AccountSettings"
|
||||||
import Landing from "./pages/Landing/Landing"
|
import Landing from "./pages/Landing/Landing"
|
||||||
import Tariffs from "./pages/Tariffs/Tariffs"
|
import Tariffs from "./pages/Tariffs/Tariffs"
|
||||||
@ -98,6 +99,7 @@ const App = () => {
|
|||||||
<Route path="/changepwd" element={<Navigate to="/" replace state={{ redirectTo: window.location.pathname + window.location.search }} />} />
|
<Route path="/changepwd" element={<Navigate to="/" replace state={{ redirectTo: window.location.pathname + window.location.search }} />} />
|
||||||
<Route path="/changepwd/expired" element={<Navigate to="/" replace state={{ redirectTo: "/changepwd/expired" }} />} />
|
<Route path="/changepwd/expired" element={<Navigate to="/" replace state={{ redirectTo: "/changepwd/expired" }} />} />
|
||||||
|
|
||||||
|
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
<Route element={<ProtectedLayout />}>
|
<Route element={<ProtectedLayout />}>
|
||||||
<Route path="/tariffs" element={<Tariffs />} />
|
<Route path="/tariffs" element={<Tariffs />} />
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export interface SendPaymentRequest {
|
export interface SendPaymentRequest {
|
||||||
type: "bankCard";
|
type: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
bankCard: {
|
bankCard: {
|
||||||
|
@ -37,6 +37,7 @@ export default function AccordionWrapper({ content, last, first, createdAt, onCl
|
|||||||
content[0].Value[0].forEach((item) => {
|
content[0].Value[0].forEach((item) => {
|
||||||
valuesByKey[item.Key] = item.Value
|
valuesByKey[item.Key] = item.Value
|
||||||
})
|
})
|
||||||
|
console.log("Я врапер")
|
||||||
console.log(content)
|
console.log(content)
|
||||||
console.log(content[0])
|
console.log(content[0])
|
||||||
console.log(content[0].Value)
|
console.log(content[0].Value)
|
||||||
|
233
src/pages/History/AccordionWrapper2.tsx
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import CustomAccordion from "@components/CustomAccordion";
|
||||||
|
import File from "@components/icons/File";
|
||||||
|
import { getDeclension } from "@utils/declension";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
import { addTariffToCart } from "@root/stores/user";
|
||||||
|
import { Tariff } from "@frontend/kitui";
|
||||||
|
import { currencyFormatter } from "@root/utils/currencyFormatter";
|
||||||
|
|
||||||
|
export type History = {
|
||||||
|
title: string;
|
||||||
|
date: string;
|
||||||
|
info: string;
|
||||||
|
description: string;
|
||||||
|
payMethod?: string;
|
||||||
|
expired?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AccordionWrapperProps {
|
||||||
|
tariff: Tariff;
|
||||||
|
price: number;
|
||||||
|
last?: boolean;
|
||||||
|
first?: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AccordionWrapper2({ tariff, price, last, first, createdAt }: AccordionWrapperProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(560));
|
||||||
|
|
||||||
|
async function handleTariffItemClick(tariffId: string) {
|
||||||
|
const { patchCartError } = await addTariffToCart(tariffId);
|
||||||
|
if (patchCartError) {
|
||||||
|
enqueueSnackbar(patchCartError);
|
||||||
|
} else {
|
||||||
|
enqueueSnackbar("Тариф добавлен в корзину");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
borderRadius: "12px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CustomAccordion
|
||||||
|
last={last}
|
||||||
|
first={first}
|
||||||
|
divide
|
||||||
|
text={tariff.privileges.map(privilege => (
|
||||||
|
`${privilege.description} - ${privilege.serviceKey} ${getDeclension(Number(privilege.serviceKey), privilege.value)}`
|
||||||
|
))}
|
||||||
|
header={
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: upMd ? "72px" : undefined,
|
||||||
|
padding: "20px 20px 20px 0",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
gap: "20px",
|
||||||
|
alignItems: upSm ? "center" : undefined,
|
||||||
|
flexDirection: upSm ? undefined : "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: upSm ? "center" : undefined,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: upSm ? undefined : "column",
|
||||||
|
gap: upMd ? "51px" : "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
width: "110px",
|
||||||
|
fontSize: upMd ? "20px" : "18px",
|
||||||
|
lineHeight: upMd ? undefined : "19px",
|
||||||
|
fontWeight: 500,
|
||||||
|
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.text.secondary,
|
||||||
|
px: 0,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createdAt}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
title={tariff.isCustom ? "Мой тариф" : tariff.name}
|
||||||
|
sx={{
|
||||||
|
fontSize: upMd ? "18px" : "16px",
|
||||||
|
lineHeight: upMd ? undefined : "19px",
|
||||||
|
fontWeight: 500,
|
||||||
|
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.gray.dark,
|
||||||
|
px: 0,
|
||||||
|
width: "200px",
|
||||||
|
maxWidth: "200px",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tariff.isCustom ? "Мой тариф" : tariff.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
flexFlow: "1",
|
||||||
|
flexBasis: "60%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" width="100%" justifyContent="space-between">
|
||||||
|
{/* <Typography
|
||||||
|
sx={{
|
||||||
|
display: upMd ? undefined : "none",
|
||||||
|
fontSize: upMd ? "18px" : "16px",
|
||||||
|
lineHeight: upMd ? undefined : "19px",
|
||||||
|
fontWeight: 400,
|
||||||
|
color: valuesByKey.expired ? theme.palette.text.disabled : theme.palette.gray.dark,
|
||||||
|
px: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{valuesByKey.payMethod && <Typography
|
||||||
|
sx={{
|
||||||
|
maxWidth: "300px",
|
||||||
|
width: "300px",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}
|
||||||
|
>Способ оплаты: {valuesByKey.payMethod}</Typography>}
|
||||||
|
</Typography> */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
height: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: upSm ? "111px" : "17px",
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: isTablet ? null : "160px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
marginLeft: isTablet ? (isMobile ? null : "auto") : null,
|
||||||
|
color: /* valuesByKey.expired */ false ? theme.palette.text.disabled : theme.palette.gray.dark,
|
||||||
|
fontSize: upSm ? "20px" : "16px",
|
||||||
|
fontWeight: 500,
|
||||||
|
textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currencyFormatter.format(price)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{!isMobile &&
|
||||||
|
<>
|
||||||
|
{/* <IconButton onClick={onClickMail}>
|
||||||
|
<EmailIcon fontSize={"large"}/>
|
||||||
|
</IconButton> */}
|
||||||
|
<IconButton
|
||||||
|
title="Добавить в корзину тариф"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleTariffItemClick(tariff._id);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
ml: "20px",
|
||||||
|
bgcolor: "#EEE4FC",
|
||||||
|
stroke: "#7E2AEA",
|
||||||
|
borderRadius: 2,
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "#7E2AEA",
|
||||||
|
stroke: "white",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
bgcolor: "black",
|
||||||
|
stroke: "white",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<File></File>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
{isMobile &&
|
||||||
|
<>
|
||||||
|
{/* <IconButton onClick={onClickMail}>
|
||||||
|
<EmailIcon fontSize={"large"}/>
|
||||||
|
</IconButton> */}
|
||||||
|
<IconButton
|
||||||
|
title="Добавить в корзину тариф"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleTariffItemClick(tariff._id);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
mr: "10px",
|
||||||
|
bgcolor: "#EEE4FC",
|
||||||
|
stroke: "#7E2AEA",
|
||||||
|
borderRadius: 2,
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "#7E2AEA",
|
||||||
|
stroke: "white",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
bgcolor: "black",
|
||||||
|
stroke: "white",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<File></File>
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -1,53 +1,53 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react";
|
||||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"
|
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack"
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
|
|
||||||
import SectionWrapper from "@root/components/SectionWrapper"
|
import SectionWrapper from "@root/components/SectionWrapper";
|
||||||
import { Select } from "@root/components/Select"
|
import { Select } from "@root/components/Select";
|
||||||
import { Tabs } from "@root/components/Tabs"
|
import { Tabs } from "@root/components/Tabs";
|
||||||
|
|
||||||
import AccordionWrapper from "./AccordionWrapper"
|
import AccordionWrapper from "./AccordionWrapper";
|
||||||
import { HISTORY } from "./historyMocks"
|
import { HISTORY } from "./historyMocks";
|
||||||
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker"
|
import { useHistoryTracker } from "@root/utils/hooks/useHistoryTracker";
|
||||||
import { useHistoryData } from "@root/utils/hooks/useHistoryData"
|
import { useHistoryData } from "@root/utils/hooks/useHistoryData";
|
||||||
import { isArray } from "cypress/types/lodash"
|
import { isArray } from "cypress/types/lodash";
|
||||||
import { ErrorBoundary } from "react-error-boundary"
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import { handleComponentError } from "@root/utils/handleComponentError"
|
import { handleComponentError } from "@root/utils/handleComponentError";
|
||||||
import { useHistoryStore } from "@root/stores/history";
|
import { useHistoryStore } from "@root/stores/history";
|
||||||
import EmailIcon from '@mui/icons-material/Email';
|
import { enqueueSnackbar } from "notistack";
|
||||||
import {enqueueSnackbar} from "notistack"
|
import { makeRequest } from "@frontend/kitui";
|
||||||
import { makeRequest } from "@frontend/kitui"
|
import { HistoryRecord, HistoryRecord2 } from "@root/api/history";
|
||||||
|
import AccordionWrapper2 from "./AccordionWrapper2";
|
||||||
|
|
||||||
const subPages = ["Платежи"]
|
const subPages = ["Платежи"];
|
||||||
// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
// const subPages = ["Платежи", "Покупки тарифов", "Окончания тарифов"]
|
||||||
|
|
||||||
export default function History() {
|
export default function History() {
|
||||||
const [selectedItem, setSelectedItem] = useState<number>(0)
|
const [selectedItem, setSelectedItem] = useState<number>(0);
|
||||||
|
|
||||||
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(600))
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000))
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const historyData = useHistoryStore(state => state.history)
|
const historyData = useHistoryStore(state => state.history);
|
||||||
|
const handleCustomBackNavigation = useHistoryTracker();
|
||||||
const handleCustomBackNavigation = useHistoryTracker()
|
|
||||||
|
|
||||||
const extractDateFromString = (tariffName: string) => {
|
const extractDateFromString = (tariffName: string) => {
|
||||||
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/)
|
const dateMatch = tariffName.match(/\d{4}-\d{2}-\d{2}/);
|
||||||
return dateMatch ? dateMatch[0] : ""
|
return dateMatch ? dateMatch[0] : "";
|
||||||
}
|
};
|
||||||
|
|
||||||
async function handleHistoryResponse(tariffId: string) {
|
async function handleHistoryResponse(tariffId: string) {
|
||||||
try {
|
try {
|
||||||
await makeRequest (
|
await makeRequest(
|
||||||
{
|
{
|
||||||
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport/${tariffId}`,
|
url: process.env.REACT_APP_DOMAIN + `/customer/sendReport/${tariffId}`,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
enqueueSnackbar("Запрос отправлен")
|
enqueueSnackbar("Запрос отправлен");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
enqueueSnackbar("извините, произошла ошибка")
|
enqueueSnackbar("извините, произошла ошибка");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,28 +94,57 @@ export default function History() {
|
|||||||
}
|
}
|
||||||
onError={handleComponentError}
|
onError={handleComponentError}
|
||||||
>
|
>
|
||||||
{historyData?.length === 0 && <Typography textAlign="center" >Нет данных</Typography>}
|
{historyData?.length === 0 && <Typography textAlign="center">Нет данных</Typography>}
|
||||||
{historyData?.filter((e) => {
|
{/* Для ненормального rawDetails */}
|
||||||
e.createdAt = extractDateFromString(e.createdAt)
|
{historyData?.filter((e): e is HistoryRecord => {
|
||||||
return(!e.isDeleted && e.key === "payCart" && Array.isArray(e.rawDetails[0].Value)
|
e.createdAt = extractDateFromString(e.createdAt);
|
||||||
)})
|
return (
|
||||||
.map(( e, index) => {
|
!e.isDeleted
|
||||||
|
&& e.key === "payCart"
|
||||||
|
&& Array.isArray(e.rawDetails)
|
||||||
|
&& Array.isArray(e.rawDetails[0].Value)
|
||||||
|
);
|
||||||
|
}).map((e, index) => {
|
||||||
return (
|
return (
|
||||||
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
||||||
<AccordionWrapper
|
<AccordionWrapper
|
||||||
first={index === 0}
|
first={index === 0}
|
||||||
last={index === historyData?.length - 1}
|
last={index === historyData?.length - 1}
|
||||||
content={e.rawDetails}
|
content={(e as HistoryRecord).rawDetails}
|
||||||
key={e.id}
|
key={e.id}
|
||||||
createdAt={e.createdAt}
|
createdAt={e.createdAt}
|
||||||
onClickMail={(event: any)=>{
|
onClickMail={(event: any) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation();
|
||||||
handleHistoryResponse(e.id)
|
handleHistoryResponse(e.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)})}
|
);
|
||||||
|
})}
|
||||||
|
{/* Для нормального rawDetails */}
|
||||||
|
{historyData?.filter((e): e is HistoryRecord2 => {
|
||||||
|
e.createdAt = extractDateFromString(e.createdAt);
|
||||||
|
return (
|
||||||
|
!e.isDeleted
|
||||||
|
&& e.key === "payCart"
|
||||||
|
&& !Array.isArray(e.rawDetails)
|
||||||
|
&& !!e.rawDetails.tariffs[0]
|
||||||
|
);
|
||||||
|
}).map((e, index) => {
|
||||||
|
return (
|
||||||
|
<Box key={index} sx={{ mt: index === 0 ? "27px" : "0px" }}>
|
||||||
|
<AccordionWrapper2
|
||||||
|
key={e.id}
|
||||||
|
first={index === 0}
|
||||||
|
last={index === historyData?.length - 1}
|
||||||
|
createdAt={e.createdAt}
|
||||||
|
tariff={e.rawDetails.tariffs[0]}
|
||||||
|
price={e.rawDetails.price}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ import {
|
|||||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||||
import SectionWrapper from "@components/SectionWrapper";
|
import SectionWrapper from "@components/SectionWrapper";
|
||||||
import PaymentMethodCard from "./PaymentMethodCard";
|
import PaymentMethodCard from "./PaymentMethodCard";
|
||||||
import mastercardLogo from "@root/assets/bank-logo/logo-mastercard.png";
|
import umoneyLogo from "@root/assets/bank-logo/umaney.png";
|
||||||
import visaLogo from "@root/assets/bank-logo/logo-visa.png";
|
import b2bLogo from "@root/assets/bank-logo/b2b.png";
|
||||||
import qiwiLogo from "@root/assets/bank-logo/logo-qiwi.png";
|
import spbLogo from "@root/assets/bank-logo/spb.png";
|
||||||
import mirLogo from "@root/assets/bank-logo/logo-mir.png";
|
import sberpayLogo from "@root/assets/bank-logo/sberpay.png";
|
||||||
import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png";
|
import tinkoffLogo from "@root/assets/bank-logo/logo-tinkoff.png";
|
||||||
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
import rsPayLogo from "@root/assets/bank-logo/rs-pay.png";
|
||||||
import { cardShadow } from "@root/utils/theme";
|
import { cardShadow } from "@root/utils/theme";
|
||||||
@ -37,11 +37,11 @@ type PaymentMethod = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const paymentMethods: PaymentMethod[] = [
|
const paymentMethods: PaymentMethod[] = [
|
||||||
{ label: "Mastercard", name: "mastercard", image: mastercardLogo },
|
{ label: "Тинькофф", name: "tinkoffBank", image: tinkoffLogo },
|
||||||
{ label: "Visa", name: "visa", image: visaLogo },
|
{ label: "СБП", name: "sbp", image: spbLogo },
|
||||||
{ label: "QIWI Кошелек", name: "qiwi", image: qiwiLogo },
|
{ label: "SberPay", name: "sberbank", image: sberpayLogo },
|
||||||
{ label: "Мир", name: "mir", image: mirLogo },
|
{ label: "B2B Сбербанк", name: "b2bSberbank", image: b2bLogo },
|
||||||
{ label: "Тинькофф", name: "tinkoff", image: tinkoffLogo },
|
{ label: "ЮMoney", name: "yoomoney", image: umoneyLogo },
|
||||||
];
|
];
|
||||||
|
|
||||||
type PaymentMethodType = (typeof paymentMethods)[number]["name"];
|
type PaymentMethodType = (typeof paymentMethods)[number]["name"];
|
||||||
@ -53,7 +53,7 @@ export default function Payment() {
|
|||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
const [selectedPaymentMethod, setSelectedPaymentMethod] =
|
||||||
useState<PaymentMethodType | null>("rspay");
|
useState<PaymentMethodType | null>("");
|
||||||
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
|
||||||
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
|
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
|
||||||
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
const [paymentValueField, setPaymentValueField] = useState<string>("0");
|
||||||
@ -90,14 +90,56 @@ export default function Payment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Number(paymentValueField) === 0) {
|
if (Number(paymentValueField) === 0) {
|
||||||
|
enqueueSnackbar("Введите сумму")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedPaymentMethod !== "rspay") {
|
if (selectedPaymentMethod !== "rspay") {
|
||||||
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
|
||||||
fromSquiz,
|
fromSquiz,
|
||||||
|
body: {
|
||||||
|
type: selectedPaymentMethod,
|
||||||
|
amount: Number(paymentValueField) * 100,
|
||||||
|
currency: "RUB",
|
||||||
|
bankCard: {
|
||||||
|
number: "RUB",
|
||||||
|
expiryYear: "2021",
|
||||||
|
expiryMonth: "05",
|
||||||
|
csc: "05",
|
||||||
|
cardholder: "IVAN IVANOV",
|
||||||
|
},
|
||||||
|
phoneNumber: "79000000000",
|
||||||
|
login: "login_test",
|
||||||
|
returnUrl: window.location.origin + "/wallet",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (selectedPaymentMethod === "rspay") {
|
||||||
|
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
|
setWarnModalOpen(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(paymentValueField)
|
||||||
|
if (Number(paymentValueField) < 900){
|
||||||
|
enqueueSnackbar("Минимальная сумма 900р")
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
|
||||||
|
|
||||||
|
if (sendRSPaymentError) {
|
||||||
|
return enqueueSnackbar(sendRSPaymentError);
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar(
|
||||||
|
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
||||||
|
);
|
||||||
|
|
||||||
|
navigate("/settings");
|
||||||
|
}
|
||||||
|
|
||||||
if (sendPaymentError) {
|
if (sendPaymentError) {
|
||||||
return enqueueSnackbar(sendPaymentError);
|
return enqueueSnackbar(sendPaymentError);
|
||||||
}
|
}
|
||||||
@ -107,6 +149,31 @@ export default function Payment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
||||||
|
setWarnModalOpen(true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(paymentValueField)
|
||||||
|
if (Number(paymentValueField) < 900){
|
||||||
|
enqueueSnackbar("Минимальная сумма 900р")
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
|
||||||
|
|
||||||
|
if (sendRSPaymentError) {
|
||||||
|
return enqueueSnackbar(sendRSPaymentError);
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar(
|
||||||
|
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
||||||
|
);
|
||||||
|
|
||||||
|
navigate("/settings");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -167,46 +234,22 @@ export default function Payment() {
|
|||||||
>
|
>
|
||||||
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
|
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
|
||||||
<PaymentMethodCard
|
<PaymentMethodCard
|
||||||
isSelected={false}
|
isSelected={selectedPaymentMethod === name}
|
||||||
key={name}
|
key={name}
|
||||||
label={label}
|
label={label}
|
||||||
image={image}
|
image={image}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSorryModalOpen(true)
|
setSelectedPaymentMethod(name)
|
||||||
// setSelectedPaymentMethod(name)
|
|
||||||
}}
|
}}
|
||||||
unpopular={true}
|
unpopular={false}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<PaymentMethodCard
|
<PaymentMethodCard
|
||||||
isSelected={false}
|
isSelected={selectedPaymentMethod === "rspay"}
|
||||||
label={"Расчётный счёт"}
|
label={"Расчётный счёт"}
|
||||||
image={rsPayLogo}
|
image={rsPayLogo}
|
||||||
onClick={async() => {
|
onClick={async() => {
|
||||||
|
setSelectedPaymentMethod("rspay")
|
||||||
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
|
||||||
setWarnModalOpen(true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(paymentValueField)
|
|
||||||
if (Number(paymentValueField) < 900){
|
|
||||||
enqueueSnackbar("Минимальная сумма 900р")
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
|
|
||||||
|
|
||||||
if (sendRSPaymentError) {
|
|
||||||
return enqueueSnackbar(sendRSPaymentError);
|
|
||||||
}
|
|
||||||
|
|
||||||
enqueueSnackbar(
|
|
||||||
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
|
||||||
);
|
|
||||||
|
|
||||||
navigate("/settings");
|
|
||||||
}}
|
}}
|
||||||
unpopular={false}
|
unpopular={false}
|
||||||
/>
|
/>
|
||||||
@ -262,49 +305,7 @@ export default function Payment() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
{paymentLink ? (
|
||||||
variant="pena-outlined-light"
|
|
||||||
onClick={async () => {
|
|
||||||
|
|
||||||
|
|
||||||
if (verificationStatus !== VerificationStatus.VERIFICATED) {
|
|
||||||
setWarnModalOpen(true);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(paymentValueField)
|
|
||||||
if (Number(paymentValueField) < 900){
|
|
||||||
enqueueSnackbar("Минимальная сумма 900р")
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
|
|
||||||
|
|
||||||
if (sendRSPaymentError) {
|
|
||||||
return enqueueSnackbar(sendRSPaymentError);
|
|
||||||
}
|
|
||||||
|
|
||||||
enqueueSnackbar(
|
|
||||||
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
|
|
||||||
);
|
|
||||||
|
|
||||||
navigate("/settings");
|
|
||||||
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
mt: "auto",
|
|
||||||
color: "black",
|
|
||||||
border: `1px solid ${theme.palette.purple.main}`,
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: theme.palette.purple.dark,
|
|
||||||
border: `1px solid ${theme.palette.purple.dark}`,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Оплатить
|
|
||||||
</Button>
|
|
||||||
{/* {paymentLink ? (
|
|
||||||
<Button
|
<Button
|
||||||
variant="pena-outlined-light"
|
variant="pena-outlined-light"
|
||||||
component="a"
|
component="a"
|
||||||
@ -340,7 +341,7 @@ export default function Payment() {
|
|||||||
>
|
>
|
||||||
Выбрать
|
Выбрать
|
||||||
</Button>
|
</Button>
|
||||||
)} */}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
|
||||||
|
20
src/pages/Support/ChatImageNewWindow.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ChatImageNewWindow() {
|
||||||
|
const location = useLocation();
|
||||||
|
console.log(location);
|
||||||
|
const srcImage = location.pathname.split("image/")[1];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
sx={{
|
||||||
|
maxHeight: "100vh",
|
||||||
|
maxWidth: "100vw",
|
||||||
|
}}
|
||||||
|
src={`https://storage.yandexcloud.net/pair/${srcImage}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -14,7 +14,7 @@ import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import SendIcon from "@components/icons/SendIcon";
|
import SendIcon from "@components/icons/SendIcon";
|
||||||
import { throttle, useToken } from "@frontend/kitui";
|
import { makeRequest, throttle, useToken } from "@frontend/kitui";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useTicketStore } from "@root/stores/tickets";
|
import { useTicketStore } from "@root/stores/tickets";
|
||||||
import {
|
import {
|
||||||
@ -38,8 +38,18 @@ import { shownMessage, sendTicketMessage } from "@root/api/ticket";
|
|||||||
import { withErrorBoundary } from "react-error-boundary";
|
import { withErrorBoundary } from "react-error-boundary";
|
||||||
import { handleComponentError } from "@root/utils/handleComponentError";
|
import { handleComponentError } from "@root/utils/handleComponentError";
|
||||||
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
import { useSSETab } from "@root/utils/hooks/useSSETab";
|
||||||
|
import {
|
||||||
|
checkAcceptableMediaType,
|
||||||
|
MAX_FILE_SIZE,
|
||||||
|
ACCEPT_SEND_MEDIA_TYPES_MAP,
|
||||||
|
} from "@utils/checkAcceptableMediaType";
|
||||||
|
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
|
import ChatDocument from "@components/FloatingSupportChat/ChatDocument";
|
||||||
|
import ChatImage from "@components/FloatingSupportChat/ChatImage";
|
||||||
|
import ChatVideo from "@components/FloatingSupportChat/ChatVideo";
|
||||||
|
|
||||||
function SupportChat() {
|
function SupportChat() {
|
||||||
|
console.log("сапортчат отрисовался");
|
||||||
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.up(460));
|
const isMobile = useMediaQuery(theme.breakpoints.up(460));
|
||||||
@ -52,6 +62,8 @@ function SupportChat() {
|
|||||||
const isPreventAutoscroll = useMessageStore(
|
const isPreventAutoscroll = useMessageStore(
|
||||||
(state) => state.isPreventAutoscroll
|
(state) => state.isPreventAutoscroll
|
||||||
);
|
);
|
||||||
|
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const token = useToken();
|
const token = useToken();
|
||||||
const ticketId = useParams().ticketId;
|
const ticketId = useParams().ticketId;
|
||||||
const ticket = tickets.find((ticket) => ticket.id === ticketId);
|
const ticket = tickets.find((ticket) => ticket.id === ticketId);
|
||||||
@ -127,7 +139,6 @@ function SupportChat() {
|
|||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[lastMessageId]
|
[lastMessageId]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -177,6 +188,45 @@ function SupportChat() {
|
|||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sendFile = async (file: File) => {
|
||||||
|
if (file === undefined) return true;
|
||||||
|
|
||||||
|
if (ticketId) {
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = new FormData();
|
||||||
|
|
||||||
|
body.append(file.name, file);
|
||||||
|
body.append("ticket", ticketId);
|
||||||
|
await makeRequest({
|
||||||
|
url: process.env.REACT_APP_DOMAIN + "/heruvym/sendFiles",
|
||||||
|
body: body,
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
const errorMessage = getMessageFromFetchError(error);
|
||||||
|
if (errorMessage) enqueueSnackbar(errorMessage);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendFileHC = async (file: File) => {
|
||||||
|
console.log(file);
|
||||||
|
const check = checkAcceptableMediaType(file);
|
||||||
|
if (check.length > 0) {
|
||||||
|
enqueueSnackbar(check);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setDisableFileButton(true);
|
||||||
|
await sendFile(file);
|
||||||
|
setDisableFileButton(false);
|
||||||
|
console.log(disableFileButton);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -262,14 +312,87 @@ function SupportChat() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ticket &&
|
{ticket &&
|
||||||
messages.map((message) => (
|
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 !== null &&
|
||||||
|
message.files.length > 0 &&
|
||||||
|
isFileImage()
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ChatImage
|
||||||
|
unAuthenticated
|
||||||
|
key={message.id}
|
||||||
|
file={message.files[0]}
|
||||||
|
createdAt={message.created_at}
|
||||||
|
isSelf={ticket.user === message.user_id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
message.files !== null &&
|
||||||
|
message.files.length > 0 &&
|
||||||
|
isFileVideo()
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ChatVideo
|
||||||
|
unAuthenticated
|
||||||
|
key={message.id}
|
||||||
|
file={message.files[0]}
|
||||||
|
createdAt={message.created_at}
|
||||||
|
isSelf={ticket.user === message.user_id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
message.files !== null &&
|
||||||
|
message.files.length > 0 &&
|
||||||
|
isFileDocument()
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ChatDocument
|
||||||
|
unAuthenticated
|
||||||
|
key={message.id}
|
||||||
|
file={message.files[0]}
|
||||||
|
createdAt={message.created_at}
|
||||||
|
isSelf={ticket.user === message.user_id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
|
unAuthenticated
|
||||||
key={message.id}
|
key={message.id}
|
||||||
text={message.message}
|
text={message.message}
|
||||||
createdAt={message.created_at}
|
createdAt={message.created_at}
|
||||||
isSelf={ticket.user === message.user_id}
|
isSelf={ticket.user === message.user_id}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -298,6 +421,27 @@ function SupportChat() {
|
|||||||
endAdornment={
|
endAdornment={
|
||||||
!upMd && (
|
!upMd && (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
sx={{ mr: "4px" }}
|
||||||
|
disabled={disableFileButton}
|
||||||
|
onClick={() => {
|
||||||
|
console.log(disableFileButton);
|
||||||
|
if (!disableFileButton) fileInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AttachFileIcon />
|
||||||
|
</IconButton>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="fileinput"
|
||||||
|
onChange={({ target }) => {
|
||||||
|
if (target.files?.[0]) {
|
||||||
|
sendFileHC(target.files?.[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
sx={{
|
sx={{
|
||||||
@ -318,6 +462,27 @@ function SupportChat() {
|
|||||||
</Box>
|
</Box>
|
||||||
{upMd && (
|
{upMd && (
|
||||||
<Box sx={{ alignSelf: "end" }}>
|
<Box sx={{ alignSelf: "end" }}>
|
||||||
|
<IconButton
|
||||||
|
sx={{ mr: "4px" }}
|
||||||
|
disabled={disableFileButton}
|
||||||
|
onClick={() => {
|
||||||
|
console.log(disableFileButton);
|
||||||
|
if (!disableFileButton) fileInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AttachFileIcon />
|
||||||
|
</IconButton>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="fileinput"
|
||||||
|
onChange={({ target }) => {
|
||||||
|
if (target.files?.[0]) {
|
||||||
|
sendFileHC(target.files?.[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="pena-contained-dark"
|
variant="pena-contained-dark"
|
||||||
onClick={handleSendMessage}
|
onClick={handleSendMessage}
|
||||||
|
@ -4,7 +4,6 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|||||||
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import NumberIcon from "@root/components/NumberIcon";
|
import NumberIcon from "@root/components/NumberIcon";
|
||||||
import { useDiscountStore } from "@root/stores/discounts";
|
import { useDiscountStore } from "@root/stores/discounts";
|
||||||
import { useHistoryStore } from "@root/stores/history";
|
|
||||||
import { useTariffStore } from "@root/stores/tariffs";
|
import { useTariffStore } from "@root/stores/tariffs";
|
||||||
import { addTariffToCart, useUserStore } from "@root/stores/user";
|
import { addTariffToCart, useUserStore } from "@root/stores/user";
|
||||||
import { calcIndividualTariffPrices } from "@root/utils/calcTariffPrices";
|
import { calcIndividualTariffPrices } from "@root/utils/calcTariffPrices";
|
||||||
@ -39,7 +38,6 @@ function TariffPage() {
|
|||||||
const discounts = useDiscountStore((state) => state.discounts);
|
const discounts = useDiscountStore((state) => state.discounts);
|
||||||
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
const purchasesAmount = useUserStore((state) => state.userAccount?.wallet.spent) ?? 0;
|
||||||
const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko";
|
const isUserNko = useUserStore((state) => state.userAccount?.status) === "nko";
|
||||||
const historyData = useHistoryStore((state) => state.history);
|
|
||||||
const currentTariffs = useCartTariffs();
|
const currentTariffs = useCartTariffs();
|
||||||
|
|
||||||
const handleCustomBackNavigation = usePrevLocation(location);
|
const handleCustomBackNavigation = usePrevLocation(location);
|
||||||
@ -70,20 +68,6 @@ function TariffPage() {
|
|||||||
return false
|
return false
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCustomTariffs = tariffs.filter((tariff) => {
|
|
||||||
return (
|
|
||||||
tariff.privileges.map((p) => p.type).includes("day") === (unit === "time") && !tariff.isDeleted && tariff.isCustom
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const tariffsFromHistory = tariffs.filter((tariff) => {
|
|
||||||
if (!historyData) return false;
|
|
||||||
|
|
||||||
const historyTariffIds = historyData.map((historyRecord) => (historyRecord.rawDetails[0] as any).Value[0][0].Value);
|
|
||||||
|
|
||||||
return historyTariffIds.includes(tariff._id);
|
|
||||||
});
|
|
||||||
|
|
||||||
const createTariffElements = (filteredTariffs: Tariff[], addFreeTariff = false) => {
|
const createTariffElements = (filteredTariffs: Tariff[], addFreeTariff = false) => {
|
||||||
console.log(filteredTariffs)
|
console.log(filteredTariffs)
|
||||||
const tariffElements = filteredTariffs
|
const tariffElements = filteredTariffs
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { HistoryRecord } from "@root/api/history";
|
import { HistoryRecord, HistoryRecord2 } from "@root/api/history";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools, persist } from "zustand/middleware";
|
import { devtools, persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
|
||||||
type HistoryStore = {
|
type HistoryStore = {
|
||||||
history: HistoryRecord[] | null;
|
history: HistoryRecord[] | HistoryRecord2[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: HistoryStore = {
|
const initialState: HistoryStore = {
|
||||||
|
@ -1,7 +1,23 @@
|
|||||||
import { FetchState, Ticket } from "@frontend/kitui"
|
import { FetchState, Ticket, TicketMessage } from "@frontend/kitui";
|
||||||
import { create } from "zustand"
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware"
|
import { devtools, persist, createJSONStorage } from "zustand/middleware";
|
||||||
|
import { useUserStore } from "./user";
|
||||||
|
import { produce } from "immer";
|
||||||
|
|
||||||
|
type SessionData = {
|
||||||
|
ticketId: string;
|
||||||
|
sessionId: string;
|
||||||
|
};
|
||||||
|
interface AuthData {
|
||||||
|
sessionData: SessionData | null;
|
||||||
|
isMessageSending: boolean;
|
||||||
|
messages: TicketMessage[];
|
||||||
|
apiPage: number;
|
||||||
|
messagesPerPage: number;
|
||||||
|
lastMessageId: string | undefined;
|
||||||
|
isPreventAutoscroll: boolean;
|
||||||
|
unauthTicketMessageFetchState: FetchState;
|
||||||
|
}
|
||||||
|
|
||||||
interface TicketStore {
|
interface TicketStore {
|
||||||
ticketCount: number;
|
ticketCount: number;
|
||||||
@ -9,38 +25,159 @@ interface TicketStore {
|
|||||||
apiPage: number;
|
apiPage: number;
|
||||||
ticketsPerPage: number;
|
ticketsPerPage: number;
|
||||||
ticketsFetchState: FetchState;
|
ticketsFetchState: FetchState;
|
||||||
|
authData: AuthData;
|
||||||
|
unauthData: AuthData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initAuthData = {
|
||||||
|
sessionData: null,
|
||||||
|
isMessageSending: false,
|
||||||
|
messages: [],
|
||||||
|
apiPage: 0,
|
||||||
|
messagesPerPage: 10,
|
||||||
|
lastMessageId: undefined,
|
||||||
|
isPreventAutoscroll: false,
|
||||||
|
unauthTicketMessageFetchState: "idle" as FetchState,
|
||||||
|
};
|
||||||
|
|
||||||
const initialState: TicketStore = {
|
const initialState: TicketStore = {
|
||||||
ticketCount: 0,
|
ticketCount: 0,
|
||||||
tickets: [],
|
tickets: [],
|
||||||
apiPage: 0,
|
apiPage: 0,
|
||||||
ticketsPerPage: 10,
|
ticketsPerPage: 10,
|
||||||
ticketsFetchState: "idle",
|
ticketsFetchState: "idle",
|
||||||
}
|
authData: initAuthData,
|
||||||
|
unauthData: initAuthData,
|
||||||
|
};
|
||||||
|
|
||||||
export const useTicketStore = create<TicketStore>()(
|
export const useTicketStore = create<TicketStore>()(
|
||||||
devtools(
|
persist(
|
||||||
(set, get) => initialState,
|
devtools((set, get) => initialState, {
|
||||||
|
name: "Unauth tickets",
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
name: "Tickets"
|
version: 0,
|
||||||
|
name: "unauth-ticket",
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
|
|
||||||
export const setTicketCount = (ticketCount: number) => useTicketStore.setState({ ticketCount })
|
export const setTicketCount = (ticketCount: number) =>
|
||||||
|
useTicketStore.setState({ ticketCount });
|
||||||
|
|
||||||
export const setTicketApiPage = (apiPage: number) => useTicketStore.setState({ apiPage: apiPage })
|
export const setTicketApiPage = (apiPage: number) =>
|
||||||
|
useTicketStore.setState({ apiPage: apiPage });
|
||||||
|
|
||||||
export const updateTickets = (receivedTickets: Ticket[]) => {
|
export const updateTickets = (receivedTickets: Ticket[]) => {
|
||||||
const state = useTicketStore.getState()
|
const state = useTicketStore.getState();
|
||||||
const ticketIdToTicketMap: { [ticketId: string]: Ticket; } = {};
|
const ticketIdToTicketMap: { [ticketId: string]: Ticket } = {};
|
||||||
|
|
||||||
[...state.tickets, ...receivedTickets].forEach(ticket => ticketIdToTicketMap[ticket.id] = ticket)
|
[...state.tickets, ...receivedTickets].forEach(
|
||||||
|
(ticket) => (ticketIdToTicketMap[ticket.id] = ticket)
|
||||||
|
);
|
||||||
|
|
||||||
useTicketStore.setState({ tickets: Object.values(ticketIdToTicketMap) })
|
useTicketStore.setState({ tickets: Object.values(ticketIdToTicketMap) });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearTickets = () => useTicketStore.setState({ ...initialState });
|
||||||
|
|
||||||
|
export const setTicketsFetchState = (ticketsFetchState: FetchState) =>
|
||||||
|
useTicketStore.setState({ ticketsFetchState });
|
||||||
|
|
||||||
|
export const setTicketData = (sessionData: SessionData) =>
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
ticket.sessionData = sessionData;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateTicket = <T extends AuthData>(
|
||||||
|
recipe: (ticket: AuthData) => void
|
||||||
|
) =>
|
||||||
|
setProducedState(
|
||||||
|
(state) => {
|
||||||
|
//В зависимости от авторизованности вызывается изменение разных объектов
|
||||||
|
if (Boolean(useUserStore.getState().userId)) {
|
||||||
|
recipe(state.authData);
|
||||||
|
} else {
|
||||||
|
recipe(state.unauthData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "updateTicket",
|
||||||
|
recipe,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function setProducedState<A extends string | { type: unknown }>(
|
||||||
|
recipe: (state: TicketStore) => void,
|
||||||
|
action?: A
|
||||||
|
) {
|
||||||
|
useTicketStore.setState((state) => produce(state, recipe), false, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearTickets = () => useTicketStore.setState({ ...initialState })
|
function filterMessageUncompleteness(messages: TicketMessage[]) {
|
||||||
|
return messages.filter(
|
||||||
|
(message) =>
|
||||||
|
"id" in message &&
|
||||||
|
"ticket_id" in message &&
|
||||||
|
"user_id" in message &&
|
||||||
|
"session_id" in message &&
|
||||||
|
"message" in message &&
|
||||||
|
"files" in message &&
|
||||||
|
"shown" in message &&
|
||||||
|
"request_screenshot" in message &&
|
||||||
|
"created_at" in message &&
|
||||||
|
((message.files !== null && message.files.length > 0) ||
|
||||||
|
message.message.length > 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const setTicketsFetchState = (ticketsFetchState: FetchState) => useTicketStore.setState({ ticketsFetchState })
|
function sortMessagesByTime(ticket1: TicketMessage, ticket2: TicketMessage) {
|
||||||
|
const date1 = new Date(ticket1.created_at).getTime();
|
||||||
|
const date2 = new Date(ticket2.created_at).getTime();
|
||||||
|
return date1 - date2;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addOrUpdateUnauthMessages = (receivedMessages: TicketMessage[]) =>
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
const filtered = filterMessageUncompleteness(receivedMessages);
|
||||||
|
if (filtered.length === 0) return;
|
||||||
|
|
||||||
|
const messageIdToMessageMap: { [messageId: string]: TicketMessage } = {};
|
||||||
|
|
||||||
|
[...ticket.messages, ...filtered].forEach(
|
||||||
|
(message) => (messageIdToMessageMap[message.id] = message)
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortedMessages = Object.values(messageIdToMessageMap).sort(
|
||||||
|
sortMessagesByTime
|
||||||
|
);
|
||||||
|
|
||||||
|
ticket.messages = sortedMessages;
|
||||||
|
ticket.lastMessageId = sortedMessages.at(-1)?.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setUnauthTicketMessageFetchState = (
|
||||||
|
unauthTicketMessageFetchState: FetchState
|
||||||
|
) =>
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
ticket.unauthTicketMessageFetchState = unauthTicketMessageFetchState;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setUnauthIsPreventAutoscroll = (isPreventAutoscroll: boolean) =>
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
ticket.isPreventAutoscroll = isPreventAutoscroll;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const incrementUnauthMessage = () =>
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
ticket.apiPage++;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const setIsMessageSending = (
|
||||||
|
isMessageSending: AuthData["isMessageSending"]
|
||||||
|
) => {
|
||||||
|
updateTicket((ticket) => {
|
||||||
|
ticket.isMessageSending = isMessageSending;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import { FetchState, TicketMessage } from "@frontend/kitui"
|
|
||||||
import { create } from "zustand"
|
|
||||||
import { createJSONStorage, devtools, persist } from "zustand/middleware"
|
|
||||||
|
|
||||||
|
|
||||||
interface UnauthTicketStore {
|
|
||||||
sessionData: {
|
|
||||||
ticketId: string;
|
|
||||||
sessionId: string;
|
|
||||||
} | null;
|
|
||||||
isMessageSending: boolean;
|
|
||||||
messages: TicketMessage[];
|
|
||||||
apiPage: number;
|
|
||||||
messagesPerPage: number;
|
|
||||||
lastMessageId: string | undefined;
|
|
||||||
isPreventAutoscroll: boolean;
|
|
||||||
unauthTicketMessageFetchState: FetchState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useUnauthTicketStore = create<UnauthTicketStore>()(
|
|
||||||
persist(
|
|
||||||
devtools(
|
|
||||||
(set, get) => ({
|
|
||||||
sessionData: null,
|
|
||||||
isMessageSending: false,
|
|
||||||
messages: [],
|
|
||||||
apiPage: 0,
|
|
||||||
messagesPerPage: 10,
|
|
||||||
lastMessageId: undefined,
|
|
||||||
isPreventAutoscroll: false,
|
|
||||||
unauthTicketMessageFetchState: "idle",
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "Unauth tickets"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
{
|
|
||||||
version: 0,
|
|
||||||
name: "unauth-ticket",
|
|
||||||
storage: createJSONStorage(() => localStorage),
|
|
||||||
partialize: state => ({
|
|
||||||
sessionData: state.sessionData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const setUnauthSessionData = (sessionData: UnauthTicketStore["sessionData"]) => useUnauthTicketStore.setState({ sessionData })
|
|
||||||
export const setIsMessageSending = (isMessageSending: UnauthTicketStore["isMessageSending"]) => useUnauthTicketStore.setState({ isMessageSending })
|
|
||||||
|
|
||||||
export const addOrUpdateUnauthMessages = (receivedMessages: TicketMessage[]) => {
|
|
||||||
const state = useUnauthTicketStore.getState()
|
|
||||||
const messageIdToMessageMap: { [messageId: string]: TicketMessage; } = {};
|
|
||||||
|
|
||||||
[...state.messages, ...receivedMessages].forEach(message => messageIdToMessageMap[message.id] = message)
|
|
||||||
|
|
||||||
const sortedMessages = Object.values(messageIdToMessageMap).sort(sortMessagesByTime)
|
|
||||||
|
|
||||||
useUnauthTicketStore.setState({
|
|
||||||
messages: sortedMessages,
|
|
||||||
lastMessageId: sortedMessages.at(-1)?.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const incrementUnauthMessageApiPage = () => {
|
|
||||||
const state = useUnauthTicketStore.getState()
|
|
||||||
|
|
||||||
useUnauthTicketStore.setState({ apiPage: state.apiPage + 1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setUnauthIsPreventAutoscroll = (isPreventAutoscroll: boolean) => useUnauthTicketStore.setState({ isPreventAutoscroll })
|
|
||||||
|
|
||||||
export const setUnauthTicketMessageFetchState = (unauthTicketMessageFetchState: FetchState) => useUnauthTicketStore.setState({ unauthTicketMessageFetchState })
|
|
||||||
|
|
||||||
function sortMessagesByTime(ticket1: TicketMessage, ticket2: TicketMessage) {
|
|
||||||
const date1 = new Date(ticket1.created_at).getTime()
|
|
||||||
const date2 = new Date(ticket2.created_at).getTime()
|
|
||||||
return date1 - date2
|
|
||||||
}
|
|
37
src/utils/checkAcceptableMediaType.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export const MAX_FILE_SIZE = 10485760;
|
||||||
|
const MAX_PHOTO_SIZE = 5242880;
|
||||||
|
const MAX_VIDEO_SIZE = 52428800;
|
||||||
|
|
||||||
|
export const ACCEPT_SEND_MEDIA_TYPES_MAP = {
|
||||||
|
picture: ["jpg", "png"],
|
||||||
|
video: ["mp4"],
|
||||||
|
document: ["doc", "docx", "pdf", "txt", "xlsx", "csv"],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const TOO_LARGE_TEXT = "Файл слишком большой";
|
||||||
|
|
||||||
|
export const checkAcceptableMediaType = (file: File) => {
|
||||||
|
if (file === null) return "";
|
||||||
|
|
||||||
|
const segments = file?.name.split(".");
|
||||||
|
const extension = segments[segments.length - 1];
|
||||||
|
const type = extension.toLowerCase();
|
||||||
|
|
||||||
|
console.log(type);
|
||||||
|
switch (type) {
|
||||||
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.document.find((name) => name === type):
|
||||||
|
if (file.size > MAX_FILE_SIZE) return TOO_LARGE_TEXT;
|
||||||
|
return "";
|
||||||
|
|
||||||
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find((name) => name === type):
|
||||||
|
if (file.size > MAX_PHOTO_SIZE) return TOO_LARGE_TEXT;
|
||||||
|
return "";
|
||||||
|
|
||||||
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find((name) => name === type):
|
||||||
|
if (file.size > MAX_VIDEO_SIZE) return TOO_LARGE_TEXT;
|
||||||
|
return "";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Не удалось отправить файл. Недопустимый тип";
|
||||||
|
}
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { GetHistoryResponse, getHistory } from "@root/api/history";
|
import { GetHistoryResponse, getHistory } from "@root/api/history";
|
||||||
import { setHistory, useHistoryStore } from "@root/stores/history";
|
import { setHistory } from "@root/stores/history";
|
||||||
|
|
||||||
export const useHistoryData = () => {
|
export const useHistoryData = () => {
|
||||||
|
|
||||||
@ -8,7 +8,6 @@ export const useHistoryData = () => {
|
|||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const [response, errorMsg] = await getHistory();
|
const [response, errorMsg] = await getHistory();
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
console.error("Произошла ошибка при вызове getHistory:", errorMsg);
|
console.error("Произошла ошибка при вызове getHistory:", errorMsg);
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,19 @@
|
|||||||
import {useEffect, useState} from "react"
|
import {useEffect, useState} from "react"
|
||||||
import { useTariffStore } from "@root/stores/tariffs";
|
import { useTariffStore } from "@root/stores/tariffs";
|
||||||
import {getRecentlyPurchasedTariffs} from "@root/api/recentlyPurchasedTariffs"
|
import {getRecentlyPurchasedTariffs} from "@root/api/recentlyPurchasedTariffs"
|
||||||
import { getTariffById } from "@root/api/tariff"
|
|
||||||
import {useHistoryStore} from "@stores/history"
|
|
||||||
|
|
||||||
export const useRecentlyPurchasedTariffs = () => {
|
export const useRecentlyPurchasedTariffs = () => {
|
||||||
const [recentlyPurchased, setRecentlyPurchased] = useState<any>([])
|
const [recentlyPurchased, setRecentlyPurchased] = useState<any>([])
|
||||||
const tariffs = useTariffStore((state) => state.tariffs);
|
const tariffs = useTariffStore((state) => state.tariffs);
|
||||||
const historyData = useHistoryStore(state => state.history);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("юзэффект начинает работаььб")
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
try {
|
try {
|
||||||
const [response, errorMsg] = await getRecentlyPurchasedTariffs();
|
const [response, errorMsg] = await getRecentlyPurchasedTariffs();
|
||||||
console.log("responce" , response)
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
console.error("Произошла ошибка при вызове getRecentlyPurchasedTariffs:", errorMsg);
|
console.error("Произошла ошибка при вызове getRecentlyPurchasedTariffs:", errorMsg);
|
||||||
}
|
}
|
||||||
if (response) {
|
if (response) {
|
||||||
const recentlyTariffs = response.slice(0, 10).map((obj: { id: string })=>obj.id)
|
const recentlyTariffs = response.slice(0, 10).map((obj: { id: string })=>obj.id)
|
||||||
console.log("responce22222222222222222" , recentlyTariffs)
|
|
||||||
console.log("tariffstariffstariffstariffstariffstariffs" , tariffs)
|
|
||||||
setRecentlyPurchased(tariffs.filter((tariffs)=>{
|
setRecentlyPurchased(tariffs.filter((tariffs)=>{
|
||||||
return recentlyTariffs.includes(tariffs._id)}));
|
return recentlyTariffs.includes(tariffs._id)}));
|
||||||
}
|
}
|
||||||
@ -31,6 +24,6 @@ console.log("responce" , response)
|
|||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [tariffs]);
|
}, [tariffs]);
|
||||||
console.log(recentlyPurchased)
|
|
||||||
return {recentlyPurchased}
|
return {recentlyPurchased}
|
||||||
}
|
}
|
11
yarn.lock
@ -4002,7 +4002,6 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591:
|
|||||||
version "1.0.30001593"
|
version "1.0.30001593"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz#7cda1d9e5b0cad6ebab4133b1f239d4ea44fe659"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz#7cda1d9e5b0cad6ebab4133b1f239d4ea44fe659"
|
||||||
integrity sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==
|
integrity sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==
|
||||||
|
|
||||||
canvas@^2.11.2:
|
canvas@^2.11.2:
|
||||||
version "2.11.2"
|
version "2.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
|
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
|
||||||
@ -5756,7 +5755,6 @@ fast-glob@^3.2.9, fast-glob@^3.3.0:
|
|||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
|
||||||
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
||||||
dependencies:
|
|
||||||
"@nodelib/fs.stat" "^2.0.2"
|
"@nodelib/fs.stat" "^2.0.2"
|
||||||
"@nodelib/fs.walk" "^1.2.3"
|
"@nodelib/fs.walk" "^1.2.3"
|
||||||
glob-parent "^5.1.2"
|
glob-parent "^5.1.2"
|
||||||
@ -6336,7 +6334,6 @@ hasown@^2.0.0, hasown@^2.0.1:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa"
|
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa"
|
||||||
integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==
|
integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==
|
||||||
dependencies:
|
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
he@^1.2.0:
|
he@^1.2.0:
|
||||||
@ -7015,7 +7012,6 @@ jake@^10.8.5:
|
|||||||
version "10.8.7"
|
version "10.8.7"
|
||||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
|
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
|
||||||
integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==
|
integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==
|
||||||
dependencies:
|
|
||||||
async "^3.2.3"
|
async "^3.2.3"
|
||||||
chalk "^4.0.2"
|
chalk "^4.0.2"
|
||||||
filelist "^1.0.4"
|
filelist "^1.0.4"
|
||||||
@ -8259,7 +8255,6 @@ lz-string@^1.5.0:
|
|||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
magic-string@^0.25.0, magic-string@^0.25.7:
|
magic-string@^0.25.0, magic-string@^0.25.7:
|
||||||
version "0.25.9"
|
version "0.25.9"
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||||
@ -8421,7 +8416,6 @@ minimatch@^5.0.1:
|
|||||||
version "5.1.6"
|
version "5.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||||
dependencies:
|
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8:
|
minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8:
|
||||||
@ -8754,7 +8748,6 @@ open@^8.0.9, open@^8.4.0:
|
|||||||
version "8.4.2"
|
version "8.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
|
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
|
||||||
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
|
integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==
|
||||||
dependencies:
|
|
||||||
define-lazy-prop "^2.0.0"
|
define-lazy-prop "^2.0.0"
|
||||||
is-docker "^2.1.1"
|
is-docker "^2.1.1"
|
||||||
is-wsl "^2.2.0"
|
is-wsl "^2.2.0"
|
||||||
@ -9517,7 +9510,6 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-select
|
|||||||
version "6.0.15"
|
version "6.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535"
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535"
|
||||||
integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==
|
integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==
|
||||||
dependencies:
|
|
||||||
cssesc "^3.0.0"
|
cssesc "^3.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
@ -10020,7 +10012,6 @@ regenerate-unicode-properties@^10.1.0:
|
|||||||
version "10.1.1"
|
version "10.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
|
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
|
||||||
integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==
|
integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==
|
||||||
dependencies:
|
|
||||||
regenerate "^1.4.2"
|
regenerate "^1.4.2"
|
||||||
|
|
||||||
regenerate@^1.4.2:
|
regenerate@^1.4.2:
|
||||||
@ -10154,7 +10145,6 @@ resolve.exports@^1.1.0:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999"
|
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999"
|
||||||
integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==
|
integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==
|
||||||
|
|
||||||
resolve.exports@^2.0.0:
|
resolve.exports@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
|
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
|
||||||
@ -10397,7 +10387,6 @@ serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
|
|||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
|
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
|
||||||
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
|
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
|
||||||
dependencies:
|
|
||||||
randombytes "^2.1.0"
|
randombytes "^2.1.0"
|
||||||
|
|
||||||
serve-index@^1.9.1:
|
serve-index@^1.9.1:
|
||||||
|