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 { useCurrentRouteChatCode } from "~/features/chat/ChatProvider";
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 { useDateTimeFormat } from "~/hooks/intl/useDateTimeFormat";
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 { formatter: headerFormatter } = useDateTimeFormat({
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
});
const { formatter: timestampFormatter } = useDateTimeFormat({
hour: "numeric",
minute: "numeric",
});
const rawRouteChatCode = useCurrentRouteChatCode();
const routeChatCodes = rawRouteChatCode
? Array.isArray(rawRouteChatCode)
? rawRouteChatCode
: [rawRouteChatCode]
: [];
const visibleRooms = chatContext.rooms
.filter(
(room) =>
room.expiresAt > Date.now() || routeChatCodes.includes(room.chatCode),
)
.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 (
{visibleRooms.length === 0 ? (
{t("common:chat.sidebar.noActiveChats")}
) : (
visibleRooms.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 { formatter: headerFormatter } = useDateTimeFormat({
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
});
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) => headerFormatter.format(d) ?? "",
)}
{room?.subtitle ? (
{room.subtitle}
) : null}
>
);
return (
{room?.url ? (
{headerContent}
) : (
{headerContent}
)}
{onClose ? (
) : null}
);
}