import clsx from "clsx"; import { ArrowLeft, MessageSquare, X } from "lucide-react"; import { Button } from "react-aria-components"; import { useTranslation } from "react-i18next"; import { Link, useFetcher } from "react-router"; import { extractRoomLink, isMatchRoomUrl, } from "~/features/chat/chat-constants"; import { resolveDatePlaceholders } from "~/features/chat/chat-utils"; import { Chat } from "~/features/chat/components/Chat"; import { useChatContext } from "~/features/chat/useChatContext"; import { useTimeFormat } from "~/hooks/useTimeFormat"; import sideNavStyles from "../SideNav.module.css"; import styles from "./ChatSidebar.module.css"; export function ChatSidebar({ onClose }: { onClose?: () => void }) { const chatContext = useChatContext(); if (!chatContext) return null; if (chatContext.activeRoom) { return ; } if (chatContext.isLoading) { return ; } return ; } function SidebarHeader({ onClose }: { onClose?: () => void }) { const { t } = useTranslation(["common"]); return (

{t("common:chat.sidebar.title")}

{onClose ? ( ) : null}
); } function LoadingState({ onClose }: { onClose?: () => void }) { const { t } = useTranslation(["common"]); return (
{t("common:chat.connecting")}
); } function RoomList({ onClose }: { onClose?: () => void }) { const { t } = useTranslation(["common"]); const chatContext = useChatContext()!; const { formatDateTime } = useTimeFormat(); const nonExpiredRooms = chatContext.rooms .filter((room) => room.expiresAt > Date.now()) .sort((a, b) => { if (a.isObsolete !== b.isObsolete) return a.isObsolete ? 1 : -1; const aRecency = a.lastMessageTimestamp || a.createdAt; const bRecency = b.lastMessageTimestamp || b.createdAt; return bRecency - aRecency; }); return (
{nonExpiredRooms.length === 0 ? (
{t("common:chat.sidebar.noActiveChats")}
) : ( nonExpiredRooms.map((room) => { const unread = chatContext.unreadCounts[room.chatCode] ?? 0; return ( ); }) )}
); } function ChatView({ onClose }: { onClose?: () => void }) { const { t } = useTranslation(["common"]); const chatContext = useChatContext()!; const activeRoom = chatContext.activeRoom!; const { formatDateTime } = useTimeFormat(); const otherRoomsUnreadCount = Object.entries(chatContext.unreadCounts) .filter(([code]) => code !== activeRoom) .reduce((sum, [, count]) => sum + count, 0); const roomLinkFetcher = useFetcher(); const room = chatContext.rooms.find((r) => r.chatCode === activeRoom); const roomExpired = Boolean(room?.expiresAt && room.expiresAt < Date.now()); const messages = chatContext.messagesForRoom(activeRoom); const participantIds = new Set(room?.participantUserIds ?? []); const usersWithLabels = { ...chatContext.chatUsers }; for (const [userIdStr, label] of Object.entries(chatContext.chatLabels)) { const userId = Number(userIdStr); if (participantIds.has(userId)) continue; const existing = usersWithLabels[userId]; if (existing) { usersWithLabels[userId] = { ...existing, title: label }; } } const isMatchRoom = room?.url ? isMatchRoomUrl(room.url) : false; const chatAdapter = { messages, send: (contents: string) => { chatContext.send(activeRoom, contents); if (isMatchRoom) { const link = extractRoomLink(contents); if (link) { roomLinkFetcher.submit( { _action: "UPSERT", url: link }, { method: "post", action: "/room", encType: "application/json", }, ); } } }, currentRoom: activeRoom, setCurrentRoom: () => {}, readyState: chatContext.readyState, unseenMessages: new Map(), }; const handleBack = () => { chatContext.setActiveRoom(null); }; const headerContent = ( <> {room?.imageUrl ? ( ) : null}
{resolveDatePlaceholders( room?.header ?? t("common:chat.sidebar.title"), (d) => formatDateTime(d, { month: "short", day: "numeric", hour: "numeric", minute: "numeric", }), )} {room?.subtitle ? ( {room.subtitle} ) : null}
); return (
{room?.url ? ( {headerContent} ) : (
{headerContent}
)} {onClose ? ( ) : null}
); }