отправка и получение медиа
This commit is contained in:
parent
f497252d6b
commit
4ad467ae7b
@ -23,6 +23,7 @@ import DiscountManagement from "@root/pages/dashboard/Content/DiscountManagement
|
|||||||
import { PromocodeManagement } from "@root/pages/dashboard/Content/PromocodeManagement";
|
import { PromocodeManagement } from "@root/pages/dashboard/Content/PromocodeManagement";
|
||||||
import { SettingRoles } from "@pages/Setting/SettingRoles";
|
import { SettingRoles } from "@pages/Setting/SettingRoles";
|
||||||
import Support from "@pages/dashboard/Content/Support/Support";
|
import Support from "@pages/dashboard/Content/Support/Support";
|
||||||
|
import ChatImageNewWindow from "@pages/dashboard/Content/Support/ChatImageNewWindow";
|
||||||
|
|
||||||
import theme from "./theme";
|
import theme from "./theme";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
@ -111,6 +112,7 @@ root.render(
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||||
|
|
||||||
<Route path="*" element={<Error404 />} />
|
<Route path="*" element={<Error404 />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
@ -9,8 +9,39 @@ import { TicketMessage } from "@root/model/ticket";
|
|||||||
import { sendTicketMessage } from "@root/api/tickets";
|
import { sendTicketMessage } from "@root/api/tickets";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useTicketStore } from "@root/stores/tickets";
|
import { useTicketStore } from "@root/stores/tickets";
|
||||||
import { getMessageFromFetchError, throttle, useEventListener, useSSESubscription, useTicketMessages, useToken } from "@frontend/kitui";
|
import { getMessageFromFetchError, makeRequest, throttle, useEventListener, useSSESubscription, useTicketMessages, useToken } from "@frontend/kitui";
|
||||||
|
import ChatImage from "./ChatImage";
|
||||||
|
import ChatDocument from "./ChatDocument";
|
||||||
|
import ChatVideo from "./ChatVideo";
|
||||||
|
import ChatMessage from "./ChatMessage";
|
||||||
|
import { ACCEPT_SEND_MEDIA_TYPES_MAP, MAX_FILE_SIZE, MAX_PHOTO_SIZE, MAX_VIDEO_SIZE } from "./fileUpload";
|
||||||
|
|
||||||
|
const tooLarge = "Файл слишком большой"
|
||||||
|
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 tooLarge
|
||||||
|
return ""
|
||||||
|
|
||||||
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.picture.find(name => name === type):
|
||||||
|
if (file.size > MAX_PHOTO_SIZE) return tooLarge
|
||||||
|
return ""
|
||||||
|
|
||||||
|
case ACCEPT_SEND_MEDIA_TYPES_MAP.video.find(name => name === type):
|
||||||
|
if (file.size > MAX_VIDEO_SIZE) return tooLarge
|
||||||
|
return ""
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "Не удалось отправить файл. Недопустимый тип"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat() {
|
||||||
const token = useToken();
|
const token = useToken();
|
||||||
@ -26,6 +57,8 @@ export default function Chat() {
|
|||||||
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
|
const isPreventAutoscroll = useMessageStore(state => state.isPreventAutoscroll);
|
||||||
const fetchState = useMessageStore(state => state.ticketMessagesFetchState);
|
const fetchState = useMessageStore(state => state.ticketMessagesFetchState);
|
||||||
const lastMessageId = useMessageStore(state => state.lastMessageId);
|
const lastMessageId = useMessageStore(state => state.lastMessageId);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [disableFileButton, setDisableFileButton] = useState(false);
|
||||||
|
|
||||||
const ticket = tickets.find(ticket => ticket.id === ticketId);
|
const ticket = tickets.find(ticket => ticket.id === ticketId);
|
||||||
|
|
||||||
@ -107,7 +140,47 @@ export default function Chat() {
|
|||||||
setMessageField("");
|
setMessageField("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddAttachment() { }
|
const sendFile = async (file: File) => {
|
||||||
|
if (file === undefined) return true;
|
||||||
|
|
||||||
|
// const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.some(
|
||||||
|
// fileType => file.name.toLowerCase().endsWith(fileType)
|
||||||
|
// );
|
||||||
|
// console.log(file.name.toLowerCase().endsWith(".png"))
|
||||||
|
// if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
||||||
|
let data;
|
||||||
|
|
||||||
|
const ticketId = ticket?.id
|
||||||
|
if (ticketId !== undefined) {
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
function handleTextfieldKeyPress(e: KeyboardEvent) {
|
||||||
if (e.key === "Enter" && !e.shiftKey) {
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
@ -145,9 +218,66 @@ export default function Chat() {
|
|||||||
colorScheme: "dark",
|
colorScheme: "dark",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ticket && messages.map(message =>
|
{ticket &&
|
||||||
<Message key={message.id} message={message} isSelf={ticket.user !== message.user_id} />
|
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
|
||||||
|
unAuthenticated
|
||||||
|
key={message.id}
|
||||||
|
text={message.message}
|
||||||
|
createdAt={message.created_at}
|
||||||
|
isSelf={ticket.user !== message.user_id}
|
||||||
|
/>
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
{ticket &&
|
{ticket &&
|
||||||
<TextField
|
<TextField
|
||||||
@ -177,13 +307,25 @@ export default function Chat() {
|
|||||||
<SendIcon sx={{ color: theme.palette.golden.main }} />
|
<SendIcon sx={{ color: theme.palette.golden.main }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleAddAttachment}
|
onClick={() => {
|
||||||
|
console.log(disableFileButton)
|
||||||
|
if (!disableFileButton) fileInputRef.current?.click()
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
height: "45px",
|
height: "45px",
|
||||||
width: "45px",
|
width: "45px",
|
||||||
p: 0,
|
p: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
id="fileinput"
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.files?.[0]) sendFileHC(e.target.files?.[0]);
|
||||||
|
}}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
<AttachFileIcon sx={{ color: theme.palette.golden.main }} />
|
<AttachFileIcon sx={{ color: theme.palette.golden.main }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
|
61
src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx
Normal file
61
src/pages/dashboard/Content/Support/Chat/ChatDocument.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import DownloadIcon from '@mui/icons-material/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 date = new Date(createdAt);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: "12px",
|
||||||
|
alignSelf: "end",
|
||||||
|
}}>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#2a2b2c",
|
||||||
|
p: "12px",
|
||||||
|
border: `1px solid ${theme.palette.golden.main}`,
|
||||||
|
borderRadius: "20px",
|
||||||
|
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||||
|
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||||
|
maxWidth: "90%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
|
||||||
|
download
|
||||||
|
href={`https://storage.yandexcloud.net/pair/${file}`}
|
||||||
|
style={{
|
||||||
|
color: "#7E2AEA",
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadIcon/>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
67
src/pages/dashboard/Content/Support/Chat/ChatImage.tsx
Normal file
67
src/pages/dashboard/Content/Support/Chat/ChatImage.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ButtonBase,
|
||||||
|
Link,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: "12px",
|
||||||
|
alignSelf: "end",
|
||||||
|
}}>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#2a2b2c",
|
||||||
|
p: "12px",
|
||||||
|
border: `1px solid ${theme.palette.golden.main}`,
|
||||||
|
borderRadius: "20px",
|
||||||
|
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||||
|
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||||
|
maxWidth: "90%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ButtonBase target="_blank" href={`/image/${file}`}>
|
||||||
|
<Box
|
||||||
|
component="img"
|
||||||
|
sx={{
|
||||||
|
height: "217px",
|
||||||
|
width: "217px",
|
||||||
|
}}
|
||||||
|
src={`https://storage.yandexcloud.net/pair/${file}`}
|
||||||
|
/>
|
||||||
|
</ButtonBase>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
52
src/pages/dashboard/Content/Support/Chat/ChatMessage.tsx
Normal file
52
src/pages/dashboard/Content/Support/Chat/ChatMessage.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
unAuthenticated?: boolean;
|
||||||
|
isSelf: boolean;
|
||||||
|
text: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatMessage({
|
||||||
|
unAuthenticated = false,
|
||||||
|
isSelf,
|
||||||
|
text,
|
||||||
|
createdAt,
|
||||||
|
}: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const upMd = useMediaQuery(theme.breakpoints.up("md"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: "12px",
|
||||||
|
alignSelf: "end",
|
||||||
|
}}>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#2a2b2c",
|
||||||
|
p: "12px",
|
||||||
|
border: `1px solid ${theme.palette.golden.main}`,
|
||||||
|
borderRadius: "20px",
|
||||||
|
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||||
|
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||||
|
maxWidth: "90%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography fontSize="14px" sx={{ wordBreak: "break-word" }}>
|
||||||
|
{text}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
68
src/pages/dashboard/Content/Support/Chat/ChatVideo.tsx
Normal file
68
src/pages/dashboard/Content/Support/Chat/ChatVideo.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
ButtonBase,
|
||||||
|
Link,
|
||||||
|
Typography,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "9px",
|
||||||
|
padding: isSelf ? "0 8px 0 0" : "0 0 0 8px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography sx={{
|
||||||
|
fontSize: "12px",
|
||||||
|
alignSelf: "end",
|
||||||
|
}}>
|
||||||
|
{new Date(createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#2a2b2c",
|
||||||
|
p: "12px",
|
||||||
|
border: `1px solid ${theme.palette.golden.main}`,
|
||||||
|
borderRadius: "20px",
|
||||||
|
borderTopLeftRadius: isSelf ? "20px" : 0,
|
||||||
|
borderTopRightRadius: isSelf ? 0 : "20px",
|
||||||
|
maxWidth: "90%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="video"
|
||||||
|
sx={{
|
||||||
|
height: "217px",
|
||||||
|
width: "217px",
|
||||||
|
}}
|
||||||
|
controls
|
||||||
|
>
|
||||||
|
<source src={`https://storage.yandexcloud.net/pair/${file}`} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
9
src/pages/dashboard/Content/Support/Chat/fileUpload.ts
Normal file
9
src/pages/dashboard/Content/Support/Chat/fileUpload.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const MAX_FILE_SIZE = 10485760;
|
||||||
|
export const MAX_PHOTO_SIZE = 5242880;
|
||||||
|
export 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;
|
20
src/pages/dashboard/Content/Support/ChatImageNewWindow.tsx
Normal file
20
src/pages/dashboard/Content/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}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user