front-hub/src/components/FloatingSupportChat/Chat.tsx

320 lines
7.6 KiB
TypeScript
Raw Normal View History

2023-08-31 10:02:11 +00:00
import {
2023-11-05 23:33:40 +00:00
Box,
FormControl,
IconButton,
InputAdornment,
InputBase,
SxProps,
Theme,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material"
import { TicketMessage } from "@frontend/kitui"
2023-08-31 10:02:11 +00:00
import {
2023-11-05 23:33:40 +00:00
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"
2023-08-31 10:02:11 +00:00
import {
2023-11-05 23:33:40 +00:00
useTicketMessages,
getMessageFromFetchError,
useSSESubscription,
useEventListener,
createTicket,
} from "@frontend/kitui"
import { sendTicketMessage } from "@root/api/ticket"
2023-04-13 16:48:17 +00:00
interface Props {
2023-08-31 10:02:11 +00:00
sx?: SxProps<Theme>;
2023-04-13 16:48:17 +00:00
}
export default function Chat({ sx }: Props) {
2023-11-05 23:33:40 +00:00
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 chatBoxRef = useRef<HTMLDivElement>(null)
2023-06-06 13:13:58 +00:00
2023-11-05 23:33:40 +00:00
useTicketMessages({
url: process.env.REACT_APP_DOMAIN + "/heruvym/getMessages",
2023-11-05 23:33:40 +00:00
isUnauth: true,
ticketId: sessionData?.ticketId,
messagesPerPage,
messageApiPage,
onSuccess: useCallback((messages) => {
if (chatBoxRef.current && chatBoxRef.current.scrollTop < 1)
chatBoxRef.current.scrollTop = 1
addOrUpdateUnauthMessages(messages)
}, []),
onError: useCallback((error: Error) => {
const message = getMessageFromFetchError(error)
if (message) enqueueSnackbar(message)
}, []),
onFetchStateChange: setUnauthTicketMessageFetchState,
})
2023-06-06 13:13:58 +00:00
2023-11-05 23:33:40 +00:00
useSSESubscription<TicketMessage>({
enabled: Boolean(sessionData),
url: process.env.REACT_APP_DOMAIN + `/heruvym/ticket?ticket=${sessionData?.ticketId}&s=${sessionData?.sessionId}`,
2023-11-05 23:33:40 +00:00
onNewData: addOrUpdateUnauthMessages,
onDisconnect: useCallback(() => {
setUnauthIsPreventAutoscroll(false)
}, []),
marker: "ticket",
})
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
const throttledScrollHandler = useMemo(
() =>
throttle(() => {
const chatBox = chatBoxRef.current
if (!chatBox) return
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
const scrollBottom =
chatBox.scrollHeight - chatBox.scrollTop - chatBox.clientHeight
const isPreventAutoscroll = scrollBottom > chatBox.clientHeight
setUnauthIsPreventAutoscroll(isPreventAutoscroll)
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
if (fetchState !== "idle") return
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
if (chatBox.scrollTop < chatBox.clientHeight) {
incrementUnauthMessageApiPage()
}
}, 200),
[fetchState]
)
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
useEventListener("scroll", throttledScrollHandler, chatBoxRef)
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
useEffect(
function scrollOnNewMessage() {
if (!chatBoxRef.current) return
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
if (!isPreventAutoscroll) {
setTimeout(() => {
scrollToBottom()
}, 50)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[lastMessageId]
)
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
async function handleSendMessage() {
if (!messageField || isMessageSending) return
2023-04-13 16:48:17 +00:00
2023-11-05 23:33:40 +00:00
if (!sessionData) {
setIsMessageSending(true)
createTicket({
url: process.env.REACT_APP_DOMAIN + "/heruvym/create",
2023-11-05 23:33:40 +00:00
body: {
Title: "Unauth title",
Message: messageField,
},
useToken: false,
})
.then((response) => {
setUnauthSessionData({
ticketId: response.Ticket,
sessionId: response.sess,
})
})
.catch((error) => {
const errorMessage = getMessageFromFetchError(error)
if (errorMessage) enqueueSnackbar(errorMessage)
})
.finally(() => {
setMessageField("")
setIsMessageSending(false)
})
} else {
setIsMessageSending(true)
2023-04-13 16:48:17 +00:00
2023-11-05 23:33:40 +00:00
const [_, sendTicketMessageError] = await sendTicketMessage(
sessionData.ticketId,
messageField
)
2023-04-13 16:48:17 +00:00
2023-11-05 23:33:40 +00:00
if (sendTicketMessageError) {
enqueueSnackbar(sendTicketMessageError)
}
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
setMessageField("")
setIsMessageSending(false)
}
}
2023-05-03 16:24:15 +00:00
2023-11-05 23:33:40 +00:00
function scrollToBottom(behavior?: ScrollBehavior) {
if (!chatBoxRef.current) return
2023-05-03 09:54:04 +00:00
2023-11-05 23:33:40 +00:00
const chatBox = chatBoxRef.current
chatBox.scroll({
left: 0,
top: chatBox.scrollHeight,
behavior,
})
}
2023-08-31 10:02:11 +00:00
2023-11-05 23:33:40 +00:00
const handleTextfieldKeyPress: React.KeyboardEventHandler<
2023-08-31 10:02:11 +00:00
HTMLInputElement | HTMLTextAreaElement
> = (e) => {
2023-11-05 23:33:40 +00:00
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
}
}
2023-08-31 10:02:11 +00:00
2023-11-05 23:33:40 +00:00
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
height: "clamp(250px, calc(100vh - 90px), 600px)",
backgroundColor: "#944FEE",
borderRadius: "8px",
...sx,
}}
>
<Box
sx={{
display: "flex",
gap: "9px",
pl: "22px",
pt: "12px",
pb: "20px",
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",
}}
>
2023-08-31 10:02:11 +00:00
онлайн-консультант
2023-11-05 23:33:40 +00:00
</Typography>
</Box>
</Box>
<Box
sx={{
flexGrow: 1,
backgroundColor: "white",
borderRadius: "8px",
display: "flex",
flexDirection: "column",
}}
>
<Box
ref={chatBoxRef}
sx={{
display: "flex",
width: "100%",
flexBasis: 0,
flexDirection: "column",
gap: upMd ? "20px" : "16px",
px: upMd ? "20px" : "5px",
py: upMd ? "20px" : "13px",
overflowY: "auto",
flexGrow: 1,
}}
>
{sessionData &&
2023-08-31 10:02:11 +00:00
messages.map((message) => (
2023-11-05 23:33:40 +00:00
<ChatMessage
unAuthenticated
key={message.id}
text={message.message}
createdAt={message.created_at}
isSelf={sessionData.sessionId === message.user_id}
/>
2023-08-31 10:02:11 +00:00
))}
2023-11-05 23:33:40 +00:00
</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
disabled={isMessageSending}
onClick={handleSendMessage}
sx={{
height: "53px",
width: "53px",
mr: "13px",
p: 0,
opacity: isMessageSending ? 0.3 : 1,
}}
>
<SendIcon
style={{
width: "100%",
height: "100%",
}}
/>
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</Box>
</Box>
)
2023-08-29 11:54:35 +00:00
}