diff --git a/apps/edr-freight-web/portal/src/components/AppLayout.tsx b/apps/edr-freight-web/portal/src/components/AppLayout.tsx index 901c492c8..35641f9fc 100644 --- a/apps/edr-freight-web/portal/src/components/AppLayout.tsx +++ b/apps/edr-freight-web/portal/src/components/AppLayout.tsx @@ -35,6 +35,7 @@ import { import { type CSSProperties, Fragment, type ReactNode, useState } from "react"; import { PROFILE_TYPE_LABELS } from "@/constants/profileMode"; import NotificationBellContainer from "@/features/notifications/NotificationBellContainer"; +import SupportWidget from "@/features/support/SupportWidget"; export interface SidebarItem { label: string; @@ -781,6 +782,9 @@ export function AppLayout({ {children} + {/* Floating customer-support chat launcher. */} + + {/* Create-profile modal β€” opens when switching to a mode the company doesn't have a profile for yet. */} void; +} + +/** + * The whole support surface: one ongoing thread with the EDR team. There is + * nothing to pick and nothing to open β€” the company has a single conversation, + * created server-side the moment the first message is sent. + */ +export function SupportPanel({ onClose }: SupportPanelProps) { + const { data: conversation } = useConversation(); + const { data: messages, isLoading } = useMessages(); + const send = useSendMessage(); + const markRead = useMarkConversationRead(); + const [draft, setDraft] = useState(""); + const viewport = useRef(null); + + // Mark read on open + whenever new messages arrive. No-op before the thread + // exists, so opening the panel never creates one. + useEffect(() => { + if (conversation) markRead.mutate(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [conversation?.id, messages?.length]); + + // Auto-scroll to newest. + useEffect(() => { + viewport.current?.scrollTo({ top: viewport.current.scrollHeight }); + }, [messages?.length]); + + const submit = async () => { + const body = draft.trim(); + if (!body) return; + setDraft(""); + await send.mutateAsync(body); + }; + + const isEmpty = !isLoading && (messages ?? []).length === 0; + + return ( + + + + + + + + + Support + + + We usually reply within a few minutes + + + + + + + + + + {isLoading ? ( + + + + ) : isEmpty ? ( + + + + + + Send us a message and our team will help you out. + + + ) : ( + + {(messages ?? []).map((m) => ( + + ))} + + )} + + + + +