feat: support chat upload file
This commit is contained in:
parent
c730993fef
commit
0f91f0037d
@ -1,34 +1,16 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "react"],
|
||||
"rules": {
|
||||
"quotes": ["error", "double"]
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,46 @@
|
||||
import { makeRequest } from "@frontend/kitui"
|
||||
import { parseAxiosError } from "@root/utils/parse-error"
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
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(
|
||||
ticketId: string,
|
||||
message: string
|
||||
ticketId: string,
|
||||
message: string
|
||||
): Promise<[null, string?]> {
|
||||
try {
|
||||
const sendTicketMessageResponse = await makeRequest<
|
||||
try {
|
||||
const sendTicketMessageResponse = await makeRequest<
|
||||
SendTicketMessageRequest,
|
||||
null
|
||||
>({
|
||||
url: `${apiUrl}/send`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||
})
|
||||
url: `${apiUrl}/send`,
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body: { ticket: ticketId, message: message, lang: "ru", files: [] },
|
||||
});
|
||||
|
||||
return [sendTicketMessageResponse]
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError)
|
||||
return [sendTicketMessageResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось отправить сообщение. ${error}`]
|
||||
}
|
||||
return [null, `Не удалось отправить сообщение. ${error}`];
|
||||
}
|
||||
}
|
||||
|
||||
export async function shownMessage(id: string): Promise<[null, string?]> {
|
||||
try {
|
||||
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
||||
url: apiUrl + "/shown",
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body: { id },
|
||||
})
|
||||
try {
|
||||
const shownMessageResponse = await makeRequest<{ id: string }, null>({
|
||||
url: apiUrl + "/shown",
|
||||
method: "POST",
|
||||
useToken: true,
|
||||
body: { id },
|
||||
});
|
||||
|
||||
return [shownMessageResponse]
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError)
|
||||
return [shownMessageResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось прочесть сообщение. ${error}`]
|
||||
}
|
||||
return [null, `Не удалось прочесть сообщение. ${error}`];
|
||||
}
|
||||
}
|
||||
|
55
src/assets/Icons/download.tsx
Normal file
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>
|
||||
);
|
||||
}
|
@ -10,32 +10,52 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { TicketMessage } from "@frontend/kitui";
|
||||
import {
|
||||
addOrUpdateUnauthMessages,
|
||||
useUnauthTicketStore,
|
||||
incrementUnauthMessageApiPage,
|
||||
setUnauthIsPreventAutoscroll,
|
||||
setUnauthSessionData,
|
||||
setIsMessageSending,
|
||||
setUnauthTicketMessageFetchState,
|
||||
} from "@root/stores/unauthTicket";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import ChatMessage from "../ChatMessage";
|
||||
import SendIcon from "../icons/SendIcon";
|
||||
import UserCircleIcon from "./UserCircleIcon";
|
||||
import { throttle } from "@frontend/kitui";
|
||||
import {
|
||||
TicketMessage,
|
||||
makeRequest,
|
||||
useTicketsFetcher,
|
||||
useTicketMessages,
|
||||
getMessageFromFetchError,
|
||||
useSSESubscription,
|
||||
useEventListener,
|
||||
createTicket,
|
||||
} from "@frontend/kitui";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import ChatMessage from "../ChatMessage";
|
||||
import SendIcon from "../icons/SendIcon";
|
||||
import UserCircleIcon from "./UserCircleIcon";
|
||||
import { throttle } from "@frontend/kitui";
|
||||
import { sendTicketMessage, shownMessage } from "@root/api/ticket";
|
||||
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,
|
||||
incrementUnauthMessageApiPage,
|
||||
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";
|
||||
|
||||
type ModalWarningType =
|
||||
| "errorType"
|
||||
| "errorSize"
|
||||
| "picture"
|
||||
| "video"
|
||||
| "audio"
|
||||
| "document"
|
||||
| null;
|
||||
interface Props {
|
||||
open: boolean;
|
||||
sx?: SxProps<Theme>;
|
||||
@ -45,23 +65,26 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
const theme = useTheme();
|
||||
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||
const [messageField, setMessageField] = useState<string>("");
|
||||
const sessionData = useUnauthTicketStore((state) => state.sessionData);
|
||||
const messages = useUnauthTicketStore((state) => state.messages);
|
||||
const messageApiPage = useUnauthTicketStore((state) => state.apiPage);
|
||||
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 [disableFileButton, setDisableFileButton] = useState(false);
|
||||
const [modalWarningType, setModalWarningType] =
|
||||
useState<ModalWarningType>(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[]>(
|
||||
"ticket",
|
||||
addOrUpdateUnauthMessages
|
||||
@ -74,8 +97,10 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
messagesPerPage,
|
||||
messageApiPage,
|
||||
onSuccess: useCallback((messages) => {
|
||||
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
|
||||
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1) {
|
||||
chatBoxRef.current.scrollTop = 1;
|
||||
}
|
||||
|
||||
addOrUpdateUnauthMessages(messages);
|
||||
}, []),
|
||||
onError: useCallback((error: Error) => {
|
||||
@ -92,6 +117,7 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
`/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
|
||||
onNewData: (ticketMessages) => {
|
||||
updateSSEValue(ticketMessages);
|
||||
|
||||
addOrUpdateUnauthMessages(ticketMessages);
|
||||
},
|
||||
onDisconnect: useCallback(() => {
|
||||
@ -100,6 +126,34 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
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(
|
||||
() =>
|
||||
throttle(() => {
|
||||
@ -149,7 +203,7 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
async function handleSendMessage() {
|
||||
if (!messageField || isMessageSending) return;
|
||||
|
||||
if (!sessionData) {
|
||||
if (!sessionData?.ticketId) {
|
||||
setIsMessageSending(true);
|
||||
createTicket({
|
||||
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
|
||||
@ -157,10 +211,10 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
Title: "Unauth title",
|
||||
Message: messageField,
|
||||
},
|
||||
useToken: false,
|
||||
useToken: Boolean(user),
|
||||
})
|
||||
.then((response) => {
|
||||
setUnauthSessionData({
|
||||
setTicketData({
|
||||
ticketId: response.Ticket,
|
||||
sessionId: response.sess,
|
||||
});
|
||||
@ -210,6 +264,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 (
|
||||
<>
|
||||
{open && (
|
||||
@ -276,16 +390,87 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{sessionData &&
|
||||
messages.map((message) => (
|
||||
<ChatMessage
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
text={message.message}
|
||||
createdAt={message.created_at}
|
||||
isSelf={sessionData.sessionId === message.user_id}
|
||||
/>
|
||||
))}
|
||||
{ticket.sessionData?.ticketId &&
|
||||
messages.map((message) => {
|
||||
const isFileVideo = () => {
|
||||
if (message.files) {
|
||||
return ACCEPT_SEND_MEDIA_TYPES_MAP.video.some(
|
||||
(fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType)
|
||||
);
|
||||
}
|
||||
};
|
||||
const isFileImage = () => {
|
||||
if (message.files) {
|
||||
return ACCEPT_SEND_MEDIA_TYPES_MAP.picture.some(
|
||||
(fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType)
|
||||
);
|
||||
}
|
||||
};
|
||||
const isFileDocument = () => {
|
||||
if (message.files) {
|
||||
return ACCEPT_SEND_MEDIA_TYPES_MAP.document.some(
|
||||
(fileType) =>
|
||||
message.files[0].toLowerCase().endsWith(fileType)
|
||||
);
|
||||
}
|
||||
};
|
||||
if (message.files.length > 0 && isFileImage()) {
|
||||
return (
|
||||
<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
|
||||
unAuthenticated
|
||||
key={message.id}
|
||||
text={message.message}
|
||||
createdAt={message.created_at}
|
||||
isSelf={
|
||||
(ticket.sessionData?.sessionId || user) ===
|
||||
message.user_id
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
|
||||
<InputBase
|
||||
@ -314,6 +499,26 @@ export default function Chat({ open = false, sx }: Props) {
|
||||
onChange={(e) => setMessageField(e.target.value)}
|
||||
endAdornment={
|
||||
<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
|
||||
disabled={isMessageSending}
|
||||
onClick={handleSendMessage}
|
||||
|
113
src/components/FloatingSupportChat/ChatDocument.tsx
Normal file
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
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>
|
||||
);
|
||||
}
|
123
src/components/FloatingSupportChat/ChatVideo.tsx
Normal file
123
src/components/FloatingSupportChat/ChatVideo.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
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={{
|
||||
height: "217px",
|
||||
width: "217px",
|
||||
}}
|
||||
controls
|
||||
>
|
||||
<source src={`https://storage.yandexcloud.net/pair/${file}`} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -4,12 +4,16 @@ import { Box, Fab, Typography, Badge, useTheme } from "@mui/material";
|
||||
import CircleDoubleDown from "./CircleDoubleDownIcon";
|
||||
import Chat from "./Chat";
|
||||
|
||||
import { useUnauthTicketStore } from "@root/stores/unauthTicket";
|
||||
import { useUserStore } from "@root/stores/user";
|
||||
import { useTicketStore } from "@root/stores/tickets";
|
||||
|
||||
export default function FloatingSupportChat() {
|
||||
const [isChatOpened, setIsChatOpened] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
const { messages } = useUnauthTicketStore((state) => state);
|
||||
const user = useUserStore((state) => state.user?._id);
|
||||
const { messages } = useTicketStore(
|
||||
(state) => state[user ? "authData" : "unauthData"]
|
||||
);
|
||||
|
||||
const animation = {
|
||||
"@keyframes runningStripe": {
|
||||
@ -35,6 +39,7 @@ export default function FloatingSupportChat() {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -1,46 +1,184 @@
|
||||
import { FetchState, Ticket } from "@frontend/kitui"
|
||||
import { create } from "zustand"
|
||||
import { devtools } from "zustand/middleware"
|
||||
import { FetchState, Ticket, TicketMessage } from "@frontend/kitui";
|
||||
import { create } from "zustand";
|
||||
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 {
|
||||
ticketCount: number;
|
||||
tickets: Ticket[];
|
||||
apiPage: number;
|
||||
ticketsPerPage: number;
|
||||
ticketsFetchState: FetchState;
|
||||
ticketCount: number;
|
||||
tickets: Ticket[];
|
||||
apiPage: number;
|
||||
ticketsPerPage: number;
|
||||
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 = {
|
||||
ticketCount: 0,
|
||||
tickets: [],
|
||||
apiPage: 0,
|
||||
ticketsPerPage: 10,
|
||||
ticketsFetchState: "idle",
|
||||
}
|
||||
ticketCount: 0,
|
||||
tickets: [],
|
||||
apiPage: 0,
|
||||
ticketsPerPage: 10,
|
||||
ticketsFetchState: "idle",
|
||||
authData: initAuthData,
|
||||
unauthData: initAuthData,
|
||||
};
|
||||
|
||||
export const useTicketStore = create<TicketStore>()(
|
||||
devtools(
|
||||
(set, get) => initialState,
|
||||
{
|
||||
name: "Tickets"
|
||||
}
|
||||
)
|
||||
)
|
||||
persist(
|
||||
devtools((set, get) => initialState, {
|
||||
name: "Unauth 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[]) => {
|
||||
const state = useTicketStore.getState()
|
||||
const ticketIdToTicketMap: { [ticketId: string]: Ticket; } = {};
|
||||
const state = useTicketStore.getState();
|
||||
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 incrementUnauthMessageApiPage = () => {
|
||||
const state = useTicketStore.getState();
|
||||
|
||||
useTicketStore.setState({ apiPage: state.apiPage + 1 });
|
||||
};
|
||||
|
||||
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
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 "Не удалось отправить файл. Недопустимый тип";
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user