Files
spsays-training/components/quiz/quiz-button.tsx
T
2026-10-15 21:09:37 +05:30

79 lines
2.3 KiB
TypeScript

"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.
* Quizzes are for signed-in readers only, so signed out it never asks.
* 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 userId = user?.id;
const [card, setCard] = useState<QuizCard | null>(null);
// Re-asked when the user changes, so progress appears right after sign-in.
useEffect(() => {
if (!userId) return;
let cancelled = false;
quiz
.forPath(path)
.then((c) => {
if (!cancelled) setCard(c);
})
.catch(() => {
/* no quiz button is the right failure */
});
return () => {
cancelled = true;
};
}, [path, userId]);
// A card left over from before sign-out is ignored rather than cleared.
if (!card || !user) 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>
);
}