From cfaf82b2cc93c6da1dcceb5aea8d0bd2c7b14cea Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Mon, 13 Oct 2025 13:05:28 -0500 Subject: [PATCH] fix: move invitations into nav and filter out current/banned rooms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improvements to invitation banner UX: - Move PendingInvitations from arcade page into GameContextNav - Now appears as part of the mini app nav (not underneath it) - Filter out invitations for the room user is currently in - Filter out invitations for rooms where user is banned - Backend filters banned room invitations automatically This resolves awkward situations where users see invitations for: 1. The room they're already in 2. Rooms they were kicked from or banned from 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../api/arcade/invitations/pending/route.ts | 17 +- apps/web/src/app/arcade/page.tsx | 14 - .../web/src/components/nav/GameContextNav.tsx | 333 +++++++++--------- .../src/components/nav/PendingInvitations.tsx | 20 +- 4 files changed, 200 insertions(+), 184 deletions(-) diff --git a/apps/web/src/app/api/arcade/invitations/pending/route.ts b/apps/web/src/app/api/arcade/invitations/pending/route.ts index b513b64c..f8b402dc 100644 --- a/apps/web/src/app/api/arcade/invitations/pending/route.ts +++ b/apps/web/src/app/api/arcade/invitations/pending/route.ts @@ -1,11 +1,12 @@ +import { and, eq, isNull } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { db, schema } from '@/db' -import { eq } from 'drizzle-orm' import { getViewerId } from '@/lib/viewer' /** * GET /api/arcade/invitations/pending * Get all pending invitations for the current user with room details + * Excludes invitations for rooms where the user is currently banned */ export async function GET(req: NextRequest) { try { @@ -33,8 +34,18 @@ export async function GET(req: NextRequest) { .where(eq(schema.roomInvitations.userId, viewerId)) .orderBy(schema.roomInvitations.createdAt) - // Filter to only pending invitations - const pendingInvitations = invitations.filter((inv) => inv.status === 'pending') + // Get all active bans for this user + const activeBans = await db + .select({ roomId: schema.roomBans.roomId }) + .from(schema.roomBans) + .where(and(eq(schema.roomBans.userId, viewerId), isNull(schema.roomBans.unbannedAt))) + + const bannedRoomIds = new Set(activeBans.map((ban) => ban.roomId)) + + // Filter to only pending invitations, excluding banned rooms + const pendingInvitations = invitations.filter( + (inv) => inv.status === 'pending' && !bannedRoomIds.has(inv.roomId) + ) return NextResponse.json({ invitations: pendingInvitations }, { status: 200 }) } catch (error: any) { diff --git a/apps/web/src/app/arcade/page.tsx b/apps/web/src/app/arcade/page.tsx index 4f5768d8..f8c42df8 100644 --- a/apps/web/src/app/arcade/page.tsx +++ b/apps/web/src/app/arcade/page.tsx @@ -5,13 +5,10 @@ import { PageWithNav } from '@/components/PageWithNav' import { css } from '../../../styled-system/css' import { EnhancedChampionArena } from '../../components/EnhancedChampionArena' import { FullscreenProvider, useFullscreen } from '../../contexts/FullscreenContext' -import { PendingInvitations } from '@/components/nav/PendingInvitations' -import { useRoomData } from '@/hooks/useRoomData' function ArcadeContent() { const { setFullscreenElement } = useFullscreen() const arcadeRef = useRef(null) - const { refetch: refetchRoomData } = useRoomData() useEffect(() => { // Register this component's main div as the fullscreen element @@ -50,17 +47,6 @@ function ArcadeContent() { })} /> - {/* Pending Invitations */} -
- refetchRoomData()} /> -
- {/* Main Champion Arena - takes remaining space */}
m.userId === currentUserId) @@ -169,171 +170,178 @@ export function GameContextNav({ const showPlayers = activePlayers.length > 0 || (shouldEmphasize && inactivePlayers.length > 0) return ( -
- {/* Game Title Section - Always mounted, hidden when in room */} + <> + {/* Pending Invitations Banner - Shows above nav when user has invitations */} + refetchRoomData()} + /> +
- -
-
+ + {/* Room Info Section - Always mounted, hidden when not in room */} +
+ + + {/* Network Players - inside same pane as room info */} + {networkPlayers.length > 0 && ( + <> +
+ {networkPlayers.map((player) => ( + + ))} + + )} +
+ + {/* Player Section - Always mounted, hidden when no players */} +
+ + +
-
- {/* Room Info Section - Always mounted, hidden when not in room */} -
- - - {/* Network Players - inside same pane as room info */} - {networkPlayers.length > 0 && ( - <> -
- {networkPlayers.map((player) => ( - - ))} - - )} -
- - {/* Player Section - Always mounted, hidden when no players */} -
- - - -
- -