add QuizBuzzer, QuizHost, QuizDisplay components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
73
packages/client/src/components/quiz-buzzer.tsx
Normal file
73
packages/client/src/components/quiz-buzzer.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
50
packages/client/src/components/quiz-display.tsx
Normal file
50
packages/client/src/components/quiz-display.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
packages/client/src/components/quiz-host.tsx
Normal file
100
packages/client/src/components/quiz-host.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user