front-hub/src/components/FloatingSupportChat/Chat.tsx
2023-05-03 12:59:04 +03:00

227 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Box, FormControl, IconButton, InputAdornment, InputBase, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material";
import { createTicket, getTicketMessages, getUnauthTicket, getUnauthTicketMessages, sendTicketMessage, subscribeToUnauthTicketMessages } from "@root/api/tickets";
import { GetMessagesRequest, TicketMessage } from "@root/model/ticket";
import { useMessageStore } from "@root/stores/messages";
import { addOrUpdateUnauthMessages, setUnauthTicket, setUnauthTicketFetchState, setUnauthTicketSessionId, useUnauthTicketStore } from "@root/stores/unauthTicket";
import { enqueueSnackbar } from "notistack";
import { useEffect, useRef, useState } from "react";
import ChatMessage from "../ChatMessage";
import SendIcon from "../icons/SendIcon";
import UserCircleIcon from "./UserCircleIcon";
interface Props {
sx?: SxProps<Theme>;
}
export default function Chat({ sx }: Props) {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const [messageField, setMessageField] = useState<string>("");
const sessionId = useUnauthTicketStore(state => state.sessionId);
const messages = useUnauthTicketStore(state => state.messages);
const messageApiPage = useUnauthTicketStore(state => state.apiPage);
const messagesPerPage = useUnauthTicketStore(state => state.messagesPerPage);
const messagesFetchStateRef = useRef(useMessageStore.getState().fetchState);
const chatBoxRef = useRef<HTMLDivElement>();
useEffect(function fetchTicketMessages() {
if (!sessionId) return;
const getTicketsBody: GetMessagesRequest = {
amt: messagesPerPage,
page: messageApiPage,
ticket: sessionId,
};
const controller = new AbortController();
setUnauthTicketFetchState("fetching");
getUnauthTicketMessages({
body: getTicketsBody,
signal: controller.signal,
}).then(result => {
console.log("GetMessagesResponse", result);
if (result?.length > 0) {
addOrUpdateUnauthMessages(result);
setUnauthTicketFetchState("idle");
} else setUnauthTicketFetchState("all fetched");
}).catch(error => {
console.log("Error fetching messages", error);
enqueueSnackbar(error.message);
});
return () => {
controller.abort();
};
}, [messageApiPage, messagesPerPage, sessionId]);
useEffect(function subscribeToMessages() {
if (!sessionId) return;
const unsubscribe = subscribeToUnauthTicketMessages({
sessionId,
onMessage(event) {
try {
const newMessage = JSON.parse(event.data) as TicketMessage;
console.log("SSE: parsed newMessage:", newMessage);
if (!newMessage.id) throw new Error("Bad SSE response");
addOrUpdateUnauthMessages([newMessage]);
} catch (error) {
console.log("SSE: couldn't parse:", event.data);
console.log("Error parsing SSE message", error);
}
},
onError(event) {
console.log("SSE Error:", event);
},
});
return () => {
unsubscribe();
// clearUnauthTicketState();
};
}, [sessionId]);
useEffect(() => useMessageStore.subscribe(state => (messagesFetchStateRef.current = state.fetchState)), []);
async function handleSendMessage() {
if (!messageField) return;
if (!sessionId) {
const response = await createTicket({
Title: "Unauth title",
Message: messageField,
}, false);
setUnauthTicketSessionId(response.sess);
} else {
sendTicketMessage({
ticket: sessionId,
message: messageField,
lang: "ru",
files: [],
}, true);
}
setMessageField("");
}
const handleTextfieldKeyPress: React.KeyboardEventHandler<HTMLInputElement | HTMLTextAreaElement> = (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
return (
<Box sx={{
display: "flex",
flexDirection: "column",
width: "400px",
height: "600px",
backgroundColor: "#944FEE",
borderRadius: "8px",
...sx,
}}>
<Box sx={{
display: "flex",
gap: "9px",
pl: "22px",
pt: "12px",
filter: "drop-shadow(0px 3px 12px rgba(37, 39, 52, 0.3))",
}}>
<UserCircleIcon />
<Box sx={{
mt: "5px",
display: "flex",
flexDirection: "column",
gap: "3px",
}}>
<Typography>Мария</Typography>
<Typography sx={{
fontSize: "16px",
lineHeight: "19px",
}}>онлайн-консультант</Typography>
</Box>
</Box>
<Box sx={{
height: "520px",
backgroundColor: "white",
mt: "auto",
borderRadius: "8px",
display: "flex",
flexDirection: "column",
}}>
<Box
ref={chatBoxRef}
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
gap: upMd ? "20px" : "16px",
px: upMd ? "20px" : "5px",
py: upMd ? "20px" : "13px",
overflowY: "auto",
flexGrow: 1,
}}
>
{messages.map((message) => (
<ChatMessage
unAuthenticated
key={message.id}
text={message.message}
time={new Date(message.created_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
isSelf={sessionId === message.user_id}
/>
))}
</Box>
<FormControl fullWidth sx={{ borderTop: "1px solid black" }}>
<InputBase
value={messageField}
fullWidth
placeholder="Введите сообщение..."
id="message"
multiline
onKeyDown={handleTextfieldKeyPress}
sx={{
width: "100%",
p: 0,
}}
inputProps={{
sx: {
fontWeight: 400,
fontSize: "16px",
lineHeight: "19px",
pt: upMd ? "30px" : "28px",
pb: upMd ? "30px" : "24px",
px: "19px",
maxHeight: "calc(19px * 5)",
color: "black",
},
}}
onChange={(e) => setMessageField(e.target.value)}
endAdornment={
<InputAdornment position="end">
<IconButton
onClick={handleSendMessage}
sx={{
height: "53px",
width: "53px",
mr: "13px",
p: 0,
}}
>
<SendIcon style={{
width: "100%",
height: "100%",
}} />
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</Box>
</Box>
);
}