Spaces:
Paused
Paused
| 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') { | |
| // Registration Validation | |
| 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') { | |
| // Reset Password Flow | |
| if (!isCodeSent) { | |
| // Simulate Sending Code | |
| if (formData.phone.length < 5) { | |
| setError("Please enter a valid phone number"); | |
| return; | |
| } | |
| setIsCodeSent(true); | |
| setSuccessMsg(`${t.authCodeSent} 8888`); // Simulation | |
| return; | |
| } | |
| // Verify Step | |
| 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: '' }); // Clear form | |
| 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> | |
| ); | |
| }; | |