Files
movieloop-frontend/src/pages/GameNightPlay.tsx
T
TehRiehlDeal 5a7e8cebc3 feat: repo hygiene - push opt-in, paged admin jobs, shared game screen (2.2.0)
WP-17. Minor: push notifications become usable and nothing a player could
reach was removed. The admin console's jobs need backend 3.0.0.

Push (M35): the whole browser half - the service worker's handlers, the
subscribe/unsubscribe calls, the server's VAPID key - had shipped with
nothing calling it, so no browser ever held a subscription. The toggle
lives in Profile because the permission prompt has to come from a click.
The old isPushSubscribed awaited serviceWorker.ready, which never settles
with no worker registered; it uses getRegistration() now.

Admin: each job pages and the button becomes Continue with the server's
cursor; the recalculate card also stopped printing "undefined leaderboard
entries" for a field the server had not sent for several releases. The
three copy-pasted job cards are one component.

Consolidation: the four game screens share use-game-screen and one search
panel; the review page's static chain is ChainDisplay in a static mode
(21 rendered states verified byte-identical); formatTime and error
extraction exist once; Endless and quick play share a franchise-aware
picker instead of two divergent sort(() => Math.random() - 0.5) copies.
The dead api/index barrel and the fourteen functions only it referenced
are gone. noUncheckedIndexedAccess is on. Whiplash's TMDB id is corrected
and every preset title is checked against TMDB.
2026-09-12 00:01:42 -07:00

101 lines
3.1 KiB
TypeScript

import { useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useGameStore } from '@/stores/game-store';
import { useGameNightStore } from '@/stores/game-night-store';
import { useGameScreen } from '@/hooks/use-game-screen';
import PageLayout from '@/components/layout/PageLayout';
import GameHeader from '@/components/game/GameHeader';
import ChainDisplay from '@/components/game/ChainDisplay';
import GameSearchPanel from '@/components/game/GameSearchPanel';
import PlayersProgress from '@/components/game-night/PlayersProgress';
import GameNightCompletionModal from '@/components/game-night/GameNightCompletionModal';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Flag } from 'lucide-react';
export default function GameNightPlay() {
const screen = useGameScreen();
const { status, chain } = screen;
const score = useGameStore((s) => s.score);
const navigate = useNavigate();
const sendChainUpdate = useGameNightStore((s) => s.sendChainUpdate);
const sendPlayerComplete = useGameNightStore((s) => s.sendPlayerComplete);
const sessionId = useGameStore((s) => s.sessionId);
const sendPlayerForfeit = useGameNightStore((s) => s.sendPlayerForfeit);
const isSpectator = useGameNightStore((s) => s.isSpectator);
const lobbyState = useGameNightStore((s) => s.lobbyState);
const roomId = useGameNightStore((s) => s.roomId);
// Broadcast chain updates
useEffect(() => {
if (chain.length > 0 && !isSpectator) {
sendChainUpdate(chain.length);
}
}, [chain.length, sendChainUpdate, isSpectator]);
// Send completion when game completes
useEffect(() => {
if (status === 'completed' && score && sessionId && !isSpectator) {
sendPlayerComplete(sessionId);
}
}, [status, score, sessionId, sendPlayerComplete, isSpectator]);
useEffect(() => {
if (status === 'idle') {
navigate('/game-night');
}
}, [status, navigate]);
// Navigate to results when room finishes
useEffect(() => {
if (lobbyState === 'finished' && roomId) {
navigate(`/game-night/${roomId}/results`);
}
}, [lobbyState, roomId, navigate]);
if (!screen.ready) {
return null;
}
return (
<PageLayout>
<div className="flex gap-6">
{/* Main game area */}
<div className="flex-1 space-y-4">
<GameHeader />
<Separator />
<ChainDisplay />
{status === 'playing' && !isSpectator && (
<GameSearchPanel screen={screen}>
<Button
variant="destructive"
size="sm"
onClick={sendPlayerForfeit}
>
<Flag className="mr-1 h-3.5 w-3.5" />
I Give Up
</Button>
</GameSearchPanel>
)}
</div>
{/* Players sidebar */}
<div className="hidden w-64 shrink-0 lg:block">
<PlayersProgress />
</div>
</div>
{/* Mobile players progress */}
<div className="mt-4 lg:hidden">
<PlayersProgress />
</div>
<GameNightCompletionModal />
</PageLayout>
);
}