feat(web): add game settings screen and submit confirmation
- Add GameSetup component with Duolingo-style button selectors for language pair, POS, difficulty, and rounds - Language swap: selecting the same language for source and target automatically swaps them instead of allowing duplicates - Add selection-before-submission flow: user clicks an option to highlight it, then confirms with a Submit button to prevent misclicks - Add selected state to OptionButton (purple ring highlight) - Play Again on score screen returns to settings instead of auto-restarting with the same configuration - Remove hardcoded GAME_SETTINGS, game configuration is now user-driven
This commit is contained in:
parent
b7b1cd383f
commit
bc7977463e
4 changed files with 209 additions and 26 deletions
|
|
@ -1,38 +1,38 @@
|
|||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { useState, useEffect } from "react";
|
||||
import type { GameSession, AnswerResult } from "@glossa/shared";
|
||||
import { useState, useCallback } from "react";
|
||||
import type { GameSession, GameRequest, AnswerResult } from "@glossa/shared";
|
||||
import { QuestionCard } from "../components/game/QuestionCard";
|
||||
import { ScoreScreen } from "../components/game/ScoreScreen";
|
||||
|
||||
const GAME_SETTINGS = {
|
||||
source_language: "en",
|
||||
target_language: "it",
|
||||
pos: "noun",
|
||||
difficulty: "easy",
|
||||
rounds: "3",
|
||||
} as const;
|
||||
import { GameSetup } from "../components/game/GameSetup";
|
||||
|
||||
function Play() {
|
||||
const [gameSession, setGameSession] = useState<GameSession | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
||||
const [results, setResults] = useState<AnswerResult[]>([]);
|
||||
const [currentResult, setCurrentResult] = useState<AnswerResult | null>(null);
|
||||
|
||||
const startGame = async () => {
|
||||
const startGame = useCallback(async (settings: GameRequest) => {
|
||||
setIsLoading(true);
|
||||
const response = await fetch("/api/v1/game/start", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(GAME_SETTINGS),
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
const data = await response.json();
|
||||
setGameSession(data.data);
|
||||
setCurrentQuestionIndex(0);
|
||||
setResults([]);
|
||||
setCurrentResult(null);
|
||||
};
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
startGame();
|
||||
const resetToSetup = useCallback(() => {
|
||||
setGameSession(null);
|
||||
setIsLoading(false);
|
||||
setCurrentQuestionIndex(0);
|
||||
setResults([]);
|
||||
setCurrentResult(null);
|
||||
}, []);
|
||||
|
||||
const handleAnswer = async (optionId: number) => {
|
||||
|
|
@ -61,8 +61,17 @@ function Play() {
|
|||
setCurrentResult(null);
|
||||
};
|
||||
|
||||
// Phase: setup
|
||||
if (!gameSession && !isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
||||
<GameSetup onStart={startGame} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Phase: loading
|
||||
if (!gameSession) {
|
||||
if (isLoading || !gameSession) {
|
||||
return (
|
||||
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center">
|
||||
<p className="text-purple-400 text-lg font-medium">Loading...</p>
|
||||
|
|
@ -74,7 +83,7 @@ function Play() {
|
|||
if (currentQuestionIndex >= gameSession.questions.length) {
|
||||
return (
|
||||
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
||||
<ScoreScreen results={results} onPlayAgain={startGame} />
|
||||
<ScoreScreen results={results} onPlayAgain={resetToSetup} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue