add QuizBuzzer, QuizHost, QuizDisplay components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 07:18:18 +01:00
parent d11c7780e9
commit 43268d8d86
3 changed files with 223 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import type { QuizQuestion } from "@celebrate-esc/shared"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface QuizBuzzerProps {
question: QuizQuestion
buzzStatus: "can_buzz" | "already_buzzed" | "excluded" | "waiting" | null
onBuzz: () => void
}
const difficultyColors: Record<string, string> = {
easy: "text-green-600",
medium: "text-yellow-600",
hard: "text-red-600",
}
export function QuizBuzzer({ question, buzzStatus, onBuzz }: QuizBuzzerProps) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Quiz Question {question.index + 1}/{question.total}</span>
<span className={`text-sm font-normal ${difficultyColors[question.difficulty] ?? ""}`}>
{question.difficulty}
</span>
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col items-center gap-4">
{question.status === "buzzing" && buzzStatus === "can_buzz" && (
<Button
size="lg"
className="h-24 w-full text-2xl"
onClick={onBuzz}
>
BUZZ!
</Button>
)}
{question.status === "buzzing" && buzzStatus === "excluded" && (
<p className="text-muted-foreground">You are excluded from this question.</p>
)}
{question.status === "judging" && buzzStatus === "already_buzzed" && (
<p className="text-lg font-semibold">You buzzed! Waiting for the host to judge...</p>
)}
{buzzStatus === "waiting" && (
<p className="text-muted-foreground">
{question.buzzerName} buzzed in! Waiting for judgment...
</p>
)}
{question.status === "resolved" && question.wasCorrect && (
<p className="text-lg font-semibold text-green-600">
{question.buzzerName} answered correctly!
</p>
)}
{question.status === "resolved" && question.wasCorrect === null && (
<p className="text-muted-foreground">
Question skipped.
</p>
)}
{question.status === "resolved" && question.wasCorrect === false && (
<p className="text-muted-foreground">
Question resolved no one answered correctly.
</p>
)}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,50 @@
import type { QuizQuestion } from "@celebrate-esc/shared"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface QuizDisplayProps {
question: QuizQuestion
}
const difficultyColors: Record<string, string> = {
easy: "text-green-600",
medium: "text-yellow-600",
hard: "text-red-600",
}
export function QuizDisplay({ question }: QuizDisplayProps) {
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Quiz Question {question.index + 1}/{question.total}</span>
<span className={`${difficultyColors[question.difficulty] ?? ""}`}>
{question.difficulty}
</span>
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-6">
<p className="text-center text-2xl font-semibold">{question.text}</p>
{question.status === "buzzing" && (
<p className="text-center text-xl text-muted-foreground">
Buzz in to answer!
</p>
)}
{question.status === "judging" && question.buzzerName && (
<p className="text-center text-xl font-semibold">
{question.buzzerName} buzzed in!
</p>
)}
{question.status === "resolved" && (
<p className="text-center text-xl">
{question.wasCorrect
? `${question.buzzerName} got it right!`
: "✗ No one got it right."}
</p>
)}
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,100 @@
import type { QuizQuestion } from "@celebrate-esc/shared"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface QuizHostProps {
question: QuizQuestion | null
onStartQuestion: () => void
onJudge: (correct: boolean) => void
onSkip: () => void
}
const difficultyColors: Record<string, string> = {
easy: "text-green-600",
medium: "text-yellow-600",
hard: "text-red-600",
}
export function QuizHost({ question, onStartQuestion, onJudge, onSkip }: QuizHostProps) {
if (!question) {
return (
<Card>
<CardHeader>
<CardTitle>Quiz</CardTitle>
</CardHeader>
<CardContent>
<Button onClick={onStartQuestion} className="w-full">
Start Next Question
</Button>
</CardContent>
</Card>
)
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<span>Question {question.index + 1}/{question.total}</span>
<span className={`text-sm font-normal ${difficultyColors[question.difficulty] ?? ""}`}>
{question.difficulty}
</span>
</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="rounded-md bg-muted p-3">
<p className="font-medium">{question.text}</p>
</div>
<div className="rounded-md border border-dashed p-3">
<p className="text-sm text-muted-foreground">Answer:</p>
<p className="font-medium">{question.answer}</p>
</div>
{question.status === "buzzing" && (
<div className="flex flex-col gap-2">
<p className="text-center text-muted-foreground">Waiting for someone to buzz...</p>
<Button variant="outline" onClick={onSkip} className="w-full">
Skip Question
</Button>
</div>
)}
{question.status === "judging" && question.buzzerName && (
<div className="flex flex-col gap-2">
<p className="text-center font-semibold">
{question.buzzerName} buzzed in!
</p>
<div className="flex gap-2">
<Button
variant="outline"
className="flex-1 border-red-300 text-red-600 hover:bg-red-50"
onClick={() => onJudge(false)}
>
Incorrect
</Button>
<Button
className="flex-1"
onClick={() => onJudge(true)}
>
Correct
</Button>
</div>
</div>
)}
{question.status === "resolved" && (
<div className="flex flex-col gap-2">
<p className="text-center text-muted-foreground">
{question.wasCorrect
? `${question.buzzerName} answered correctly!`
: "No one answered correctly."}
</p>
<Button onClick={onStartQuestion} className="w-full">
Next Question
</Button>
</div>
)}
</CardContent>
</Card>
)
}