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
140
apps/web/src/components/game/GameSetup.tsx
Normal file
140
apps/web/src/components/game/GameSetup.tsx
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
SUPPORTED_POS,
|
||||
DIFFICULTY_LEVELS,
|
||||
GAME_ROUNDS,
|
||||
} from "@glossa/shared";
|
||||
import type { GameRequest } from "@glossa/shared";
|
||||
|
||||
const LABELS: Record<string, string> = {
|
||||
en: "English",
|
||||
it: "Italian",
|
||||
noun: "Nouns",
|
||||
verb: "Verbs",
|
||||
easy: "Easy",
|
||||
intermediate: "Intermediate",
|
||||
hard: "Hard",
|
||||
"3": "3 rounds",
|
||||
"10": "10 rounds",
|
||||
};
|
||||
|
||||
type GameSetupProps = { onStart: (settings: GameRequest) => void };
|
||||
|
||||
type SettingGroupProps = {
|
||||
label: string;
|
||||
options: readonly string[];
|
||||
selected: string;
|
||||
onSelect: (value: string) => void;
|
||||
};
|
||||
|
||||
const SettingGroup = ({
|
||||
label,
|
||||
options,
|
||||
selected,
|
||||
onSelect,
|
||||
}: SettingGroupProps) => (
|
||||
<div className="w-full">
|
||||
<p className="text-sm font-medium text-purple-400 mb-2">{label}</p>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
onClick={() => onSelect(option)}
|
||||
className={`py-2 px-5 rounded-xl font-semibold text-sm border-b-4 transition-all duration-200 cursor-pointer ${
|
||||
selected === option
|
||||
? "bg-purple-600 text-white border-purple-800"
|
||||
: "bg-white text-purple-900 border-purple-200 hover:bg-purple-50 hover:border-purple-300"
|
||||
}`}
|
||||
>
|
||||
{LABELS[option] ?? option}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const GameSetup = ({ onStart }: GameSetupProps) => {
|
||||
const [sourceLanguage, setSourceLanguage] = useState<string>(
|
||||
SUPPORTED_LANGUAGE_CODES[0],
|
||||
);
|
||||
const [targetLanguage, setTargetLanguage] = useState<string>(
|
||||
SUPPORTED_LANGUAGE_CODES[1],
|
||||
);
|
||||
const [pos, setPos] = useState<string>(SUPPORTED_POS[0]);
|
||||
const [difficulty, setDifficulty] = useState<string>(DIFFICULTY_LEVELS[0]);
|
||||
const [rounds, setRounds] = useState<string>(GAME_ROUNDS[0]);
|
||||
|
||||
const handleSourceLanguage = (value: string) => {
|
||||
if (value === targetLanguage) {
|
||||
setTargetLanguage(sourceLanguage);
|
||||
}
|
||||
setSourceLanguage(value);
|
||||
};
|
||||
|
||||
const handleTargetLanguage = (value: string) => {
|
||||
if (value === sourceLanguage) {
|
||||
setSourceLanguage(targetLanguage);
|
||||
}
|
||||
setTargetLanguage(value);
|
||||
};
|
||||
|
||||
const handleStart = () => {
|
||||
onStart({
|
||||
source_language: sourceLanguage,
|
||||
target_language: targetLanguage,
|
||||
pos,
|
||||
difficulty,
|
||||
rounds,
|
||||
} as GameRequest);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
|
||||
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
|
||||
<h1 className="text-3xl font-bold text-purple-900 mb-1">Glossa</h1>
|
||||
<p className="text-sm text-gray-400">Set up your quiz</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-3xl shadow-lg p-6 w-full flex flex-col gap-5">
|
||||
<SettingGroup
|
||||
label="I speak"
|
||||
options={SUPPORTED_LANGUAGE_CODES}
|
||||
selected={sourceLanguage}
|
||||
onSelect={handleSourceLanguage}
|
||||
/>
|
||||
<SettingGroup
|
||||
label="I want to learn"
|
||||
options={SUPPORTED_LANGUAGE_CODES}
|
||||
selected={targetLanguage}
|
||||
onSelect={handleTargetLanguage}
|
||||
/>
|
||||
<SettingGroup
|
||||
label="Word type"
|
||||
options={SUPPORTED_POS}
|
||||
selected={pos}
|
||||
onSelect={setPos}
|
||||
/>
|
||||
<SettingGroup
|
||||
label="Difficulty"
|
||||
options={DIFFICULTY_LEVELS}
|
||||
selected={difficulty}
|
||||
onSelect={setDifficulty}
|
||||
/>
|
||||
<SettingGroup
|
||||
label="Rounds"
|
||||
options={GAME_ROUNDS}
|
||||
selected={rounds}
|
||||
onSelect={setRounds}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleStart}
|
||||
className="w-full py-4 rounded-2xl text-xl font-bold bg-linear-to-r from-pink-400 to-purple-500 text-white border-b-4 border-purple-700 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
Start Quiz
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue