Spaces:
Sleeping
Sleeping
| import { supabase } from '../supabaseClient'; | |
| import React, { useState } from 'react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| // --- SVG Icon Components --- | |
| const EyeIcon = () => (<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>); | |
| const EyeOffIcon = () => (<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>); | |
| export default function AppliLogin({ onNavigate }) { | |
| const [mode, setMode] = useState('login'); | |
| const [email, setEmail] = useState(''); | |
| const [password, setPassword] = useState(''); | |
| const [confirmPassword, setConfirmPassword] = useState(''); | |
| const [isPasswordVisible, setIsPasswordVisible] = useState(false); | |
| const [errors, setErrors] = useState({}); | |
| const [loading, setLoading] = useState(false); | |
| // Notification State | |
| const [notification, setNotification] = useState(null); | |
| const validate = () => { | |
| const newErrors = {}; | |
| if (!email.trim()) newErrors.email = 'Email is required.'; | |
| else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Email is invalid.'; | |
| if (mode !== 'forgot') { | |
| if (!password) newErrors.password = 'Password is required.'; | |
| } | |
| if (mode === 'register') { | |
| if (password !== confirmPassword) newErrors.confirmPassword = 'Passwords do not match.'; | |
| } | |
| return newErrors; | |
| }; | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| const formErrors = validate(); | |
| if (Object.keys(formErrors).length > 0) { | |
| setErrors(formErrors); | |
| return; | |
| } | |
| setErrors({}); | |
| setLoading(true); | |
| setNotification(null); | |
| try { | |
| if (mode === 'login') { | |
| const { data, error } = await supabase.auth.signInWithPassword({ | |
| email, | |
| password, | |
| }); | |
| if (error) throw error; | |
| // Check role | |
| const role = data.user?.user_metadata?.role; | |
| if (role === 'applicant') { | |
| onNavigate('dashboard'); | |
| } else { | |
| setNotification({ type: 'error', message: 'Unauthorized role detected.' }); | |
| await supabase.auth.signOut(); | |
| } | |
| } else if (mode === 'register') { | |
| const { error } = await supabase.auth.signUp({ | |
| email, | |
| password, | |
| options: { data: { role: 'applicant' } } | |
| }); | |
| if (error) throw error; | |
| setNotification({ type: 'success', message: 'Registration successful! Please confirm your Email and login.' }); | |
| setMode('login'); | |
| } else if (mode === 'forgot') { | |
| const { error } = await supabase.auth.resetPasswordForEmail(email, { | |
| redirectTo: `${window.location.origin}/reset-password`, | |
| }); | |
| if (error) throw error; | |
| setNotification({ type: 'success', message: 'Password reset link sent! Check your email.' }); | |
| setMode('login'); | |
| } | |
| } catch (error) { | |
| setNotification({ type: 'error', message: error.message }); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const formVariants = { | |
| hidden: { opacity: 0, y: 20 }, | |
| visible: { opacity: 1, y: 0 }, | |
| exit: { opacity: 0, y: -20 } | |
| }; | |
| const notificationStyles = { | |
| padding: '0.75rem 1rem', marginBottom: '1rem', borderRadius: '0.5rem', | |
| fontSize: '0.875rem', textAlign: 'center', border: '1px solid', | |
| }; | |
| return ( | |
| <div style={{ position: 'relative', minHeight: '100vh', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '1rem', overflow: 'hidden', backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif" }}> | |
| <style>{`@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { transition: background-color 5000s ease-in-out 0s; -webkit-text-fill-color: #fff ; }`}</style> | |
| {/* --- 🔴 FIXED BACK BUTTON (Z-INDEX 999) --- */} | |
| <button | |
| onClick={() => onNavigate('login')} // Ensure this matches your prop name | |
| style={{ | |
| position: 'fixed', // Changed from absolute to fixed | |
| top: '20px', | |
| left: '20px', | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| gap: '8px', | |
| padding: '12px 20px', | |
| backgroundColor: '#3a2f04', | |
| color: '#cebe65', | |
| border: '2px solid #3a2f04', | |
| borderRadius: '10px', | |
| fontWeight: '700', | |
| cursor: 'pointer', | |
| zIndex: 9999, // Super high Z-Index | |
| boxShadow: '0 4px 10px rgba(0,0,0,0.3)' | |
| }} | |
| > | |
| <span>⬅</span> Back | |
| </button> | |
| {/* Background Orbs */} | |
| <> | |
| <div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '384px', height: '384px', backgroundColor: '#FBBF24', top: '-50px', left: '-100px' }}></div> | |
| <div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#F59E0B', bottom: '-80px', right: '-120px' }}></div> | |
| </> | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| transition={{ duration: 0.4 }} | |
| style={{ width: '100%', maxWidth: '512px', borderRadius: '1rem', backgroundColor: 'rgba(251, 191, 36, 0.1)', backdropFilter: 'blur(12px)', border: '1px solid rgba(251, 191, 36, 0.3)', padding: '2rem', zIndex: 10 }}> | |
| {/* Notification */} | |
| <AnimatePresence> | |
| {notification && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }} | |
| style={{ | |
| ...notificationStyles, | |
| backgroundColor: notification.type === 'success' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)', | |
| borderColor: notification.type === 'success' ? 'rgba(5, 150, 105, 0.3)' : 'rgba(220, 38, 38, 0.3)', | |
| color: notification.type === 'success' ? '#34D399' : '#F87171' | |
| }} | |
| > | |
| {notification.message} | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| <AnimatePresence mode="wait"> | |
| <motion.div key={mode} variants={formVariants} initial="hidden" animate="visible" exit="exit" transition={{ duration: 0.3 }}> | |
| {/* --- Login Form --- */} | |
| {mode === 'login' && ( | |
| <div> | |
| <h1 style={{ fontSize: '0.875rem', fontWeight: 'bold', color: '#FCD34D', marginBottom: '0.5rem' }}>IRIS</h1> | |
| <h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Applicant Portal</h2> | |
| <form onSubmit={handleSubmit}> | |
| <div style={{ marginBottom: '1rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label> | |
| <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="lead@example.com" disabled={loading} style={{ width: '100%', padding: '0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.email ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| {errors.email && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem' }}>{errors.email}</p>} | |
| </div> | |
| <div style={{ marginBottom: '1.5rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Password</label> | |
| <div style={{ position: 'relative' }}> | |
| <input type={isPasswordVisible ? 'text' : 'password'} value={password} onChange={(e) => setPassword(e.target.value)} placeholder="••••••••" disabled={loading} style={{ width: '100%', padding: '0.75rem 3rem 0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.password ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| <button type="button" onClick={() => setIsPasswordVisible(!isPasswordVisible)} style={{ position: 'absolute', right: '0.5rem', top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', color: '#FCD34D', cursor: 'pointer' }}>{isPasswordVisible ? <EyeOffIcon /> : <EyeIcon />}</button> | |
| </div> | |
| {errors.password ? <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem' }}>{errors.password}</p> : <button type="button" onClick={() => setMode('forgot')} style={{ fontSize: '0.75rem', color: '#FCD34D', background: 'none', border: 'none', cursor: 'pointer', float: 'right', marginTop: '0.5rem' }}>Forgot Password?</button>} | |
| </div> | |
| <motion.button type="submit" whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.98 }} disabled={loading} style={{ width: '100%', backgroundColor: '#FBBF24', color: '#1a202c', fontWeight: 'bold', padding: '0.75rem 0', borderRadius: '0.5rem', border: 'none', cursor: 'pointer', marginTop: '1rem' }}>{loading ? 'Signing In...' : 'Sign In'}</motion.button> | |
| </form> | |
| <p style={{ fontSize: '0.75rem', textAlign: 'center', color: '#9ca3af', marginTop: '2rem' }}>Don't have an account? <button onClick={() => setMode('register')} style={{ color: '#FCD34D', fontWeight: '600', background: 'none', border: 'none', cursor: 'pointer' }}>Register For Free</button></p> | |
| </div> | |
| )} | |
| {/* --- Register Form --- */} | |
| {mode === 'register' && ( | |
| <div> | |
| <h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Create Account</h2> | |
| <form onSubmit={handleSubmit}> | |
| <div style={{ marginBottom: '1rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label> | |
| <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={loading} style={{ width: '100%', padding: '0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.email ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| </div> | |
| <div style={{ marginBottom: '1rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Password</label> | |
| <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} disabled={loading} style={{ width: '100%', padding: '0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.password ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| </div> | |
| <div style={{ marginBottom: '1.5rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Confirm Password</label> | |
| <input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={loading} style={{ width: '100%', padding: '0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.confirmPassword ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| </div> | |
| <motion.button type="submit" whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.98 }} disabled={loading} style={{ width: '100%', backgroundColor: '#FBBF24', color: '#1a202c', fontWeight: 'bold', padding: '0.75rem 0', borderRadius: '0.5rem', border: 'none', cursor: 'pointer' }}>{loading ? 'Registering...' : 'Register'}</motion.button> | |
| </form> | |
| <p style={{ fontSize: '0.75rem', textAlign: 'center', color: '#9ca3af', marginTop: '2rem' }}>Already have an account? <button onClick={() => setMode('login')} style={{ color: '#FCD34D', fontWeight: '600', background: 'none', border: 'none', cursor: 'pointer' }}>Sign In</button></p> | |
| </div> | |
| )} | |
| {/* --- Forgot Password --- */} | |
| {mode === 'forgot' && ( | |
| <div> | |
| <h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Forgot Password</h2> | |
| <form onSubmit={handleSubmit}> | |
| <div style={{ marginBottom: '1.5rem' }}> | |
| <label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label> | |
| <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={loading} style={{ width: '100%', padding: '0.75rem 1rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid ${errors.email ? '#EF4444' : 'rgba(251, 191, 36, 0.3)'}`, borderRadius: '0.5rem', color: 'white' }} /> | |
| </div> | |
| <motion.button type="submit" whileHover={{ scale: 1.03 }} whileTap={{ scale: 0.98 }} disabled={loading} style={{ width: '100%', backgroundColor: '#FBBF24', color: '#1a202c', fontWeight: 'bold', padding: '0.75rem 0', borderRadius: '0.5rem', border: 'none', cursor: 'pointer' }}>{loading ? 'Sending...' : 'Send Reset Link'}</motion.button> | |
| </form> | |
| <p style={{ fontSize: '0.75rem', textAlign: 'center', color: '#9ca3af', marginTop: '2rem' }}><button onClick={() => setMode('login')} style={{ color: '#FCD34D', fontWeight: '600', background: 'none', border: 'none', cursor: 'pointer' }}>Back to Sign In</button></p> | |
| </div> | |
| )} | |
| </motion.div> | |
| </AnimatePresence> | |
| </motion.div> | |
| </div> | |
| ); | |
| } |