Spaces:
Sleeping
Sleeping
File size: 14,621 Bytes
ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 59f9574 ea9ca44 59f9574 ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 9d6cc86 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | 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 !important; }`}</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>
);
} |