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:
lila 2026-04-11 21:18:35 +02:00
parent b7b1cd383f
commit bc7977463e
4 changed files with 209 additions and 26 deletions

View file

@ -1,3 +1,4 @@
import { useState } from "react";
import type { GameQuestion, AnswerResult } from "@glossa/shared";
import { OptionButton } from "./OptionButton";
@ -18,11 +19,31 @@ export const QuestionCard = ({
onAnswer,
onNext,
}: QuestionCardProps) => {
const [selectedOptionId, setSelectedOptionId] = useState<number | null>(null);
const getOptionState = (optionId: number) => {
if (!currentResult) return "idle" as const;
if (optionId === currentResult.correctOptionId) return "correct" as const;
if (optionId === currentResult.selectedOptionId) return "wrong" as const;
return "disabled" as const;
if (currentResult) {
if (optionId === currentResult.correctOptionId) return "correct" as const;
if (optionId === currentResult.selectedOptionId) return "wrong" as const;
return "disabled" as const;
}
if (optionId === selectedOptionId) return "selected" as const;
return "idle" as const;
};
const handleSelect = (optionId: number) => {
if (currentResult) return;
setSelectedOptionId(optionId);
};
const handleSubmit = () => {
if (selectedOptionId === null) return;
onAnswer(selectedOptionId);
};
const handleNext = () => {
setSelectedOptionId(null);
onNext();
};
return (
@ -48,15 +69,24 @@ export const QuestionCard = ({
key={option.optionId}
text={option.text}
state={getOptionState(option.optionId)}
onSelect={() => onAnswer(option.optionId)}
onSelect={() => handleSelect(option.optionId)}
/>
))}
</div>
{!currentResult && selectedOptionId !== null && (
<button
onClick={handleSubmit}
className="w-full py-3 rounded-2xl text-lg 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"
>
Submit
</button>
)}
{currentResult && (
<button
onClick={onNext}
className="mt-2 py-3 px-10 rounded-2xl text-lg font-bold bg-purple-600 text-white border-b-4 border-purple-800 hover:bg-purple-500 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
onClick={handleNext}
className="w-full py-3 rounded-2xl text-lg font-bold bg-purple-600 text-white border-b-4 border-purple-800 hover:bg-purple-500 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2 transition-all duration-200 cursor-pointer"
>
{questionNumber === totalQuestions ? "See Results" : "Next"}
</button>