From a2a2de9bcb835047714e8ae1c4b8c419883ecf89 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 12 Mar 2026 19:37:42 +0200 Subject: [PATCH] Chat sidebar work --- app/components/layout/ChatSidebar.module.css | 31 ++++ app/components/layout/ChatSidebar.tsx | 23 ++- app/components/layout/TopRightButtons.tsx | 51 ++++-- app/features/chat/ChatProvider.tsx | 146 ++++++++++++------ app/features/chat/ChatSystemMessage.server.ts | 19 ++- app/features/chat/chat-constants.ts | 6 - app/features/chat/routes/api.chat-users.ts | 1 - .../scrims/loaders/scrims.$id.server.ts | 12 +- .../loaders/q.match.$id.server.ts | 25 ++- .../sendouq/loaders/q.looking.server.ts | 16 +- .../loaders/to.$id.matches.$mid.server.ts | 34 ++-- app/utils/logger.ts | 1 + 12 files changed, 231 insertions(+), 134 deletions(-) diff --git a/app/components/layout/ChatSidebar.module.css b/app/components/layout/ChatSidebar.module.css index 654690937..a64593068 100644 --- a/app/components/layout/ChatSidebar.module.css +++ b/app/components/layout/ChatSidebar.module.css @@ -160,6 +160,29 @@ flex-shrink: 0; } +.chatHeaderInfo { + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; +} + +.chatHeaderLink { + display: flex; + flex-direction: column; + overflow: hidden; + min-width: 0; + text-decoration: none; + color: inherit; + padding: var(--s-1) var(--s-2); + border-radius: var(--radius-field); + transition: background-color 0.15s; +} + +.chatHeaderLink:hover { + background-color: var(--color-bg-higher); +} + .chatHeaderTitle { font-size: var(--font-xs); font-weight: var(--weight-semi); @@ -168,6 +191,14 @@ white-space: nowrap; } +.chatHeaderSubtitle { + font-size: var(--font-2xs); + color: var(--color-text-high); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .emptyState { padding: var(--s-4); text-align: center; diff --git a/app/components/layout/ChatSidebar.tsx b/app/components/layout/ChatSidebar.tsx index 3becfd617..0ee482f32 100644 --- a/app/components/layout/ChatSidebar.tsx +++ b/app/components/layout/ChatSidebar.tsx @@ -1,12 +1,12 @@ import { ArrowLeft, MessageSquare, X } from "lucide-react"; import { Button } from "react-aria-components"; import { useTranslation } from "react-i18next"; +import { Link } from "react-router"; import { Chat } from "~/features/chat/components/Chat"; import { useChatContext } from "~/features/chat/useChatContext"; import { useTimeFormat } from "~/hooks/useTimeFormat"; import styles from "./ChatSidebar.module.css"; -// xxx: also make it so that if only one room then we go directly to the chat view instead of showing the room list export function ChatSidebar({ onClose }: { onClose?: () => void }) { const chatContext = useChatContext(); @@ -103,15 +103,30 @@ function ChatView({ onClose }: { onClose?: () => void }) { chatContext.setActiveRoom(null); }; + const headerContent = ( + <> + + {room?.header ?? activeRoom} + + {room?.subtitle ? ( + {room.subtitle} + ) : null} + + ); + return (
- - {room?.header ?? activeRoom} - + {room?.url ? ( + + {headerContent} + + ) : ( +
{headerContent}
+ )} {onClose ? (
); } + +function ChatButton({ + variant, + onPress, + unreadCount, +}: { + variant: "outlined" | "primary"; + onPress: () => void; + unreadCount?: number; +}) { + return ( + <> + } + variant={variant} + onPress={onPress} + /> + {unreadCount ? ( + {unreadCount} + ) : null} + + ); +} diff --git a/app/features/chat/ChatProvider.tsx b/app/features/chat/ChatProvider.tsx index 0879f48a1..130b2cc37 100644 --- a/app/features/chat/ChatProvider.tsx +++ b/app/features/chat/ChatProvider.tsx @@ -1,7 +1,7 @@ import { nanoid } from "nanoid"; import { WebSocket } from "partysocket"; import * as React from "react"; -import { useMatches, useRevalidator } from "react-router"; +import { useFetcher, useMatches, useRevalidator } from "react-router"; import { logger } from "~/utils/logger"; import { soundPath } from "~/utils/urls"; import type { @@ -15,7 +15,6 @@ import { ChatContext } from "./useChatContext"; const PING_INTERVAL_MS = 60_000; const LOCAL_STORAGE_PREFIX = "chat_read__"; -const USER_FETCH_BATCH_DELAY_MS = 200; function flattenServerRoom(serverRoom: ServerRoomInfo): RoomInfo { return { @@ -91,9 +90,6 @@ function ChatProviderInner({ >({}); const ws = React.useRef(undefined); - const pendingUserFetches = React.useRef>(new Set()); - const fetchTimeoutRef = - React.useRef>(undefined); const computeUnreadCounts = React.useCallback((roomList: RoomInfo[]) => { const counts: Record = {}; @@ -105,36 +101,13 @@ function ChatProviderInner({ setUnreadCounts(counts); }, []); - const fetchUnknownUsers = React.useCallback((userIds: number[]) => { - for (const id of userIds) { - pendingUserFetches.current.add(id); - } - - clearTimeout(fetchTimeoutRef.current); - fetchTimeoutRef.current = setTimeout(async () => { - const ids = [...pendingUserFetches.current]; - pendingUserFetches.current.clear(); - - if (ids.length === 0) return; - - try { - // xxx: we should use remix fetcher for this - const response = await fetch(`/api/chat-users?ids=${ids.join(",")}`); - if (!response.ok) return; - - const users = (await response.json()) as Record; - setChatUsersCache((prev) => ({ ...prev, ...users })); - } catch { - // fetch failed, will retry on next unknown user - } - }, USER_FETCH_BATCH_DELAY_MS); - }, []); - const onMessage = React.useEffectEvent((e: MessageEvent) => { const parsed = JSON.parse(e.data); + logger.debug("WS message received:", parsed); // Initial rooms payload on connect if (parsed.rooms && Array.isArray(parsed.rooms)) { + logger.debug("WS initial rooms payload, count:", parsed.rooms.length); const serverRooms = parsed.rooms as ServerRoomInfo[]; const roomList = serverRooms.map(flattenServerRoom); setRooms(roomList); @@ -150,6 +123,7 @@ function ChatProviderInner({ // ROOM_JOINED: new room added if (parsed.event === "ROOM_JOINED" && parsed.room) { + logger.debug("WS ROOM_JOINED:", parsed.room?.chatCode); const serverRoom = parsed.room as ServerRoomInfo; const newRoom = flattenServerRoom(serverRoom); setRooms((prev) => { @@ -167,6 +141,12 @@ function ChatProviderInner({ // CHAT_HISTORY response (also returned by SUBSCRIBE with metadata) if (parsed.event === "CHAT_HISTORY" && Array.isArray(parsed.messages)) { + logger.debug( + "WS CHAT_HISTORY for:", + parsed.chatCode, + "messages:", + parsed.messages.length, + ); const chatCode = parsed.chatCode as string; const messages = parsed.messages as ChatMessage[]; setMessagesByRoom((prev) => ({ @@ -206,6 +186,12 @@ function ChatProviderInner({ ) as (ChatMessage & { totalMessageCount?: number })[]; const isSystemMessage = Boolean(messageArr[0].type); + logger.debug( + "WS message(s):", + messageArr.length, + "system:", + isSystemMessage, + ); if (isSystemMessage) { revalidate(); } @@ -238,10 +224,6 @@ function ChatProviderInner({ return { ...prev, [roomCode]: [...existing, msg] }; }); - if (msg.userId && !chatUsersCache[msg.userId]) { - fetchUnknownUsers([msg.userId]); - } - if (msg.totalMessageCount) { setRooms((prev) => prev.map((r) => @@ -396,10 +378,21 @@ function ChatProviderInner({ if (open && activeRoom) { markAsRead(activeRoom); } + + if (open && rooms.length === 1 && !activeRoom) { + requestHistory(rooms[0].chatCode); + setActiveRoom(rooms[0].chatCode); + } }, - [activeRoom, markAsRead], + [activeRoom, markAsRead, requestHistory, rooms.length, rooms[0]?.chatCode], ); + useFetchUnknownChatUsers({ + messages: messagesByRoom, + chatUsersCache, + setChatUsersCache, + }); + useChatRouteSync({ rooms, userId, @@ -452,7 +445,6 @@ 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({ @@ -476,20 +468,11 @@ function useChatRouteSync({ React.SetStateAction> >; }) { - const matches = useMatches(); + const chatCode = useCurrentRouteChatCode(); const subscribedRoomRef = React.useRef(null); + const previousRouteChatCodeRef = React.useRef(null); React.useEffect(() => { - let chatCode: string | null = null; - - for (const match of matches) { - const matchData = match.data as { chatCode?: string } | undefined; - if (matchData?.chatCode) { - chatCode = matchData.chatCode; - break; - } - } - const previousSubscribed = subscribedRoomRef.current; // Clean up previous non-participant subscription if chatCode changed @@ -500,10 +483,15 @@ function useChatRouteSync({ const { [previousSubscribed]: _, ...rest } = prev; return rest; }); + setActiveRoom(null); + setSidebarOpen(false); subscribedRoomRef.current = null; } - if (!chatCode) return; + if (!chatCode) { + previousRouteChatCodeRef.current = null; + return; + } const room = rooms.find((r) => r.chatCode === chatCode); const isParticipant = room?.participantUserIds.includes(userId); @@ -513,10 +501,15 @@ function useChatRouteSync({ subscribedRoomRef.current = chatCode; } - setActiveRoom(chatCode); - setSidebarOpen(true); + const routeChatCodeChanged = previousRouteChatCodeRef.current !== chatCode; + previousRouteChatCodeRef.current = chatCode; + + if (routeChatCodeChanged) { + setActiveRoom(chatCode); + setSidebarOpen(true); + } }, [ - matches, + chatCode, rooms, userId, setActiveRoom, @@ -527,3 +520,54 @@ function useChatRouteSync({ setMessagesByRoom, ]); } + +function useCurrentRouteChatCode() { + const matches = useMatches(); + + for (const match of matches) { + const matchData = match.data as { chatCode?: string } | undefined; + if (matchData?.chatCode) { + return matchData.chatCode; + } + } + + return null; +} + +function useFetchUnknownChatUsers({ + messages, + chatUsersCache, + setChatUsersCache, +}: { + messages: Record; + chatUsersCache: Record; + setChatUsersCache: React.Dispatch< + React.SetStateAction> + >; +}) { + const fetcher = useFetcher>(); + + const unknownIds: number[] = []; + for (const msgs of Object.values(messages)) { + for (const msg of msgs) { + if (msg.userId && !chatUsersCache[msg.userId]) { + unknownIds.push(msg.userId); + } + } + } + + const idsParam = unknownIds.sort((a, b) => a - b).join(","); + + React.useEffect(() => { + if (!idsParam || fetcher.state !== "idle") return; + + logger.debug(`Fetching unknown chat users: ${idsParam}`); + fetcher.load(`/api/chat-users?ids=${idsParam}`); + }, [idsParam, fetcher.load, fetcher.state]); + + React.useEffect(() => { + if (!fetcher.data) return; + + setChatUsersCache((prev) => ({ ...prev, ...fetcher.data })); + }, [fetcher.data, setChatUsersCache]); +} diff --git a/app/features/chat/ChatSystemMessage.server.ts b/app/features/chat/ChatSystemMessage.server.ts index 9eaf0bf70..e8c30c8a0 100644 --- a/app/features/chat/ChatSystemMessage.server.ts +++ b/app/features/chat/ChatSystemMessage.server.ts @@ -1,8 +1,10 @@ +import { add } from "date-fns"; import { nanoid } from "nanoid"; +import * as UserRepository from "~/features/user-page/UserRepository.server"; import { IS_E2E_TEST_RUN } from "~/utils/e2e"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; -import type { ChatMessage, ChatUser } from "./chat-types"; +import type { ChatMessage } from "./chat-types"; const SKALOP_TOKEN_HEADER_NAME = "Skalop-Token"; @@ -64,8 +66,7 @@ interface SetMetadataArgs { subtitle: string; url: string; participantUserIds: number[]; - chatUsers: Record; - expiresAt: number; + expiresAfter: { hours: number } | { days: number }; } // xxx: actually there is no dedup like this, for as long as the service is up, we dont resend metadata @@ -73,7 +74,7 @@ const DEDUP_INTERVAL_MS = 30_000; const DEDUP_PRUNE_MS = 1000 * 60 * 60 * 24 * 30; const metadataDedup = new Map(); -export function setMetadata(args: SetMetadataArgs) { +export async function setMetadata(args: SetMetadataArgs) { if (systemMessagesDisabled) return; if (!process.env.SKALOP_SYSTEM_MESSAGE_URL) return; @@ -89,6 +90,12 @@ export function setMetadata(args: SetMetadataArgs) { } } + const expiresAt = add(new Date(), args.expiresAfter).getTime(); + + const chatUsers = await UserRepository.findChatUsersByUserIds( + args.participantUserIds, + ); + return void fetch(process.env.SKALOP_SYSTEM_MESSAGE_URL, { method: "POST", body: JSON.stringify({ @@ -96,8 +103,8 @@ export function setMetadata(args: SetMetadataArgs) { chatCode: args.chatCode, metadata: { participantUserIds: args.participantUserIds, - chatUsers: args.chatUsers, - expiresAt: args.expiresAt, + chatUsers, + expiresAt, header: args.header, subtitle: args.subtitle, url: args.url, diff --git a/app/features/chat/chat-constants.ts b/app/features/chat/chat-constants.ts index cb2fc7b60..8e0f93316 100644 --- a/app/features/chat/chat-constants.ts +++ b/app/features/chat/chat-constants.ts @@ -1,7 +1 @@ export const MESSAGE_MAX_LENGTH = 200; - -// xxx: use date-fns -export const SENDOUQ_MATCH_EXPIRY_MS = 1000 * 60 * 60; -export const TOURNAMENT_MATCH_EXPIRY_MS = 1000 * 60 * 60 * 2; -export const LEAGUE_MATCH_EXPIRY_MS = 1000 * 60 * 60 * 24 * 30; -export const SCRIM_EXPIRY_MS = 1000 * 60 * 60 * 3; diff --git a/app/features/chat/routes/api.chat-users.ts b/app/features/chat/routes/api.chat-users.ts index 0b59dfb22..68eb06829 100644 --- a/app/features/chat/routes/api.chat-users.ts +++ b/app/features/chat/routes/api.chat-users.ts @@ -12,7 +12,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { return Response.json({}); } - // xxx: use zod const parsedIds = idsParam .split(",") .map(Number) diff --git a/app/features/scrims/loaders/scrims.$id.server.ts b/app/features/scrims/loaders/scrims.$id.server.ts index e19be6555..0e2817edf 100644 --- a/app/features/scrims/loaders/scrims.$id.server.ts +++ b/app/features/scrims/loaders/scrims.$id.server.ts @@ -1,6 +1,5 @@ import type { LoaderFunctionArgs } from "react-router"; -import { setMetadata } from "~/features/chat/ChatSystemMessage.server"; -import { SCRIM_EXPIRY_MS } from "~/features/chat/chat-constants"; +import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server"; import { tournamentDataCached } from "~/features/tournament-bracket/core/Tournament.server"; import * as UserRepository from "~/features/user-page/UserRepository.server"; import { notFoundIfFalsy } from "../../../utils/remix.server"; @@ -27,26 +26,23 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { } const participantIds = Scrim.participantIdsListFromAccepted(post); - const chatUsers = await UserRepository.findChatUsersByUserIds(participantIds); // xxx: additional condition if (post.chatCode) { - setMetadata({ + ChatSystemMessage.setMetadata({ chatCode: post.chatCode, // xxx: better header+subtitle header: "Scrim", subtitle: `Scrim #${post.id}`, url: `/scrims/${post.id}`, participantUserIds: participantIds, - chatUsers, - expiresAt: Date.now() + SCRIM_EXPIRY_MS, + expiresAfter: { hours: 3 }, }); } return { post, - // xxx: only chatCode if permissions - chatCode: post.chatCode, + chatCode: user.roles.includes("STAFF") ? post.chatCode : undefined, 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 f268e8ff3..7ecd57750 100644 --- a/app/features/sendouq-match/loaders/q.match.$id.server.ts +++ b/app/features/sendouq-match/loaders/q.match.$id.server.ts @@ -1,14 +1,13 @@ import type { LoaderFunctionArgs } from "react-router"; import { getUser } from "~/features/auth/core/user.server"; -import { setMetadata } from "~/features/chat/ChatSystemMessage.server"; -import { SENDOUQ_MATCH_EXPIRY_MS } from "~/features/chat/chat-constants"; +import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server"; import { SendouQ } from "~/features/sendouq/core/SendouQ.server"; import * as PrivateUserNoteRepository from "~/features/sendouq/PrivateUserNoteRepository.server"; import { reportedWeaponsToArrayOfArrays } from "~/features/sendouq-match/core/reported-weapons.server"; import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server"; import * as SQMatchRepository from "~/features/sendouq-match/SQMatchRepository.server"; -import * as UserRepository from "~/features/user-page/UserRepository.server"; import { notFoundIfFalsy, parseParams } from "~/utils/remix.server"; +import { sendouQMatchPage } from "~/utils/urls"; import { qMatchPageParamsSchema } from "../q-match-schemas"; export const loader = async ({ params }: LoaderFunctionArgs) => { @@ -35,24 +34,19 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { ? await ReportedWeaponRepository.findByMatchId(matchId) : null; - // xxx: also if match is even ongoing - if (match.chatCode && user) { + if (match.chatCode && !match.isLocked) { const participantIds = [ ...matchUnmapped.groupAlpha.members, ...matchUnmapped.groupBravo.members, ].map((m) => m.id); - const chatUsers = - await UserRepository.findChatUsersByUserIds(participantIds); - - setMetadata({ + ChatSystemMessage.setMetadata({ chatCode: match.chatCode, - header: "SQ Match", - subtitle: `Match #${matchId}`, - url: `/q/match/${matchId}`, + header: `Match #${matchId}`, + subtitle: "SendouQ", + url: sendouQMatchPage(matchId), participantUserIds: participantIds, - chatUsers, - expiresAt: Date.now() + SENDOUQ_MATCH_EXPIRY_MS, + expiresAfter: { hours: 2 }, }); } @@ -67,7 +61,6 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { }) : null, rawReportedWeapons, - // xxx: only chatCode if permissions - chatCode: match.chatCode, + chatCode: user?.roles.includes("STAFF") ? match.chatCode : null, }; }; diff --git a/app/features/sendouq/loaders/q.looking.server.ts b/app/features/sendouq/loaders/q.looking.server.ts index 9389b54f1..1d43e5705 100644 --- a/app/features/sendouq/loaders/q.looking.server.ts +++ b/app/features/sendouq/loaders/q.looking.server.ts @@ -1,10 +1,8 @@ import type { LoaderFunctionArgs } from "react-router"; import { requireUser } from "~/features/auth/core/user.server"; -import { setMetadata } from "~/features/chat/ChatSystemMessage.server"; -import { SENDOUQ_MATCH_EXPIRY_MS } from "~/features/chat/chat-constants"; +import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server"; import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server"; import { cachedStreams } from "~/features/sendouq-streams/core/streams.server"; -import * as UserRepository from "~/features/user-page/UserRepository.server"; import { groupExpiryStatus } from "../core/groups"; import { SendouQ } from "../core/SendouQ.server"; import * as PrivateUserNoteRepository from "../PrivateUserNoteRepository.server"; @@ -35,19 +33,17 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { }); } + // xxx: how to handle group changing..? if (ownGroup?.chatCode) { const memberIds = ownGroup.members.map((m: { id: number }) => m.id); - const chatUsers = await UserRepository.findChatUsersByUserIds(memberIds); - setMetadata({ + ChatSystemMessage.setMetadata({ chatCode: ownGroup.chatCode, - header: "SQ Group", - // xxx: better subtitle - subtitle: "Looking for match", + header: "Group", + subtitle: "SendouQ", url: "/q/looking", participantUserIds: memberIds, - chatUsers, - expiresAt: Date.now() + SENDOUQ_MATCH_EXPIRY_MS, + expiresAfter: { hours: 1 }, }); } 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 4c5b9c297..7a3a96591 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 @@ -1,7 +1,7 @@ import cachified from "@epic-web/cachified"; import type { LoaderFunctionArgs } from "react-router"; -import { setMetadata } from "~/features/chat/ChatSystemMessage.server"; -import { TOURNAMENT_MATCH_EXPIRY_MS } from "~/features/chat/chat-constants"; +import { getUser } from "~/features/auth/core/user.server"; +import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server"; import * as TournamentRepository from "~/features/tournament/TournamentRepository.server"; import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server"; import * as UserRepository from "~/features/user-page/UserRepository.server"; @@ -9,7 +9,9 @@ import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server"; import { IS_E2E_TEST_RUN } from "~/utils/e2e"; import { logger } from "~/utils/logger"; import { notFoundIfFalsy, parseParams } from "~/utils/remix.server"; +import { tournamentMatchPage } from "~/utils/urls"; import { mapListFromResults, resolveMapList } from "../core/mapList.server"; +import { tournamentFromDBCached } from "../core/Tournament.server"; import { findMatchById } from "../queries/findMatchById.server"; import { findResultsByMatchId } from "../queries/findResultsByMatchId.server"; import { matchPageParamsSchema } from "../tournament-bracket-schemas.server"; @@ -22,6 +24,11 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { params, schema: matchPageParamsSchema, }); + const user = getUser(); + const tournament = await tournamentFromDBCached({ + tournamentId, + user: undefined, + }); const match = notFoundIfFalsy(findMatchById(matchId)); @@ -94,21 +101,17 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { }) : false; - // xxx: also need to check match is currently ongoing - if (match.chatCode) { + if (match.chatCode && !matchIsOver) { const playerIds = match.players.map((p) => p.id); - // xxx: should this be resolved inside setMetadata? - const chatUsers = await UserRepository.findChatUsersByUserIds(playerIds); + const matchContext = tournament.matchContextNamesById(matchId); - // xxx: module.function format - setMetadata({ + ChatSystemMessage.setMetadata({ chatCode: match.chatCode, - header: "Tournament Match", - subtitle: `Match #${matchId}`, - url: `/to/${tournamentId}/matches/${matchId}`, + header: matchContext.roundName ?? `Match #${matchId}`, + subtitle: tournament.ctx.name, + url: tournamentMatchPage({ tournamentId, matchId }), participantUserIds: playerIds, - chatUsers, - expiresAt: Date.now() + TOURNAMENT_MATCH_EXPIRY_MS, + expiresAfter: { hours: 2 }, }); } @@ -119,7 +122,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { matchIsOver, endedEarly, noScreen, - // xxx: only chatCode if permissions - chatCode: match.chatCode, + chatCode: tournament.isOrganizerOrStreamer(user) + ? match.chatCode + : undefined, }; }; diff --git a/app/utils/logger.ts b/app/utils/logger.ts index 631261fdb..b3ac0d27f 100644 --- a/app/utils/logger.ts +++ b/app/utils/logger.ts @@ -22,4 +22,5 @@ export const logger = { info: (...args: unknown[]) => console.log(...formatLog(...args)), error: (...args: unknown[]) => console.error(...formatLog(...args)), warn: (...args: unknown[]) => console.warn(...formatLog(...args)), + debug: (...args: unknown[]) => console.debug(...formatLog(...args)), };