ai / components /AuthModal.tsx
Lianjx's picture
Upload 75 files
8fb4cca verified
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>
);
};