import { lazy, type ReactNode, Suspense, useCallback, useMemo, useState, } from "react"; import { useSearchParams } from "react-router"; import { useAuth } from "../../hooks/auth/useAuth.ts"; import { ChatContext, type ChatContextValue } from "./ChatContext.ts"; const ChatModal = lazy(() => import("../../components/chat/ChatModal.tsx").then((m) => ({ default: m.ChatModal, })) ); /** * Owns the single live-chat modal instance so the header button, the floating * button, and notification deep-links all drive one modal. Open state is derived * (no effects): a manual open, or a `?chat=open` query param (used by mention * notifications), opens it — and it's only ever open for signed-in users. */ export function ChatProvider({ children }: { children: ReactNode }) { const { user } = useAuth(); const [manuallyOpen, setManuallyOpen] = useState(false); const [searchParams, setSearchParams] = useSearchParams(); const deepLinkOpen = searchParams.get("chat") === "open"; const isChatOpen = !!user && (manuallyOpen || deepLinkOpen); const openChat = useCallback(() => setManuallyOpen(true), []); const closeChat = useCallback(() => { setManuallyOpen(false); // Strip the deep-link param so a reload/back-nav doesn't reopen the modal. if (searchParams.get("chat") === "open") { const next = new URLSearchParams(searchParams); next.delete("chat"); setSearchParams(next, { replace: true }); } }, [searchParams, setSearchParams]); const value: ChatContextValue = useMemo( () => ({ isChatOpen, openChat, closeChat }), [isChatOpen, openChat, closeChat], ); return ( {children} {isChatOpen && ( )} ); }