the best in the west is mouaz
All checks were successful
Build frontend / build (push) Successful in 55s
All checks were successful
Build frontend / build (push) Successful in 55s
This commit is contained in:
350
app/account-verification/page.js
Normal file
350
app/account-verification/page.js
Normal file
@ -0,0 +1,350 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
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';
|
||||
|
||||
export default function AccountVerificationPage() {
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [emailCode, setEmailCode] = useState('');
|
||||
const [phoneCode, setPhoneCode] = useState('');
|
||||
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 {
|
||||
router.push('/login');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const handleSendEmailOTP = async () => {
|
||||
setSendingEmailOTP(true);
|
||||
try {
|
||||
await sendEmailOTP();
|
||||
toast.success('تم إرسال رمز التحقق إلى بريدك الإلكتروني');
|
||||
setShowEmailInput(true);
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'فشل إرسال رمز التحقق');
|
||||
} finally {
|
||||
setSendingEmailOTP(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyEmail = async () => {
|
||||
if (!emailCode.trim()) {
|
||||
toast.error('الرجاء إدخال رمز التحقق');
|
||||
return;
|
||||
}
|
||||
setVerifyingEmail(true);
|
||||
try {
|
||||
await verifyEmail(emailCode.trim());
|
||||
setEmailVerified(true);
|
||||
setShowEmailInput(false);
|
||||
toast.success('تم التحقق من البريد الإلكتروني بنجاح');
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'رمز التحقق غير صحيح');
|
||||
} finally {
|
||||
setVerifyingEmail(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendPhoneOTP = async () => {
|
||||
setSendingPhoneOTP(true);
|
||||
try {
|
||||
await sendPhoneOTP();
|
||||
toast.success('تم إرسال رمز التحقق إلى هاتفك');
|
||||
setShowPhoneInput(true);
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'فشل إرسال رمز التحقق');
|
||||
} finally {
|
||||
setSendingPhoneOTP(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyPhone = async () => {
|
||||
if (!phoneCode.trim()) {
|
||||
toast.error('الرجاء إدخال رمز التحقق');
|
||||
return;
|
||||
}
|
||||
setVerifyingPhone(true);
|
||||
try {
|
||||
await verifyPhone(phoneCode.trim());
|
||||
setPhoneVerified(true);
|
||||
setShowPhoneInput(false);
|
||||
toast.success('تم التحقق من رقم الهاتف بنجاح');
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'رمز التحقق غير صحيح');
|
||||
} finally {
|
||||
setVerifyingPhone(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.1 }
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: { opacity: 1, y: 0 }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
||||
<Toaster position="top-center" reverseOrder={false} />
|
||||
|
||||
<div className="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"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5" />
|
||||
</Link>
|
||||
<h1 className="text-2xl font-bold text-gray-900">التحقق من الحساب</h1>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-6"
|
||||
>
|
||||
<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" />
|
||||
<h2 className="text-lg font-semibold text-gray-800">البريد الإلكتروني</h2>
|
||||
{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" />
|
||||
موثق
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">{user?.email || 'بريد إلكتروني غير مسجل'}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{emailVerified
|
||||
? 'بريدك الإلكتروني موثق ويمكنك استخدامه لتسجيل الدخول'
|
||||
: 'يرجى توثيق بريدك الإلكتروني لحماية حسابك'}
|
||||
</p>
|
||||
</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"
|
||||
>
|
||||
{sendingEmailOTP ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Send className="w-4 h-4" />
|
||||
)}
|
||||
{sendingEmailOTP ? 'جاري الإرسال...' : 'إرسال رمز التحقق'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showEmailInput && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
className="space-y-3"
|
||||
>
|
||||
<p className="text-xs text-gray-500">أدخل الرمز المكون من 6 أرقام الذي تم إرساله إلى بريدك الإلكتروني</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={emailCode}
|
||||
onChange={(e) => setEmailCode(e.target.value)}
|
||||
placeholder="رمز التحقق"
|
||||
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"
|
||||
>
|
||||
{verifyingEmail ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Key className="w-4 h-4" />
|
||||
)}
|
||||
تحقق
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSendEmailOTP}
|
||||
disabled={sendingEmailOTP}
|
||||
className="text-xs text-amber-600 hover:text-amber-700"
|
||||
>
|
||||
إعادة إرسال الرمز
|
||||
</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" />
|
||||
<span className="text-sm font-medium">تم توثيق البريد الإلكتروني بنجاح</span>
|
||||
</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" />
|
||||
<h2 className="text-lg font-semibold text-gray-800">رقم الهاتف</h2>
|
||||
{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" />
|
||||
موثق
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600">{user?.phone || 'رقم هاتف غير مسجل'}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
{phoneVerified
|
||||
? 'رقم هاتفك موثق ويمكنك استخدامه لتسجيل الدخول'
|
||||
: 'يرجى توثيق رقم هاتفك لحماية حسابك'}
|
||||
</p>
|
||||
</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"
|
||||
>
|
||||
{sendingPhoneOTP ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Send className="w-4 h-4" />
|
||||
)}
|
||||
{sendingPhoneOTP ? 'جاري الإرسال...' : 'إرسال رمز التحقق'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPhoneInput && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
className="space-y-3"
|
||||
>
|
||||
<p className="text-xs text-gray-500">أدخل الرمز المكون من 6 أرقام الذي تم إرساله إلى هاتفك</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={phoneCode}
|
||||
onChange={(e) => setPhoneCode(e.target.value)}
|
||||
placeholder="رمز التحقق"
|
||||
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"
|
||||
>
|
||||
{verifyingPhone ? (
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Key className="w-4 h-4" />
|
||||
)}
|
||||
تحقق
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSendPhoneOTP}
|
||||
disabled={sendingPhoneOTP}
|
||||
className="text-xs text-amber-600 hover:text-amber-700"
|
||||
>
|
||||
إعادة إرسال الرمز
|
||||
</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" />
|
||||
<span className="text-sm font-medium">تم توثيق رقم الهاتف بنجاح</span>
|
||||
</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" />
|
||||
<h3 className="text-lg font-semibold">لماذا يجب توثيق حسابك؟</h3>
|
||||
</div>
|
||||
<ul className="space-y-2 text-sm text-amber-50">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||
حماية حسابك من الوصول غير المصرح به
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||
استعادة كلمة المرور بسهولة في حال فقدانها
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||
زيادة الثقة مع مالكي العقارات والمستأجرين
|
||||
</li>
|
||||
</ul>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user