Files
football-next/app/(user)/quiz/DailyQuizClient.tsx
2026-05-03 17:01:46 +03:30

255 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect, useCallback } from "react";
type Question = { id: string; questionText: string; options: string[]; order: number };
type Quiz = {
id: string;
windowStart: string | Date;
windowEnd: string | Date;
isProcessed: boolean;
questions: Question[];
};
function useCountdown(target: Date) {
const calc = useCallback(() => Math.max(0, target.getTime() - Date.now()), [target]);
const [ms, setMs] = useState(calc);
useEffect(() => {
const t = setInterval(() => setMs(calc()), 1000);
return () => clearInterval(t);
}, [calc]);
const s = Math.floor(ms / 1000);
return { hours: Math.floor(s / 3600), minutes: Math.floor((s % 3600) / 60), seconds: s % 60, done: ms === 0 };
}
function CountdownUnit({ value, label }: { value: number; label: string }) {
return (
<div className="flex flex-col items-center">
<div className="bg-white/10 backdrop-blur border border-white/20 rounded-xl w-16 h-16 flex items-center justify-center text-2xl font-bold tabular-nums">
{String(value).padStart(2, "0")}
</div>
<span className="text-xs text-gray-400 mt-1">{label}</span>
</div>
);
}
export default function DailyQuizClient({
quiz,
alreadySubmitted,
}: {
quiz: Quiz | null;
alreadySubmitted: boolean;
}) {
const [answers, setAnswers] = useState<(number | null)[]>(
quiz ? Array(quiz.questions.length).fill(null) : []
);
const [step, setStep] = useState(0);
const [submitted, setSubmitted] = useState(alreadySubmitted);
const [result, setResult] = useState<{
score: number;
correct: number;
total: number;
rewardTier: "GOLD" | "SILVER" | "BRONZE" | null;
rewardTierLabel: string | null;
} | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const windowEnd = quiz ? new Date(quiz.windowEnd) : new Date();
const windowStart = quiz ? new Date(quiz.windowStart) : new Date();
const countdown = useCountdown(windowEnd);
const startCountdown = useCountdown(windowStart);
const now = new Date();
const isProcessed = quiz?.isProcessed ?? false;
const isActive = quiz ? !isProcessed && now >= windowStart && now <= windowEnd : false;
const notStarted = quiz ? !isProcessed && now < windowStart : false;
async function handleSubmit() {
if (!quiz) return;
if (answers.some((a) => a === null)) {
setError("لطفاً به همه سوالات پاسخ دهید");
return;
}
setLoading(true);
setError("");
const res = await fetch("/api/quiz/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ quizId: quiz.id, answers }),
});
const data = await res.json();
if (res.ok) {
setResult(data);
setSubmitted(true);
} else {
setError(data.error ?? "خطا");
}
setLoading(false);
}
if (!quiz) {
return (
<div className="max-w-lg mx-auto text-center py-20">
<div className="text-6xl mb-4">*</div>
<h1 className="text-2xl font-bold mb-2">کوییزی برای امروز وجود ندارد</h1>
<p className="text-gray-400">فردا دوباره بیا</p>
</div>
);
}
if (submitted) {
return (
<div className="max-w-lg mx-auto text-center py-20">
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-2xl p-8">
{result ? (
<>
<div className="text-6xl mb-4">{result.score === 100 ? "*" : result.score >= 50 ? "+" : "-"}</div>
<h2 className="text-2xl font-bold mb-2">نتیجه شما</h2>
<div className="text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 to-amber-500 my-4">
{result.score}%
</div>
<p className="text-gray-300">{result.correct} از {result.total} سوال صحیح</p>
{result.score === 100 && (
<p className="mt-4 text-green-400 font-medium">شما در قرعهکشی Golden Card شرکت دارید</p>
)}
</>
) : (
<>
<div className="text-5xl mb-4">OK</div>
<h2 className="text-xl font-bold">پاسخهای شما ثبت شد</h2>
</>
)}
</div>
</div>
);
}
if (notStarted) {
return (
<div className="max-w-lg mx-auto text-center py-20">
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-2xl p-8">
<p className="text-gray-400 mb-4">کوییز هنوز شروع نشده و در این زمان باز میشود:</p>
<div className="flex justify-center gap-3">
<CountdownUnit value={startCountdown.hours} label="ساعت" />
<CountdownUnit value={startCountdown.minutes} label="دقیقه" />
<CountdownUnit value={startCountdown.seconds} label="ثانیه" />
</div>
</div>
</div>
);
}
if (!isActive) {
return (
<div className="max-w-lg mx-auto text-center py-20">
<div className="text-5xl mb-4">!</div>
<h2 className="text-xl font-bold">
{isProcessed ? "این کوییز بعد از قرعه‌کشی بسته شده است" : "بازه زمانی کوییز به پایان رسیده"}
</h2>
</div>
);
}
const q = quiz.questions[step];
const progress = ((step + 1) / quiz.questions.length) * 100;
return (
<div className="max-w-xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-2xl font-bold mb-1">کوییز روزانه</h1>
<p className="text-gray-400 text-sm">پاسخ صحیح = شانس برنده شدن Golden Card</p>
</div>
<div className="flex justify-center gap-3 mb-8">
<CountdownUnit value={countdown.hours} label="ساعت" />
<CountdownUnit value={countdown.minutes} label="دقیقه" />
<CountdownUnit value={countdown.seconds} label="ثانیه" />
</div>
<div className="mb-6">
<div className="flex justify-between text-xs text-gray-400 mb-1">
<span>سوال {step + 1} از {quiz.questions.length}</span>
<span>{Math.round(progress)}%</span>
</div>
<div className="h-1.5 bg-white/10 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-yellow-400 to-amber-500 rounded-full transition-all duration-500"
style={{ width: `${progress}%` }}
/>
</div>
</div>
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-2xl p-6 mb-4">
<p className="text-lg font-medium mb-6 leading-relaxed">{q.questionText}</p>
<div className="flex flex-col gap-3">
{q.options.map((opt, oi) => (
<button
key={oi}
type="button"
onClick={() => {
const updated = [...answers];
updated[step] = oi;
setAnswers(updated);
}}
className={`text-right px-4 py-3 rounded-xl border transition-all text-sm ${
answers[step] === oi
? "border-yellow-400 bg-yellow-400/10 text-yellow-300"
: "border-white/10 hover:border-white/30 hover:bg-white/5"
}`}
>
<span className="font-bold ml-2 text-gray-400">{["الف", "ب", "ج", "د"][oi]})</span>
{opt}
</button>
))}
</div>
</div>
{error && <p className="text-red-400 text-sm text-center mb-3">{error}</p>}
<div className="flex gap-3">
{step > 0 && (
<button
type="button"
onClick={() => setStep(step - 1)}
className="flex-1 py-3 rounded-xl border border-white/20 hover:bg-white/5 transition text-sm"
>
قبلی
</button>
)}
{step < quiz.questions.length - 1 ? (
<button
type="button"
onClick={() => {
if (answers[step] === null) {
setError("لطفاً یک گزینه انتخاب کنید");
return;
}
setError("");
setStep(step + 1);
}}
className="flex-1 py-3 rounded-xl bg-gradient-to-r from-yellow-500 to-amber-500 text-black font-bold hover:opacity-90 transition text-sm"
>
بعدی
</button>
) : (
<button
type="button"
onClick={handleSubmit}
disabled={loading}
className="flex-1 py-3 rounded-xl bg-gradient-to-r from-green-500 to-emerald-500 text-white font-bold hover:opacity-90 transition text-sm disabled:opacity-50"
>
{loading ? "در حال ارسال..." : "ثبت پاسخ‌ها"}
</button>
)}
</div>
</div>
);
}