From d884cb3f3b48b8b2ad131896dafd8abb54fc3a3a Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:59:41 +0200 Subject: [PATCH] Chat fixes --- app/features/chat/ChatProvider.tsx | 90 +++++++++++++------ .../scrims/loaders/scrims.$id.server.ts | 2 + .../loaders/q.match.$id.server.ts | 2 + .../sendouq/loaders/q.looking.server.ts | 2 + .../loaders/to.$id.matches.$mid.server.ts | 2 + 5 files changed, 70 insertions(+), 28 deletions(-) diff --git a/app/features/chat/ChatProvider.tsx b/app/features/chat/ChatProvider.tsx index a095a6921..0879f48a1 100644 --- a/app/features/chat/ChatProvider.tsx +++ b/app/features/chat/ChatProvider.tsx @@ -4,7 +4,11 @@ import * as React from "react"; import { useMatches, useRevalidator } from "react-router"; import { logger } from "~/utils/logger"; import { soundPath } from "~/utils/urls"; -import type { RoomInfo, ServerRoomInfo } from "./chat-provider-types"; +import type { + RoomInfo, + RoomMetadata, + ServerRoomInfo, +} from "./chat-provider-types"; import type { ChatMessage, ChatUser } from "./chat-types"; import { messageTypeToSound, soundEnabled, soundVolume } from "./chat-utils"; import { ChatContext } from "./useChatContext"; @@ -161,7 +165,7 @@ function ChatProviderInner({ return; } - // CHAT_HISTORY response + // CHAT_HISTORY response (also returned by SUBSCRIBE with metadata) if (parsed.event === "CHAT_HISTORY" && Array.isArray(parsed.messages)) { const chatCode = parsed.chatCode as string; const messages = parsed.messages as ChatMessage[]; @@ -169,6 +173,30 @@ function ChatProviderInner({ ...prev, [chatCode]: messages, })); + + if (parsed.metadata) { + const metadata = parsed.metadata as RoomMetadata; + const newRoom: RoomInfo = { + chatCode, + header: metadata.header, + subtitle: metadata.subtitle ?? "", + url: metadata.url ?? "", + participantUserIds: metadata.participantUserIds, + expiresAt: metadata.expiresAt, + lastMessageTimestamp: messages.at(-1)?.timestamp ?? 0, + totalMessageCount: messages.length, + }; + setRooms((prev) => { + const exists = prev.some((r) => r.chatCode === chatCode); + if (exists) return prev; + return [...prev, newRoom]; + }); + + if (metadata.chatUsers) { + setChatUsersCache((prev) => ({ ...prev, ...metadata.chatUsers })); + } + } + return; } @@ -379,6 +407,8 @@ function ChatProviderInner({ setSidebarOpen, subscribe, unsubscribe, + setRooms, + setMessagesByRoom, }); const contextValue = React.useMemo( @@ -422,6 +452,9 @@ function ChatProviderInner({ ); } +// xxx: bug: when viewing as non-participant can't go back to list view +// xxx: bug: room should close automatically if non-participant and leaves the route +// xxx: bug: route loading state should show before room metadata loads function useChatRouteSync({ rooms, userId, @@ -429,6 +462,8 @@ function useChatRouteSync({ setSidebarOpen, subscribe, unsubscribe, + setRooms, + setMessagesByRoom, }: { rooms: RoomInfo[]; userId: number; @@ -436,6 +471,10 @@ function useChatRouteSync({ setSidebarOpen: (open: boolean) => void; subscribe: (chatCode: string) => void; unsubscribe: (chatCode: string) => void; + setRooms: React.Dispatch>; + setMessagesByRoom: React.Dispatch< + React.SetStateAction> + >; }) { const matches = useMatches(); const subscribedRoomRef = React.useRef(null); @@ -451,38 +490,31 @@ function useChatRouteSync({ } } - if (!chatCode) { - // Leaving a chat route: unsubscribe from staff-subscribed rooms - if (subscribedRoomRef.current) { - unsubscribe(subscribedRoomRef.current); - subscribedRoomRef.current = null; - } - return; + const previousSubscribed = subscribedRoomRef.current; + + // Clean up previous non-participant subscription if chatCode changed + if (previousSubscribed && previousSubscribed !== chatCode) { + unsubscribe(previousSubscribed); + setRooms((prev) => prev.filter((r) => r.chatCode !== previousSubscribed)); + setMessagesByRoom((prev) => { + const { [previousSubscribed]: _, ...rest } = prev; + return rest; + }); + subscribedRoomRef.current = null; } + if (!chatCode) return; + const room = rooms.find((r) => r.chatCode === chatCode); + const isParticipant = room?.participantUserIds.includes(userId); - if (room) { - const isParticipant = room.participantUserIds.includes(userId); - - if (isParticipant) { - setActiveRoom(chatCode); - setSidebarOpen(true); - } else { - // Staff/TO viewing: subscribe temporarily - subscribe(chatCode); - subscribedRoomRef.current = chatCode; - setActiveRoom(chatCode); - setSidebarOpen(true); - } + if (!isParticipant && subscribedRoomRef.current !== chatCode) { + subscribe(chatCode); + subscribedRoomRef.current = chatCode; } - return () => { - if (subscribedRoomRef.current) { - unsubscribe(subscribedRoomRef.current); - subscribedRoomRef.current = null; - } - }; + setActiveRoom(chatCode); + setSidebarOpen(true); }, [ matches, rooms, @@ -491,5 +523,7 @@ function useChatRouteSync({ setSidebarOpen, subscribe, unsubscribe, + setRooms, + setMessagesByRoom, ]); } diff --git a/app/features/scrims/loaders/scrims.$id.server.ts b/app/features/scrims/loaders/scrims.$id.server.ts index bf2df6121..e19be6555 100644 --- a/app/features/scrims/loaders/scrims.$id.server.ts +++ b/app/features/scrims/loaders/scrims.$id.server.ts @@ -45,6 +45,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { return { post, + // xxx: only chatCode if permissions + chatCode: post.chatCode, anyUserPrefersNoScreen: await UserRepository.anyUserPrefersNoScreen(participantIds), tournamentMapPool: post.mapsTournament diff --git a/app/features/sendouq-match/loaders/q.match.$id.server.ts b/app/features/sendouq-match/loaders/q.match.$id.server.ts index dd272dd8e..f268e8ff3 100644 --- a/app/features/sendouq-match/loaders/q.match.$id.server.ts +++ b/app/features/sendouq-match/loaders/q.match.$id.server.ts @@ -67,5 +67,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { }) : null, rawReportedWeapons, + // xxx: only chatCode if permissions + chatCode: match.chatCode, }; }; diff --git a/app/features/sendouq/loaders/q.looking.server.ts b/app/features/sendouq/loaders/q.looking.server.ts index cba5c0b60..9389b54f1 100644 --- a/app/features/sendouq/loaders/q.looking.server.ts +++ b/app/features/sendouq/loaders/q.looking.server.ts @@ -57,6 +57,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { ? [] : groups, ownGroup, + // xxx: only chatCode if permissions + chatCode: ownGroup?.chatCode, likes: ownGroup ? await SQGroupRepository.allLikesByGroupId(ownGroup.id) : { diff --git a/app/features/tournament-bracket/loaders/to.$id.matches.$mid.server.ts b/app/features/tournament-bracket/loaders/to.$id.matches.$mid.server.ts index 7fe5ddd70..4c5b9c297 100644 --- a/app/features/tournament-bracket/loaders/to.$id.matches.$mid.server.ts +++ b/app/features/tournament-bracket/loaders/to.$id.matches.$mid.server.ts @@ -119,5 +119,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { matchIsOver, endedEarly, noScreen, + // xxx: only chatCode if permissions + chatCode: match.chatCode, }; };