refactoring ui into separate components, updating ui, adding color scheme

This commit is contained in:
lila 2026-04-11 20:53:10 +02:00
parent ea33b7fcc8
commit b7b1cd383f
4 changed files with 184 additions and 55 deletions

View file

@ -0,0 +1,27 @@
type OptionButtonProps = {
text: string;
state: "idle" | "disabled" | "correct" | "wrong";
onSelect: () => void;
};
export const OptionButton = ({ text, state, onSelect }: OptionButtonProps) => {
const base =
"w-full py-3 px-6 rounded-2xl text-lg font-semibold transition-all duration-200 border-b-4 cursor-pointer";
const styles = {
idle: "bg-white text-purple-900 border-purple-200 hover:bg-purple-50 hover:border-purple-300 hover:-translate-y-0.5 active:translate-y-0 active:border-b-2",
disabled: "bg-gray-100 text-gray-400 border-gray-200 cursor-default",
correct: "bg-emerald-400 text-white border-emerald-600 scale-[1.02]",
wrong: "bg-pink-400 text-white border-pink-600",
};
return (
<button
className={`${base} ${styles[state]}`}
onClick={onSelect}
disabled={state !== "idle"}
>
{text}
</button>
);
};

View file

@ -0,0 +1,66 @@
import type { GameQuestion, AnswerResult } from "@glossa/shared";
import { OptionButton } from "./OptionButton";
type QuestionCardProps = {
question: GameQuestion;
questionNumber: number;
totalQuestions: number;
currentResult: AnswerResult | null;
onAnswer: (optionId: number) => void;
onNext: () => void;
};
export const QuestionCard = ({
question,
questionNumber,
totalQuestions,
currentResult,
onAnswer,
onNext,
}: QuestionCardProps) => {
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;
};
return (
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
<div className="flex items-center gap-2 text-sm font-medium text-purple-400">
<span>
{questionNumber} / {totalQuestions}
</span>
</div>
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
<h2 className="text-3xl font-bold text-purple-900 mb-2">
{question.prompt}
</h2>
{question.gloss && (
<p className="text-sm text-gray-400 italic">{question.gloss}</p>
)}
</div>
<div className="flex flex-col gap-3 w-full">
{question.options.map((option) => (
<OptionButton
key={option.optionId}
text={option.text}
state={getOptionState(option.optionId)}
onSelect={() => onAnswer(option.optionId)}
/>
))}
</div>
{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"
>
{questionNumber === totalQuestions ? "See Results" : "Next"}
</button>
)}
</div>
);
};

View file

@ -0,0 +1,60 @@
import type { AnswerResult } from "@glossa/shared";
type ScoreScreenProps = { results: AnswerResult[]; onPlayAgain: () => void };
export const ScoreScreen = ({ results, onPlayAgain }: ScoreScreenProps) => {
const score = results.filter((r) => r.isCorrect).length;
const total = results.length;
const percentage = Math.round((score / total) * 100);
const getMessage = () => {
if (percentage === 100) return "Perfect! 🎉";
if (percentage >= 80) return "Great job! 🌟";
if (percentage >= 60) return "Not bad! 💪";
if (percentage >= 40) return "Keep practicing! 📚";
return "Don't give up! 🔄";
};
return (
<div className="flex flex-col items-center gap-8 w-full max-w-md mx-auto">
<div className="bg-white rounded-3xl shadow-lg p-10 w-full text-center">
<p className="text-lg font-medium text-purple-400 mb-2">Your Score</p>
<h2 className="text-6xl font-bold text-purple-900 mb-1">
{score}/{total}
</h2>
<p className="text-2xl mb-6">{getMessage()}</p>
<div className="w-full bg-purple-100 rounded-full h-4 mb-2">
<div
className="bg-linear-to-r from-pink-400 to-purple-500 h-4 rounded-full transition-all duration-700"
style={{ width: `${percentage}%` }}
/>
</div>
<p className="text-sm text-gray-400">{percentage}% correct</p>
</div>
<div className="flex flex-col gap-2 w-full">
{results.map((result, index) => (
<div
key={result.questionId}
className={`flex items-center gap-3 py-2 px-4 rounded-xl text-sm ${
result.isCorrect
? "bg-emerald-50 text-emerald-700"
: "bg-pink-50 text-pink-700"
}`}
>
<span className="font-bold">{index + 1}.</span>
<span>{result.isCorrect ? "✓ Correct" : "✗ Wrong"}</span>
</div>
))}
</div>
<button
onClick={onPlayAgain}
className="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"
>
Play Again
</button>
</div>
);
};