| |
|
| |
|
| | 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> |
| | ); |
| | }; |
| |
|