255 lines
8.8 KiB
TypeScript
255 lines
8.8 KiB
TypeScript
"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>
|
||
);
|
||
}
|