|
|
|
|
|
|
|
|
import React, { useState } from 'react'; |
|
|
import { X, User, Lock, Phone, CheckCircle, ArrowRight, AlertCircle, RefreshCw, MessageSquare, Eye, EyeOff } from 'lucide-react'; |
|
|
import { Language } from '../types'; |
|
|
import { TRANSLATIONS } from '../constants/translations'; |
|
|
|
|
|
interface AuthModalProps { |
|
|
isVisible: boolean; |
|
|
onClose: () => void; |
|
|
language: Language; |
|
|
onLogin: (phone: string, password?: string) => void; |
|
|
onRegister: (phone: string, name: string, pass: string) => void; |
|
|
onResetPassword?: (phone: string, newPass: string) => Promise<boolean>; |
|
|
} |
|
|
|
|
|
export const AuthModal: React.FC<AuthModalProps> = ({ isVisible, onClose, language, onLogin, onRegister, onResetPassword }) => { |
|
|
const [mode, setMode] = useState<'login' | 'register' | 'reset'>('login'); |
|
|
const [formData, setFormData] = useState({ phone: '', name: '', password: '', confirmPassword: '', code: '' }); |
|
|
const [error, setError] = useState<string | null>(null); |
|
|
const [successMsg, setSuccessMsg] = useState<string | null>(null); |
|
|
const [isCodeSent, setIsCodeSent] = useState(false); |
|
|
const [showPassword, setShowPassword] = useState(false); |
|
|
const t = TRANSLATIONS[language]; |
|
|
|
|
|
if (!isVisible) return null; |
|
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => { |
|
|
e.preventDefault(); |
|
|
setError(null); |
|
|
setSuccessMsg(null); |
|
|
|
|
|
if (mode === 'login') { |
|
|
onLogin(formData.phone, formData.password); |
|
|
} else if (mode === 'register') { |
|
|
|
|
|
if (!formData.name.trim()) { |
|
|
setError("Name is required"); |
|
|
return; |
|
|
} |
|
|
if (formData.password.length < 6) { |
|
|
setError("Password must be at least 6 characters"); |
|
|
return; |
|
|
} |
|
|
if (formData.password !== formData.confirmPassword) { |
|
|
setError(t.authPassMismatch); |
|
|
return; |
|
|
} |
|
|
onRegister(formData.phone, formData.name, formData.password); |
|
|
setFormData({ phone: '', name: '', password: '', confirmPassword: '', code: '' }); |
|
|
onClose(); |
|
|
} else if (mode === 'reset') { |
|
|
|
|
|
if (!isCodeSent) { |
|
|
|
|
|
if (formData.phone.length < 5) { |
|
|
setError("Please enter a valid phone number"); |
|
|
return; |
|
|
} |
|
|
setIsCodeSent(true); |
|
|
setSuccessMsg(`${t.authCodeSent} 8888`); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (formData.code !== '8888') { |
|
|
setError("Invalid verification code (Demo: 8888)"); |
|
|
return; |
|
|
} |
|
|
if (formData.password.length < 6) { |
|
|
setError("New password must be at least 6 characters"); |
|
|
return; |
|
|
} |
|
|
if (formData.password !== formData.confirmPassword) { |
|
|
setError(t.authPassMismatch); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (onResetPassword) { |
|
|
const success = await onResetPassword(formData.phone, formData.password); |
|
|
if (success) { |
|
|
setSuccessMsg(t.authResetSuccess); |
|
|
setTimeout(() => { |
|
|
setMode('login'); |
|
|
setIsCodeSent(false); |
|
|
setSuccessMsg(null); |
|
|
setFormData({ ...formData, password: '', confirmPassword: '', code: '' }); |
|
|
}, 2000); |
|
|
} else { |
|
|
setError(t.authPhoneNotFound); |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
const toggleMode = (newMode: 'login' | 'register' | 'reset') => { |
|
|
setMode(newMode); |
|
|
setFormData({ phone: '', name: '', password: '', confirmPassword: '', code: '' }); |
|
|
setError(null); |
|
|
setSuccessMsg(null); |
|
|
setIsCodeSent(false); |
|
|
setShowPassword(false); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm animate-fade-in"> |
|
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden relative"> |
|
|
<button onClick={onClose} className="absolute top-4 right-4 p-1 rounded-full hover:bg-gray-100 transition-colors z-10"> |
|
|
<X className="w-5 h-5 text-gray-500" /> |
|
|
</button> |
|
|
|
|
|
<div className="p-8"> |
|
|
<div className="text-center mb-6"> |
|
|
<div className={`w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3 transition-colors ${mode === 'login' ? 'bg-rose-100 text-rose-500' : mode === 'register' ? 'bg-green-100 text-green-500' : 'bg-blue-100 text-blue-500'}`}> |
|
|
{mode === 'login' ? <User className="w-6 h-6" /> : mode === 'register' ? <CheckCircle className="w-6 h-6" /> : <RefreshCw className="w-6 h-6" />} |
|
|
</div> |
|
|
<h2 className="text-2xl font-serif font-bold text-gray-900"> |
|
|
{mode === 'login' ? t.authLogin : mode === 'register' ? t.authRegister : t.authReset} |
|
|
</h2> |
|
|
<p className="text-sm text-gray-500 mt-1">Romantic Life Studio</p> |
|
|
</div> |
|
|
|
|
|
{error && ( |
|
|
<div className="mb-4 p-3 bg-red-50 border border-red-100 rounded-lg flex items-center gap-2 text-xs text-red-600 font-medium animate-pulse"> |
|
|
<AlertCircle className="w-4 h-4 shrink-0" /> |
|
|
{error} |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{successMsg && ( |
|
|
<div className="mb-4 p-3 bg-green-50 border border-green-100 rounded-lg flex items-center gap-2 text-xs text-green-600 font-medium animate-fade-in"> |
|
|
<CheckCircle className="w-4 h-4 shrink-0" /> |
|
|
{successMsg} |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4"> |
|
|
{mode === 'register' && ( |
|
|
<div className="relative group animate-fade-in-down"> |
|
|
<User className="absolute top-3 left-3 w-5 h-5 text-gray-400 group-focus-within:text-rose-500 transition-colors" /> |
|
|
<input |
|
|
type="text" |
|
|
placeholder={t.authNamePlace} |
|
|
required |
|
|
value={formData.name} |
|
|
onChange={e => setFormData({...formData, name: e.target.value})} |
|
|
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-gray-200 focus:border-rose-500 outline-none bg-gray-50 focus:bg-white transition-all" |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<div className="relative group"> |
|
|
<Phone className="absolute top-3 left-3 w-5 h-5 text-gray-400 group-focus-within:text-rose-500 transition-colors" /> |
|
|
<input |
|
|
type="tel" |
|
|
placeholder={t.authPhonePlace} |
|
|
required |
|
|
disabled={mode === 'reset' && isCodeSent} |
|
|
value={formData.phone} |
|
|
onChange={e => setFormData({...formData, phone: e.target.value})} |
|
|
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-gray-200 focus:border-rose-500 outline-none bg-gray-50 focus:bg-white transition-all disabled:bg-gray-100 disabled:text-gray-500" |
|
|
/> |
|
|
</div> |
|
|
|
|
|
{mode === 'reset' && isCodeSent && ( |
|
|
<div className="relative group animate-fade-in-down"> |
|
|
<MessageSquare className="absolute top-3 left-3 w-5 h-5 text-gray-400 group-focus-within:text-rose-500 transition-colors" /> |
|
|
<input |
|
|
type="text" |
|
|
placeholder={t.authCodePlace} |
|
|
required |
|
|
value={formData.code} |
|
|
onChange={e => setFormData({...formData, code: e.target.value})} |
|
|
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-gray-200 focus:border-rose-500 outline-none bg-gray-50 focus:bg-white transition-all" |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{/* Password Fields - Hidden for Reset step 1 */} |
|
|
{!(mode === 'reset' && !isCodeSent) && ( |
|
|
<div className="relative group"> |
|
|
<Lock className="absolute top-3 left-3 w-5 h-5 text-gray-400 group-focus-within:text-rose-500 transition-colors" /> |
|
|
<input |
|
|
type={showPassword ? "text" : "password"} |
|
|
placeholder={mode === 'reset' ? t.authNewPassPlace : t.authPassPlace} |
|
|
required |
|
|
value={formData.password} |
|
|
onChange={e => setFormData({...formData, password: e.target.value})} |
|
|
className="w-full pl-10 pr-10 py-2.5 rounded-lg border border-gray-200 focus:border-rose-500 outline-none bg-gray-50 focus:bg-white transition-all" |
|
|
/> |
|
|
<button |
|
|
type="button" |
|
|
onClick={() => setShowPassword(!showPassword)} |
|
|
className="absolute top-3 right-3 text-gray-400 hover:text-gray-600 focus:outline-none" |
|
|
tabIndex={-1} |
|
|
> |
|
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />} |
|
|
</button> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
{(mode === 'register' || (mode === 'reset' && isCodeSent)) && ( |
|
|
<div className="relative group animate-fade-in-down"> |
|
|
<CheckCircle className="absolute top-3 left-3 w-5 h-5 text-gray-400 group-focus-within:text-rose-500 transition-colors" /> |
|
|
<input |
|
|
type="password" |
|
|
placeholder={t.authConfirmPassPlace} |
|
|
required |
|
|
value={formData.confirmPassword} |
|
|
onChange={e => setFormData({...formData, confirmPassword: e.target.value})} |
|
|
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-gray-200 focus:border-rose-500 outline-none bg-gray-50 focus:bg-white transition-all" |
|
|
/> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<button type="submit" className={`w-full py-3 text-white rounded-xl font-bold shadow-lg transition-all flex items-center justify-center gap-2 mt-2 ${mode === 'login' ? 'bg-rose-600 hover:bg-rose-700 shadow-rose-200' : mode === 'reset' ? 'bg-blue-600 hover:bg-blue-700 shadow-blue-200' : 'bg-green-600 hover:bg-green-700 shadow-green-200'}`}> |
|
|
{mode === 'login' ? t.authLogin : mode === 'register' ? t.authRegister : isCodeSent ? t.authSubmit : t.authSendCode} |
|
|
{!(mode === 'reset' && !isCodeSent) && <ArrowRight className="w-4 h-4" />} |
|
|
</button> |
|
|
</form> |
|
|
|
|
|
{mode === 'login' && ( |
|
|
<div className="mt-3 text-right"> |
|
|
<button onClick={() => toggleMode('reset')} className="text-xs text-gray-500 hover:text-rose-500 transition-colors"> |
|
|
{t.authForgotPass} |
|
|
</button> |
|
|
</div> |
|
|
)} |
|
|
|
|
|
<div className="mt-6 text-center border-t border-gray-100 pt-4"> |
|
|
<button |
|
|
onClick={() => toggleMode(mode === 'login' ? 'register' : 'login')} |
|
|
className="text-sm text-rose-500 font-bold hover:text-rose-600 transition-colors hover:underline" |
|
|
> |
|
|
{mode === 'login' ? t.authNoAccount : t.authHasAccount} |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|