Files
movieloop-frontend/src/pages/VersusGame.tsx
T
2026-03-09 15:03:08 -07:00

127 lines
4.2 KiB
TypeScript

import { useEffect } from 'react';
import { useNavigate } from 'react-router';
import { useGameStore } from '@/stores/game-store';
import { useVersusStore } from '@/stores/versus-store';
import { useChainValidation } from '@/hooks/use-chain-validation';
import PageLayout from '@/components/layout/PageLayout';
import GameHeader from '@/components/game/GameHeader';
import ChainDisplay from '@/components/game/ChainDisplay';
import SearchAutocomplete from '@/components/game/SearchAutocomplete';
import ValidationFeedback from '@/components/game/ValidationFeedback';
import HintButton from '@/components/game/HintButton';
import OpponentProgress from '@/components/versus/OpponentProgress';
import VersusCompletionModal from '@/components/versus/VersusCompletionModal';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Undo2 } from 'lucide-react';
export default function VersusGame() {
const status = useGameStore((s) => s.status);
const chain = useGameStore((s) => s.chain);
const currentSearchMode = useGameStore((s) => s.currentSearchMode);
const passedMovieB = useGameStore((s) => s.passedMovieB);
const movieA = useGameStore((s) => s.movieA);
const movieB = useGameStore((s) => s.movieB);
const isValidating = useGameStore((s) => s.isValidating);
const score = useGameStore((s) => s.score);
const undoLastLink = useGameStore((s) => s.undoLastLink);
const navigate = useNavigate();
const { validateAndAddActor, validateAndAddMovie } = useChainValidation();
const sendChainUpdate = useVersusStore((s) => s.sendChainUpdate);
const sendMatchComplete = useVersusStore((s) => s.sendMatchComplete);
// Broadcast chain updates to opponent
useEffect(() => {
if (chain.length > 0) {
sendChainUpdate(chain.length);
}
}, [chain.length, sendChainUpdate]);
// Send match completion when game completes
useEffect(() => {
if (status === 'completed' && score) {
sendMatchComplete(score);
}
}, [status, score, sendMatchComplete]);
useEffect(() => {
if (status === 'idle') {
navigate('/versus');
}
}, [status, navigate]);
if (status === 'idle' || !movieA || !movieB) {
return null;
}
const lastLink = chain[chain.length - 1];
let searchLabel = '';
let searchPlaceholder = '';
if (currentSearchMode === 'actor' && lastLink?.type === 'movie') {
searchLabel = `Who was in "${lastLink.title}"?`;
searchPlaceholder = 'Search for an actor...';
} else if (currentSearchMode === 'movie' && lastLink?.type === 'actor') {
searchLabel = `What movie was ${lastLink.name} also in?`;
searchPlaceholder = 'Search for a movie...';
}
const showLoopHint = passedMovieB && currentSearchMode === 'movie' && movieA;
return (
<PageLayout>
<div className="space-y-4">
<GameHeader />
<OpponentProgress />
<Separator />
<ChainDisplay />
{status === 'playing' && (
<div className="space-y-3">
<Separator />
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">{searchLabel}</label>
<div className="flex items-center gap-2">
<HintButton />
<Button
variant="ghost"
size="sm"
onClick={undoLastLink}
disabled={chain.length <= 1 || isValidating}
>
<Undo2 className="mr-1 h-3.5 w-3.5" />
Undo
</Button>
</div>
</div>
<SearchAutocomplete
mode={currentSearchMode}
onSelectActor={validateAndAddActor}
onSelectMovie={validateAndAddMovie}
disabled={isValidating}
placeholder={searchPlaceholder}
/>
<ValidationFeedback />
{showLoopHint && (
<p className="text-sm text-green-600">
Select &quot;{movieA.title}&quot; to close the loop!
</p>
)}
</div>
</div>
)}
</div>
<VersusCompletionModal />
</PageLayout>
);
}