feat: quiz
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* The quiz button on a content page.
|
||||
*
|
||||
* Asks the BFF whether this page has a published quiz and renders nothing if
|
||||
* not, so it can sit on every content page without leaving an empty slot.
|
||||
* Its label tracks progress: Take quiz → Continue quiz → View results.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { ClipboardCheck } from "lucide-react";
|
||||
import { useAuth } from "@/components/auth/auth-provider";
|
||||
import { quiz, quizActionLabel, type QuizCard } from "@/lib/quiz";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export function QuizButton({
|
||||
path,
|
||||
className,
|
||||
}: {
|
||||
/** The content page's own path, e.g. /books/bg/2. */
|
||||
path: string;
|
||||
className?: string;
|
||||
}) {
|
||||
const { user } = useAuth();
|
||||
const [card, setCard] = useState<QuizCard | null>(null);
|
||||
|
||||
// Re-asked when the user changes, so progress appears right after sign-in.
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
quiz
|
||||
.forPath(path)
|
||||
.then((c) => {
|
||||
if (!cancelled) setCard(c);
|
||||
})
|
||||
.catch(() => {
|
||||
/* no quiz button is the right failure */
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [path, user?.id]);
|
||||
|
||||
if (!card) return null;
|
||||
|
||||
const done = card.my.state === "completed";
|
||||
return (
|
||||
<Link
|
||||
href={`/quizzes/${card.id}`}
|
||||
title={card.title}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium transition-colors",
|
||||
done
|
||||
? "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
: "border-primary/40 bg-primary/10 text-primary hover:bg-primary/15",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<ClipboardCheck className="size-3.5" />
|
||||
{quizActionLabel(card.my.state)}
|
||||
{card.my.state === "in_progress" && (
|
||||
<span className="tabular-nums opacity-70">
|
||||
{card.my.answered}/{card.my.total}
|
||||
</span>
|
||||
)}
|
||||
{done && card.my.score !== undefined && (
|
||||
<span className="tabular-nums opacity-70">
|
||||
{card.my.score}/{card.my.total}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user