import React from "react"; import { Box, Flex, Skeleton } from "@chakra-ui/react"; import type { Track, ChatMessage } from "./Api"; import { Api, consumeChatAdminControl } from "./Api"; import { Colors } from "./theme"; import { useChat } from "./ChatProvider"; import { ChatLog } from "./ChatLog"; import type { ChatStatus, ChatUpdate } from "./ChatSession"; import { ChatStatusView } from "./ChatStatusView"; import { ChatHistoryView } from "./ChatHistoryView"; import { ChatForm } from "./ChatForm"; export interface Props { track: Track; } export type ChatSessionStatusTuple = [ChatStatus | undefined, Error | null | undefined]; type ChatHistoryLoadingStatus = | { status: "LOADING"; error?: null } | { status: "LOADED"; error?: null } | { status: "ERRORED"; error: Error }; export const TrackChat: React.FC<Props> = ({ track }) => { const chat = useChat(); const { data: session } = Api.useSession(); const { data: chatMessagePin } = Api.useChatMessagePin(track.slug); const [[chatSessionStatus, chatSessionError], setChatSessionStatusTuple] = React.useState<ChatSessionStatusTuple>([ chat?.session?.status, chat?.session?.error, ]); const [isLoadingHistory, setIsLoadingHistory] = React.useState<ChatHistoryLoadingStatus>({ status: "LOADING" }); const [chatLog] = React.useState(new ChatLog()); const [chatHistory, setChatHistory] = React.useState<ChatMessage[]>([]); const trackChannel = track.chat ? chat.tracks?.[track.slug]?.channel_arn ?? null : null; const [chatCallbacks] = React.useState({ onStatus(status: ChatStatus, error: Error | null) { console.log("onStatusChange", status, error); setChatSessionStatusTuple([status, error]); }, onMessage(update: ChatUpdate) { const adminControl = update.message?.adminControl; if (adminControl) { consumeChatAdminControl(adminControl); } chatLog.append(update); }, }); React.useEffect(() => { chatLog.onUpdate = (h) => setChatHistory(h); }, [chatLog]); React.useEffect(() => { if (!chat.session) return; return chat.session.subscribeStatus(chatCallbacks.onStatus); }, [chat.session]); React.useEffect(() => { if (!chat.session) return; if (!trackChannel) return; console.log("TrackChat: subscribeMessageUpdate"); const unsubscribe = chat.session.subscribeMessageUpdate(trackChannel, chatCallbacks.onMessage); return () => { console.log("TrackChat: subscribeMessageUpdate; unsubscribing"); unsubscribe(); }; }, [chat.session, trackChannel]); React.useEffect(() => { if (!chat.session) return; if (!chatSessionStatus || chatSessionStatus === "INIT") return; if (!trackChannel) return; const onChatHistory = (messages: ChatMessage[]) => { console.log("onChatHistory", messages); setIsLoadingHistory({ status: "LOADED" }); chatLog.reverseMerge(messages); }; chat.session .getHistory(trackChannel) .then(onChatHistory) .catch((e) => { console.error(e); setIsLoadingHistory({ status: "ERRORED", error: e }); }); }, [chat.session, chatSessionStatus, trackChannel]); if (!track.chat) { console.warn("TrackChat: NO TRACK CHAT PRESENT"); return <></>; } // TODO: disable ChatForm until obtain session return ( <Flex direction="column" h="100%" w="100%" border="1px solid" borderColor={Colors.chatBorder2}> <ChatStatusView status={chatSessionStatus} loading={isLoadingHistory.status === "LOADING"} error={chatSessionError || isLoadingHistory.error} /> <Box flexGrow={1} flexShrink={0} flexBasis={0} w="100%" overflowX="hidden" overflowY="hidden"> {trackChannel ? ( <ChatHistoryView track={track} messages={chatHistory} loading={isLoadingHistory.status === "LOADING"} showAdminActions={session?.attendee?.is_staff ?? false} pinnedMessage={chatMessagePin?.pin?.message ?? null} /> ) : ( <Skeleton flexGrow={1} flexShrink={0} flexBasis={0} /> )} </Box> <Box w="100%"> <ChatForm track={track} channel={trackChannel} /> </Box> </Flex> ); }; export default TrackChat;