39343-vm/pages/Auth.tsx
2026-03-27 12:21:43 +00:00

409 lines
19 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { StorageService } from '../services/storageService';
import { PlatformService } from '../services/platformService';
import { useNavigate } from 'react-router-dom';
import { UserProfile, UserRole } from '../types';
import { Button } from '../components/ui/Button';
import { Loader2, Eye, EyeOff, GraduationCap, Users, BookOpen, Sun, Moon, KeyRound, ChevronLeft, AtSign, ArrowLeft, CheckSquare, Square, AlertCircle, ShieldCheck, X, Facebook, Globe } from 'lucide-react';
import { Logo } from '../components/ui/Logo';
const Auth: React.FC = () => {
const [isLogin, setIsLogin] = useState(true);
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [role, setRole] = useState<UserRole>('student');
const [error, setError] = useState('');
const [rememberMe, setRememberMe] = useState(true);
const [isDarkMode, setIsDarkMode] = useState(() => {
if (typeof window !== 'undefined') {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) return savedTheme === 'dark';
return true;
}
return true;
});
const [showDemoMenu, setShowDemoMenu] = useState(false);
const navigate = useNavigate();
const [formData, setFormData] = useState({
email: '',
password: '',
name: '',
username: '',
schoolName: '',
birthCertificateId: '',
studentId: '',
guardianName: '',
grade: '',
subjects: '',
profession: ''
});
const [passwordStrength, setPasswordStrength] = useState(0);
const [passwordFeedback, setPasswordFeedback] = useState<string[]>([]);
useEffect(() => {
const root = window.document.documentElement;
if (isDarkMode) {
root.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
root.classList.remove('dark');
localStorage.setItem('theme', 'light');
}
}, [isDarkMode]);
const validatePassword = (pass: string) => {
const feedback = [];
let score = 0;
if (pass.length >= 8) score++; else feedback.push("At least 8 characters");
if (/[A-Z]/.test(pass)) score++; else feedback.push("One uppercase letter");
if (/[0-9]/.test(pass)) score++; else feedback.push("One number");
if (/[^A-Za-z0-9]/.test(pass)) score++; else feedback.push("One special character");
if (pass.length > 12) score++;
setPasswordStrength(score);
setPasswordFeedback(feedback);
return score;
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
if (e.target.name === 'password') {
validatePassword(e.target.value);
}
};
const validateEmail = (email: string) => {
return String(email)
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
};
const handleDemoLogin = async (type: 'student' | 'teacher') => {
setLoading(true);
let email = '';
const password = 'demo123';
switch (type) {
case 'student': email = 'student@demo.com'; break;
case 'teacher': email = 'teacher@demo.com'; break;
}
try {
const { success, error } = await StorageService.login(email, password, true);
if (success) {
navigate('/greeting');
} else {
setError(error || "Demo login failed.");
setLoading(false);
}
} catch (e) {
setError("Login error");
setLoading(false);
}
};
const handleSocialLogin = async (provider: 'facebook' | 'google') => {
setLoading(true);
// Simulate API delay
await new Promise(r => setTimeout(r, 1500));
// Simulate account linking
PlatformService.connect(provider, { username: provider === 'facebook' ? 'fb_user' : 'goog_user' });
// Login with a demo account to proceed to app
// In a real app, this would get a token and create/fetch a user
const { success } = await StorageService.login('student@demo.com', 'demo123', true);
if (success) {
navigate('/greeting');
} else {
setError("Social Auth Failed");
setLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!validateEmail(formData.email)) {
setError("Please enter a valid email address.");
return;
}
if (!isLogin) {
if (passwordStrength < 3) {
setError("Password is too weak. Please meet the requirements.");
return;
}
}
setLoading(true);
try {
if (isLogin) {
const { success, error } = await StorageService.login(formData.email, formData.password, rememberMe);
if (success) {
navigate('/greeting');
} else {
setError(error || "Invalid credentials.");
}
} else {
const newProfile: UserProfile = {
id: '',
email: formData.email,
name: formData.name,
username: formData.username || formData.email.split('@')[0],
role: role,
schoolName: (role === 'student' || role === 'teacher') ? formData.schoolName : undefined,
birthCertificateId: role === 'student' ? formData.birthCertificateId : undefined,
studentId: role === 'student' ? formData.studentId : undefined,
guardianName: role === 'student' ? formData.guardianName : undefined,
grade: role === 'student' ? formData.grade : undefined,
subjects: (role === 'student' || role === 'teacher') && formData.subjects ? formData.subjects.split(',').map(s => s.trim()) : undefined,
profession: role === 'citizen' ? formData.profession : (role === 'teacher' ? 'Teacher' : undefined),
points: 0,
xp: 0
};
const { success, error } = await StorageService.register(newProfile, formData.password, true);
if (success) {
navigate('/greeting');
} else {
setError(error || "Registration failed.");
}
}
} catch (err) {
setError("An unexpected error occurred.");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center relative overflow-hidden bg-black transition-colors duration-500 font-sans">
<div
className="absolute inset-0 bg-cover bg-center opacity-70"
style={{ backgroundImage: "url('https://images.unsplash.com/photo-1486870591958-9b9d0d1dda99?q=80&w=2400&auto=format&fit=crop')" }}
></div>
<div className="absolute inset-0 bg-gradient-to-b from-black/60 via-transparent to-black/90"></div>
<div className="absolute inset-0 bg-black/20"></div>
<div className="fixed top-6 left-6 z-50">
<button onClick={() => navigate('/welcome')} className="p-3 rounded-full bg-white/10 dark:bg-gray-900/40 backdrop-blur-md text-white shadow-xl border border-white/20 hover:scale-110 transition-all">
<ArrowLeft size={20}/>
</button>
</div>
<div className="fixed top-6 right-6 z-50">
<button
onClick={() => setIsDarkMode(!isDarkMode)}
className="p-3 rounded-full bg-white/10 dark:bg-gray-900/40 backdrop-blur-md text-white dark:text-yellow-400 shadow-xl border border-white/20 hover:scale-110 transition-all"
>
{isDarkMode ? <Sun size={20} /> : <Moon size={20} />}
</button>
</div>
<div className="relative z-10 w-full max-w-lg px-4 flex flex-col items-center">
<div className="text-center mb-10 animate-in fade-in slide-in-from-top-4 duration-1000">
<h1 className="text-5xl md:text-7xl font-black text-white tracking-tighter uppercase italic drop-shadow-[0_10px_20px_rgba(0,0,0,0.8)]">
Welcome to <span className="text-red-500 drop-shadow-[0_0_20px_rgba(220,38,38,0.5)]">Rudraksha</span>
</h1>
<p className="text-gray-200 text-sm md:text-base font-bold uppercase tracking-[0.4em] mt-4 opacity-80 drop-shadow-md">
Secure Digital Portal
</p>
</div>
<div className="bg-white/10 dark:bg-black/40 backdrop-blur-3xl p-8 md:p-12 rounded-[3.5rem] shadow-[0_30px_100px_rgba(0,0,0,0.5)] w-full border border-white/20 animate-in zoom-in duration-700">
<div className="text-center mb-8 flex flex-col items-center">
<div className="w-20 h-20 mb-6 transform hover:scale-110 transition-transform duration-500">
<Logo className="w-full h-full drop-shadow-xl" />
</div>
<p className="text-white font-bold uppercase tracking-widest text-xs opacity-60">
{isLogin ? "Authenticate to Access World" : "Create your digital identity"}
</p>
</div>
{error && (
<div className="bg-red-500/20 text-red-100 p-4 rounded-2xl text-sm mb-6 border border-red-500/30 font-bold flex items-center gap-2 animate-in shake">
<AlertCircle size={18} className="text-red-400" /> {error}
</div>
)}
{!isLogin && (
<div className="flex p-1.5 bg-white/10 dark:bg-black/30 rounded-2xl mb-8 gap-1 border border-white/10">
{[{id: 'student', icon: GraduationCap, label: 'Student'}, {id: 'teacher', icon: BookOpen, label: 'Teacher'}, {id: 'citizen', icon: Users, label: 'Citizen'}].map((r) => (
<button
key={r.id}
type="button"
onClick={() => setRole(r.id as UserRole)}
className={`flex-1 py-3 text-xs font-black rounded-xl flex flex-col items-center justify-center gap-1.5 transition-all ${
role === r.id
? 'bg-red-600 text-white shadow-lg scale-105'
: 'text-gray-400 hover:text-white'
}`}
>
<r.icon size={18} /> {r.label}
</button>
))}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<div className="relative group">
<AtSign className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-red-500 transition-colors" size={20}/>
<input
name="email" type="email" required
value={formData.email}
className="w-full pl-14 pr-5 py-4 rounded-2xl border-2 border-white/10 bg-black/20 text-white focus:border-red-500 outline-none transition-all font-bold text-sm placeholder-gray-500"
placeholder={role === 'citizen' ? "Email Address" : "School Email Address"}
onChange={handleChange}
/>
</div>
</div>
<div>
<div className="relative group">
<KeyRound className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-red-500 transition-colors" size={20}/>
<input
name="password"
type={showPassword ? "text" : "password"}
required
value={formData.password}
className="w-full pl-14 pr-12 py-4 rounded-2xl border-2 border-white/10 bg-black/20 text-white focus:border-red-500 outline-none transition-all font-bold text-sm placeholder-gray-500"
placeholder="Password"
onChange={handleChange}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-5 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
</div>
{!isLogin && formData.password && (
<div className="mt-3 space-y-2 animate-in fade-in slide-in-from-top-2">
<div className="h-1.5 w-full bg-white/10 rounded-full overflow-hidden flex gap-1">
{[1, 2, 3, 4, 5].map((i) => (
<div
key={i}
className={`flex-1 rounded-full h-full transition-all duration-500 ${
passwordStrength >= i
? (passwordStrength < 3 ? 'bg-red-500' : (passwordStrength < 4 ? 'bg-yellow-500' : 'bg-green-500'))
: 'opacity-0'
}`}
/>
))}
</div>
</div>
)}
</div>
{isLogin && (
<div className="flex items-center justify-between px-2">
<button type="button" onClick={() => setRememberMe(!rememberMe)} className="flex items-center gap-2 text-sm font-bold text-gray-400 hover:text-white transition-colors">
{rememberMe ? <CheckSquare size={18} className="text-red-500" /> : <Square size={18} />}
Remember Me
</button>
<button type="button" className="text-xs font-bold text-red-400 hover:text-red-300 uppercase tracking-wide">Forgot Pass?</button>
</div>
)}
{!isLogin && (
<div className="space-y-5 animate-in fade-in slide-in-from-top-4 pt-2">
<div className="grid grid-cols-1 gap-5">
<input
name="name" type="text" required
className="w-full px-6 py-4 rounded-2xl border-2 border-white/10 bg-black/20 text-white focus:border-red-500 outline-none transition-all font-bold text-sm"
placeholder="Full Name"
onChange={handleChange}
/>
<input
name="username" type="text" required
className="w-full px-6 py-4 rounded-2xl border-2 border-white/10 bg-black/20 text-white focus:border-red-500 outline-none transition-all font-bold text-sm"
placeholder="Username (e.g. ram_b)"
onChange={handleChange}
/>
</div>
</div>
)}
<Button type="submit" className="w-full h-16 text-lg font-black uppercase tracking-widest shadow-[0_15px_40px_rgba(220,38,38,0.3)] rounded-2xl bg-red-600 hover:bg-red-700 transform hover:scale-[1.02] active:scale-95 transition-all text-white border-none" disabled={loading}>
{loading ? <Loader2 className="animate-spin" /> : (isLogin ? "Enter Portal" : "Create Account")}
</Button>
{isLogin && (
<div className="space-y-3 pt-2">
<div className="relative flex py-2 items-center">
<div className="flex-grow border-t border-white/10"></div>
<span className="flex-shrink-0 mx-4 text-gray-500 text-[10px] font-black uppercase tracking-widest">Or Continue With</span>
<div className="flex-grow border-t border-white/10"></div>
</div>
<div className="grid grid-cols-2 gap-3">
<button type="button" onClick={() => handleSocialLogin('facebook')} className="flex items-center justify-center gap-2 bg-blue-600/20 hover:bg-blue-600 text-blue-200 hover:text-white border border-blue-500/30 p-3 rounded-2xl transition-all active:scale-95">
<Facebook size={18} /> <span className="text-xs font-bold">Facebook</span>
</button>
<button type="button" onClick={() => handleSocialLogin('google')} className="flex items-center justify-center gap-2 bg-white/10 hover:bg-white text-gray-200 hover:text-black border border-white/20 p-3 rounded-2xl transition-all active:scale-95">
<Globe size={18} /> <span className="text-xs font-bold">Google</span>
</button>
</div>
{!showDemoMenu ? (
<button
type="button"
onClick={() => setShowDemoMenu(true)}
className="w-full py-3 text-xs font-bold uppercase tracking-widest text-gray-400 hover:text-white flex items-center justify-center gap-2 transition-colors border-2 border-dashed border-white/10 rounded-2xl hover:bg-white/5 mt-4"
>
<KeyRound size={16} /> Quick Demo Access
</button>
) : (
<div className="bg-black/40 p-5 rounded-3xl border border-white/10 animate-in zoom-in-95 duration-200 mt-2">
<div className="flex justify-between items-center mb-4 px-2">
<span className="text-[10px] font-black text-gray-500 uppercase tracking-[0.2em]">Select Role</span>
<button type="button" onClick={() => setShowDemoMenu(false)} className="text-gray-500 hover:text-white">
<X size={16} />
</button>
</div>
<div className="grid grid-cols-2 gap-3">
<button type="button" onClick={() => handleDemoLogin('student')} className="flex flex-col items-center justify-center p-3 bg-white/5 hover:bg-white/10 rounded-2xl text-[10px] font-black uppercase tracking-wide transition-all group">
<GraduationCap size={24} className="mb-2 text-blue-400"/> Student
</button>
<button type="button" onClick={() => handleDemoLogin('teacher')} className="flex flex-col items-center justify-center p-3 bg-white/5 hover:bg-white/10 rounded-2xl text-[10px] font-black uppercase tracking-wide transition-all group">
<BookOpen size={24} className="mb-2 text-purple-400"/> Teacher
</button>
</div>
</div>
)}
</div>
)}
</form>
<div className="mt-8 text-center border-t border-white/10 pt-6">
<button
onClick={() => { setIsLogin(!isLogin); setError(''); }}
className="text-sm font-bold text-gray-400 hover:text-white transition-colors"
>
{isLogin ? "New User? Create Account" : "Existing User? Login"}
</button>
</div>
</div>
</div>
</div>
);
};
export default Auth;