fix: Resolve redundant quiz titles by merging them into the page header
All checks were successful
Automated Container Build / build-and-push (push) Successful in 55s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 55s
This commit is contained in:
parent
d3d094e442
commit
2457bfcb39
3 changed files with 64 additions and 34 deletions
|
|
@ -107,6 +107,11 @@ export default function QuizStudyPage() {
|
||||||
fetchQuizAndAttempts();
|
fetchQuizAndAttempts();
|
||||||
}, [fetchQuizAndAttempts]);
|
}, [fetchQuizAndAttempts]);
|
||||||
|
|
||||||
|
const [showTopics, setShowTopics] = useState(false);
|
||||||
|
const categories = quiz
|
||||||
|
? Array.from(new Set(quiz.questions.map(q => q.category.toUpperCase()))).join(" · ")
|
||||||
|
: "";
|
||||||
|
|
||||||
if (loading || !quiz) {
|
if (loading || !quiz) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
|
@ -121,25 +126,46 @@ export default function QuizStudyPage() {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto w-full py-2 md:py-4">
|
<div className="max-w-4xl mx-auto w-full py-2 md:py-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex flex-col md:flex-row items-start justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push(`/${classSlug}/quizzes`)}
|
onClick={() => router.push(`/${classSlug}/quizzes`)}
|
||||||
className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-text-heading mb-2 transition-colors cursor-pointer"
|
className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-text-heading mb-4 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
Back to quizzes
|
Back to quizzes
|
||||||
</button>
|
</button>
|
||||||
<h1 className="text-xl font-bold text-text-heading">{quiz.name}</h1>
|
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTopics(!showTopics)}
|
||||||
|
className="flex items-center gap-2 group cursor-pointer text-left focus:outline-none mb-2"
|
||||||
|
>
|
||||||
|
<h1 className="text-3xl font-bold font-serif text-text-heading group-hover:text-primary transition-colors">
|
||||||
|
{quiz.name}
|
||||||
|
</h1>
|
||||||
|
<svg
|
||||||
|
className={`w-6 h-6 text-text-muted transition-transform duration-200 ${showTopics ? "rotate-180" : ""}`}
|
||||||
|
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showTopics && (
|
||||||
|
<div className="text-sm tracking-widest text-text-muted font-medium mb-3 animate-in fade-in slide-in-from-top-2 duration-200 leading-relaxed">
|
||||||
|
{categories}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{quiz.description && (
|
{quiz.description && (
|
||||||
<p className="text-sm text-text-secondary mt-1">{quiz.description}</p>
|
<p className="text-sm text-text-secondary mt-1">{quiz.description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* View toggle & Share */}
|
{/* View toggle & Share */}
|
||||||
<div className="flex items-center gap-2 md:gap-4">
|
<div className="flex items-center gap-2 md:gap-4 self-end md:self-auto md:mt-10">
|
||||||
{view === "take" && (
|
{view === "take" && (
|
||||||
<button
|
<button
|
||||||
onClick={handleRestart}
|
onClick={handleRestart}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,44 @@ export function SharedViewer({ type, data }: SharedViewerProps) {
|
||||||
const [restartKey, setRestartKey] = useState(0);
|
const [restartKey, setRestartKey] = useState(0);
|
||||||
const [view, setView] = useState<"study" | "list">("study");
|
const [view, setView] = useState<"study" | "list">("study");
|
||||||
|
|
||||||
|
const [showTopics, setShowTopics] = useState(false);
|
||||||
|
const categories = type === "quizzes" && data.questions
|
||||||
|
? Array.from(new Set(data.questions.map((q: any) => q.category.toUpperCase()))).join(" · ")
|
||||||
|
: "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-8">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 mb-8">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h1 className="text-2xl font-bold text-text-heading mb-2">
|
{type === "quizzes" ? (
|
||||||
{data.name}
|
<>
|
||||||
</h1>
|
<button
|
||||||
|
onClick={() => setShowTopics(!showTopics)}
|
||||||
|
className="flex items-center gap-2 group cursor-pointer text-left focus:outline-none mb-2"
|
||||||
|
>
|
||||||
|
<h1 className="text-3xl font-bold font-serif text-text-heading group-hover:text-primary transition-colors">
|
||||||
|
{data.name}
|
||||||
|
</h1>
|
||||||
|
<svg
|
||||||
|
className={`w-6 h-6 text-text-muted transition-transform duration-200 ${showTopics ? "rotate-180" : ""}`}
|
||||||
|
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showTopics && (
|
||||||
|
<div className="text-sm tracking-widest text-text-muted font-medium mb-3 animate-in fade-in slide-in-from-top-2 duration-200 leading-relaxed">
|
||||||
|
{categories}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<h1 className="text-2xl font-bold text-text-heading mb-2">
|
||||||
|
{data.name}
|
||||||
|
</h1>
|
||||||
|
)}
|
||||||
|
|
||||||
<p className="text-text-secondary">
|
<p className="text-text-secondary">
|
||||||
From class: <span className="font-medium text-text-heading">{data.class.name}</span>
|
From class: <span className="font-medium text-text-heading">{data.class.name}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
||||||
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [resultsData, setResultsData] = useState<any | null>(null);
|
const [resultsData, setResultsData] = useState<any | null>(null);
|
||||||
const [showTopics, setShowTopics] = useState(false);
|
|
||||||
|
|
||||||
// Initialize session
|
// Initialize session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -268,35 +267,9 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
|
||||||
return <div>Question not found.</div>;
|
return <div>Question not found.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories = Array.from(new Set(quiz.questions.map(q => q.category.toUpperCase()))).join(" · ");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
|
|
||||||
{/* Title & Meta */}
|
|
||||||
<div className="w-full max-w-3xl mb-6 text-left">
|
|
||||||
<button
|
|
||||||
onClick={() => setShowTopics(!showTopics)}
|
|
||||||
className="flex items-center gap-2 group cursor-pointer text-left focus:outline-none"
|
|
||||||
>
|
|
||||||
<h1 className="text-3xl font-bold font-serif text-text-heading group-hover:text-primary transition-colors">
|
|
||||||
{quiz.name}
|
|
||||||
</h1>
|
|
||||||
<svg
|
|
||||||
className={`w-6 h-6 text-text-muted transition-transform duration-200 ${showTopics ? "rotate-180" : ""}`}
|
|
||||||
fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showTopics && (
|
|
||||||
<div className="text-sm tracking-widest text-text-muted font-medium mt-3 animate-in fade-in slide-in-from-top-2 duration-200 leading-relaxed">
|
|
||||||
{categories}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Unified Card */}
|
{/* Main Unified Card */}
|
||||||
<div className="w-full max-w-3xl bg-bg-surface-alt rounded-2xl border border-border-light shadow-[var(--shadow-card)] overflow-hidden mb-8">
|
<div className="w-full max-w-3xl bg-bg-surface-alt rounded-2xl border border-border-light shadow-[var(--shadow-card)] overflow-hidden mb-8">
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue