2026-08-06 22:26:35 +03:00
|
|
|
"use client";
|
2026-05-25 21:27:39 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
import { useRouter } from "next/navigation";
|
|
|
|
|
import Link from "next/link";
|
|
|
|
|
import { motion } from "framer-motion";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import { Mail, Phone, Shield, ChevronLeft, Loader2, CheckCircle, XCircle, Send, Key } from "lucide-react";
|
|
|
|
|
import toast, { Toaster } from "react-hot-toast";
|
|
|
|
|
import AuthService from "../services/AuthService";
|
|
|
|
|
import { sendEmailOTP, sendPhoneOTP, verifyEmail, verifyPhone } from "../utils/api";
|
|
|
|
|
import InteractiveBackground from "../components/Animation/Background";
|
2026-05-25 21:27:39 +03:00
|
|
|
|
|
|
|
|
export default function AccountVerificationPage() {
|
|
|
|
|
const router = useRouter();
|
2026-07-01 15:43:58 +03:00
|
|
|
const { t } = useTranslation();
|
2026-05-25 21:27:39 +03:00
|
|
|
const [user, setUser] = useState(null);
|
|
|
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
const [emailCode, setEmailCode] = useState("");
|
|
|
|
|
const [phoneCode, setPhoneCode] = useState("");
|
2026-05-25 21:27:39 +03:00
|
|
|
const [sendingEmailOTP, setSendingEmailOTP] = useState(false);
|
|
|
|
|
const [sendingPhoneOTP, setSendingPhoneOTP] = useState(false);
|
|
|
|
|
const [verifyingEmail, setVerifyingEmail] = useState(false);
|
|
|
|
|
const [verifyingPhone, setVerifyingPhone] = useState(false);
|
|
|
|
|
const [emailVerified, setEmailVerified] = useState(false);
|
|
|
|
|
const [phoneVerified, setPhoneVerified] = useState(false);
|
|
|
|
|
const [showEmailInput, setShowEmailInput] = useState(false);
|
|
|
|
|
const [showPhoneInput, setShowPhoneInput] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const authUser = AuthService.getUser();
|
|
|
|
|
if (authUser) {
|
|
|
|
|
setUser(authUser);
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
} else {
|
2026-08-06 22:26:35 +03:00
|
|
|
router.push("/login");
|
2026-05-25 21:27:39 +03:00
|
|
|
}
|
|
|
|
|
}, [router]);
|
|
|
|
|
|
|
|
|
|
const handleSendEmailOTP = async () => {
|
|
|
|
|
setSendingEmailOTP(true);
|
|
|
|
|
try {
|
|
|
|
|
await sendEmailOTP();
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("accountVerification.emailOtpSent"));
|
2026-05-25 21:27:39 +03:00
|
|
|
setShowEmailInput(true);
|
|
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(err.message || t("accountVerification.otpSendFailed"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} finally {
|
|
|
|
|
setSendingEmailOTP(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleVerifyEmail = async () => {
|
|
|
|
|
if (!emailCode.trim()) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("accountVerification.otpRequired"));
|
2026-05-25 21:27:39 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setVerifyingEmail(true);
|
|
|
|
|
try {
|
|
|
|
|
await verifyEmail(emailCode.trim());
|
|
|
|
|
setEmailVerified(true);
|
|
|
|
|
setShowEmailInput(false);
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("accountVerification.emailVerifiedSuccess"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(err.message || t("accountVerification.otpInvalid"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} finally {
|
|
|
|
|
setVerifyingEmail(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSendPhoneOTP = async () => {
|
|
|
|
|
setSendingPhoneOTP(true);
|
|
|
|
|
try {
|
|
|
|
|
await sendPhoneOTP();
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("accountVerification.phoneOtpSent"));
|
2026-05-25 21:27:39 +03:00
|
|
|
setShowPhoneInput(true);
|
|
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(err.message || t("accountVerification.otpSendFailed"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} finally {
|
|
|
|
|
setSendingPhoneOTP(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleVerifyPhone = async () => {
|
|
|
|
|
if (!phoneCode.trim()) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("accountVerification.otpRequired"));
|
2026-05-25 21:27:39 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setVerifyingPhone(true);
|
|
|
|
|
try {
|
|
|
|
|
await verifyPhone(phoneCode.trim());
|
|
|
|
|
setPhoneVerified(true);
|
|
|
|
|
setShowPhoneInput(false);
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("accountVerification.phoneVerifiedSuccess"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(err.message || t("accountVerification.otpInvalid"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} finally {
|
|
|
|
|
setVerifyingPhone(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const containerVariants = {
|
|
|
|
|
hidden: { opacity: 0 },
|
|
|
|
|
visible: {
|
|
|
|
|
opacity: 1,
|
2026-08-06 22:26:35 +03:00
|
|
|
transition: { staggerChildren: 0.1 },
|
|
|
|
|
},
|
2026-05-25 21:27:39 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const itemVariants = {
|
|
|
|
|
hidden: { opacity: 0, y: 20 },
|
2026-08-06 22:26:35 +03:00
|
|
|
visible: { opacity: 1, y: 0 },
|
2026-05-25 21:27:39 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="min-h-screen py-8" dir="rtl">
|
2026-05-25 21:27:39 +03:00
|
|
|
<Toaster position="top-center" reverseOrder={false} />
|
2026-08-06 22:26:35 +03:00
|
|
|
<InteractiveBackground />
|
|
|
|
|
<div className="relative z-10 container mx-auto px-4 max-w-2xl">
|
|
|
|
|
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="flex items-center gap-3 mb-8">
|
|
|
|
|
<Link href="/settings" className="p-2 rounded-xl hover:bg-gray-200 transition-colors text-gray-600">
|
2026-05-25 21:27:39 +03:00
|
|
|
<ChevronLeft className="w-5 h-5" />
|
|
|
|
|
</Link>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h1 className="text-2xl font-bold text-gray-900">{t("accountVerification.title")}</h1>
|
2026-05-25 21:27:39 +03:00
|
|
|
</motion.div>
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-6">
|
2026-05-25 21:27:39 +03:00
|
|
|
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
|
|
|
|
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
|
|
|
|
<Mail className="w-5 h-5 text-amber-600" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-lg font-semibold text-gray-800">{t("accountVerification.emailSection")}</h2>
|
2026-05-25 21:27:39 +03:00
|
|
|
{emailVerified && (
|
|
|
|
|
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
|
|
|
|
<CheckCircle className="w-3 h-3" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("accountVerification.verified")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-6">
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<p className="text-sm text-gray-600">{user?.email || t("accountVerification.emailNotRegistered")}</p>
|
|
|
|
|
<p className="text-xs text-gray-400 mt-1">{emailVerified ? t("accountVerification.emailVerifiedDesc") : t("accountVerification.emailVerifyDesc")}</p>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
{!emailVerified && !showEmailInput && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSendEmailOTP}
|
|
|
|
|
disabled={sendingEmailOTP}
|
|
|
|
|
className="flex items-center gap-2 px-4 py-2 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors text-sm disabled:opacity-50"
|
|
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{sendingEmailOTP ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
|
|
|
|
|
{sendingEmailOTP ? t("accountVerification.sending") : t("accountVerification.sendOtp")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{showEmailInput && (
|
2026-08-06 22:26:35 +03:00
|
|
|
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} className="space-y-3">
|
|
|
|
|
<p className="text-xs text-gray-500">{t("accountVerification.enter6DigitCode")}</p>
|
2026-05-25 21:27:39 +03:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={emailCode}
|
|
|
|
|
onChange={(e) => setEmailCode(e.target.value)}
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("accountVerification.otpPlaceholder")}
|
2026-05-25 21:27:39 +03:00
|
|
|
maxLength={6}
|
|
|
|
|
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleVerifyEmail}
|
|
|
|
|
disabled={verifyingEmail}
|
|
|
|
|
className="px-6 py-3 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-colors text-sm font-medium disabled:opacity-50 flex items-center gap-2"
|
|
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{verifyingEmail ? <Loader2 className="w-4 h-4 animate-spin" /> : <Key className="w-4 h-4" />}
|
|
|
|
|
{t("accountVerification.verify")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<button onClick={handleSendEmailOTP} disabled={sendingEmailOTP} className="text-xs text-amber-600 hover:text-amber-700">
|
|
|
|
|
{t("accountVerification.resendCode")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{emailVerified && (
|
|
|
|
|
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
|
|
|
|
<CheckCircle className="w-5 h-5" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<span className="text-sm font-medium">{t("accountVerification.emailVerifiedSuccess")}</span>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
|
|
|
|
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
|
|
|
|
<Phone className="w-5 h-5 text-amber-600" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-lg font-semibold text-gray-800">{t("accountVerification.phoneSection")}</h2>
|
2026-05-25 21:27:39 +03:00
|
|
|
{phoneVerified && (
|
|
|
|
|
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
|
|
|
|
<CheckCircle className="w-3 h-3" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("accountVerification.verified")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-6">
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<p className="text-sm text-gray-600">{user?.phone || t("accountVerification.phoneNotRegistered")}</p>
|
|
|
|
|
<p className="text-xs text-gray-400 mt-1">{phoneVerified ? t("accountVerification.phoneVerifiedDesc") : t("accountVerification.phoneVerifyDesc")}</p>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
{!phoneVerified && !showPhoneInput && (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSendPhoneOTP}
|
|
|
|
|
disabled={sendingPhoneOTP}
|
|
|
|
|
className="flex items-center gap-2 px-4 py-2 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors text-sm disabled:opacity-50"
|
|
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{sendingPhoneOTP ? <Loader2 className="w-4 h-4 animate-spin" /> : <Send className="w-4 h-4" />}
|
|
|
|
|
{sendingPhoneOTP ? t("accountVerification.sending") : t("accountVerification.sendOtp")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{showPhoneInput && (
|
2026-08-06 22:26:35 +03:00
|
|
|
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} className="space-y-3">
|
|
|
|
|
<p className="text-xs text-gray-500">{t("accountVerification.enter6DigitCodePhone")}</p>
|
2026-05-25 21:27:39 +03:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={phoneCode}
|
|
|
|
|
onChange={(e) => setPhoneCode(e.target.value)}
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("accountVerification.otpPlaceholder")}
|
2026-05-25 21:27:39 +03:00
|
|
|
maxLength={6}
|
|
|
|
|
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleVerifyPhone}
|
|
|
|
|
disabled={verifyingPhone}
|
|
|
|
|
className="px-6 py-3 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-colors text-sm font-medium disabled:opacity-50 flex items-center gap-2"
|
|
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{verifyingPhone ? <Loader2 className="w-4 h-4 animate-spin" /> : <Key className="w-4 h-4" />}
|
|
|
|
|
{t("accountVerification.verify")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<button onClick={handleSendPhoneOTP} disabled={sendingPhoneOTP} className="text-xs text-amber-600 hover:text-amber-700">
|
|
|
|
|
{t("accountVerification.resendCode")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</button>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{phoneVerified && (
|
|
|
|
|
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
|
|
|
|
<CheckCircle className="w-5 h-5" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<span className="text-sm font-medium">{t("accountVerification.phoneVerifiedSuccess")}</span>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
<motion.div variants={itemVariants} className="bg-gradient-to-br from-amber-500 to-amber-600 rounded-2xl p-6 text-white">
|
|
|
|
|
<div className="flex items-center gap-3 mb-3">
|
|
|
|
|
<Shield className="w-6 h-6" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<h3 className="text-lg font-semibold">{t("accountVerification.whyVerify")}</h3>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<ul className="space-y-2 text-sm " style={{ color: "#0f172a", fontWeight: "400" }}>
|
2026-05-25 21:27:39 +03:00
|
|
|
<li className="flex items-center gap-2">
|
|
|
|
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("accountVerification.reason1")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</li>
|
|
|
|
|
<li className="flex items-center gap-2">
|
|
|
|
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("accountVerification.reason2")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</li>
|
|
|
|
|
<li className="flex items-center gap-2">
|
|
|
|
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("accountVerification.reason3")}
|
2026-05-25 21:27:39 +03:00
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|