119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogDescription,
|
|
} from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useGameStore } from '@/stores/game-store';
|
|
import { useAuthStore } from '@/stores/auth-store';
|
|
import { submitScore } from '@/api/leaderboards';
|
|
import ScoreDisplay from './ScoreDisplay';
|
|
import ShareableResult from './ShareableResult';
|
|
import CelebrationOverlay from './CelebrationOverlay';
|
|
import { playSound } from '@/lib/sounds';
|
|
import { useNavigate } from 'react-router';
|
|
|
|
function formatElapsed(seconds: number): string {
|
|
const m = Math.floor(seconds / 60);
|
|
const s = seconds % 60;
|
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
export default function GameCompletionModal() {
|
|
const status = useGameStore((s) => s.status);
|
|
const score = useGameStore((s) => s.score);
|
|
const chain = useGameStore((s) => s.chain);
|
|
const movieA = useGameStore((s) => s.movieA);
|
|
const movieB = useGameStore((s) => s.movieB);
|
|
const hintsUsed = useGameStore((s) => s.hintsUsed);
|
|
const resetGame = useGameStore((s) => s.resetGame);
|
|
const user = useAuthStore((s) => s.user);
|
|
const navigate = useNavigate();
|
|
const [showModal, setShowModal] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (status === 'completed') {
|
|
setShowModal(true);
|
|
playSound('completion');
|
|
|
|
// Auto-submit score if logged in
|
|
if (user && score) {
|
|
submitScore(
|
|
score.totalScore,
|
|
score.chainLength,
|
|
score.elapsedSeconds,
|
|
hintsUsed,
|
|
).catch(() => {
|
|
// Silent fail — score is still shown locally
|
|
});
|
|
}
|
|
} else {
|
|
setShowModal(false);
|
|
}
|
|
}, [status]);
|
|
|
|
const handlePlayAgain = () => {
|
|
resetGame();
|
|
navigate('/');
|
|
};
|
|
|
|
const handleViewChain = () => {
|
|
setShowModal(false);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{status === 'completed' && <CelebrationOverlay />}
|
|
<Dialog open={showModal} onOpenChange={(open) => !open && setShowModal(false)}>
|
|
<DialogContent
|
|
className="sm:max-w-md"
|
|
showCloseButton={false}
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle className="text-center text-2xl">
|
|
Congratulations!
|
|
</DialogTitle>
|
|
<DialogDescription className="text-center">
|
|
You completed the movie loop!
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="py-4">
|
|
{score && (
|
|
<>
|
|
<ScoreDisplay score={score} />
|
|
<div className="mt-3 flex justify-center gap-4 text-sm text-muted-foreground">
|
|
<span>Completed in {formatElapsed(score.elapsedSeconds)}</span>
|
|
{hintsUsed > 0 && (
|
|
<span>{hintsUsed} hint{hintsUsed > 1 ? 's' : ''} used</span>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col items-center gap-3">
|
|
{score && movieA && movieB && (
|
|
<ShareableResult
|
|
score={score}
|
|
chain={chain}
|
|
movieATitle={movieA.title}
|
|
movieBTitle={movieB.title}
|
|
/>
|
|
)}
|
|
<div className="flex gap-3">
|
|
<Button variant="outline" onClick={handleViewChain}>
|
|
View Chain
|
|
</Button>
|
|
<Button onClick={handlePlayAgain} size="lg">
|
|
Play Again
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|