Spaces:
Sleeping
Sleeping
Commit ·
01c8f1f
1
Parent(s): 3535722
feat: implement dynamic scoring, webhooks, and resume download
Browse files- src/components/CandidateDrawer.jsx +1 -1
- src/pages/AdminLogin.jsx +43 -31
- src/pages/AppliLogin.jsx +98 -117
src/components/CandidateDrawer.jsx
CHANGED
|
@@ -123,7 +123,7 @@ const CandidateDrawer = ({ isOpen, onClose, candidate }) => {
|
|
| 123 |
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '2rem' }}>
|
| 124 |
<div>
|
| 125 |
<h2 style={{ fontSize: '1.75rem', fontWeight: 'bold', color: 'white' }}>{candidate.name}</h2>
|
| 126 |
-
<p style={{ color: '#94a3b8' }}>{candidate.jobTitle || candidate.role} • {candidate.experience}
|
| 127 |
</div>
|
| 128 |
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#94a3b8', cursor: 'pointer' }}>
|
| 129 |
<X size={24} />
|
|
|
|
| 123 |
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '2rem' }}>
|
| 124 |
<div>
|
| 125 |
<h2 style={{ fontSize: '1.75rem', fontWeight: 'bold', color: 'white' }}>{candidate.name}</h2>
|
| 126 |
+
<p style={{ color: '#94a3b8' }}>{candidate.jobTitle || candidate.role} • {candidate.experience} </p>
|
| 127 |
</div>
|
| 128 |
<button onClick={onClose} style={{ background: 'none', border: 'none', color: '#94a3b8', cursor: 'pointer' }}>
|
| 129 |
<X size={24} />
|
src/pages/AdminLogin.jsx
CHANGED
|
@@ -3,20 +3,16 @@ import React, { useState } from 'react';
|
|
| 3 |
import { motion } from 'framer-motion';
|
| 4 |
|
| 5 |
// --- SVG Icon Components ---
|
| 6 |
-
const UserIcon = () => (
|
| 7 |
-
const LockIcon = () => (
|
| 8 |
-
const SpinnerIcon = () => <motion.svg animate={{ rotate: 360 }} transition={{ duration: 1, repeat: Infinity, ease: "linear" }} style={{ width: '20px', height: '20px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></motion.svg>;
|
| 9 |
-
|
| 10 |
|
| 11 |
export default function AdminLogin({ onNavigate }) {
|
| 12 |
const [email, setEmail] = useState('');
|
| 13 |
const [password, setPassword] = useState('');
|
| 14 |
-
|
| 15 |
-
// --- STATE UPDATES ---
|
| 16 |
const [loading, setLoading] = useState(false);
|
| 17 |
-
const [error, setError] = useState('');
|
| 18 |
|
| 19 |
-
// --- UPDATED LOGIN HANDLER ---
|
| 20 |
const handleAdminLogin = async (e) => {
|
| 21 |
e.preventDefault();
|
| 22 |
setError('');
|
|
@@ -29,40 +25,33 @@ export default function AdminLogin({ onNavigate }) {
|
|
| 29 |
setLoading(true);
|
| 30 |
|
| 31 |
try {
|
| 32 |
-
// 1. Authenticate
|
| 33 |
const { data: { user }, error: authError } = await supabase.auth.signInWithPassword({
|
| 34 |
email: email,
|
| 35 |
password: password,
|
| 36 |
});
|
| 37 |
|
| 38 |
if (authError) throw authError;
|
| 39 |
-
if (!user) throw new Error("Login failed
|
| 40 |
|
| 41 |
-
// 2.
|
| 42 |
const { data: roleData, error: roleError } = await supabase
|
| 43 |
-
.from('user_roles')
|
| 44 |
.select('role')
|
| 45 |
-
.eq('user_id', user.id)
|
| 46 |
.single();
|
| 47 |
|
| 48 |
-
// Handle case where no role row exists
|
| 49 |
if (roleError && roleError.code !== 'PGRST116') {
|
| 50 |
-
|
| 51 |
}
|
| 52 |
|
| 53 |
-
// 3.
|
| 54 |
-
//
|
| 55 |
-
|
| 56 |
-
// If this is a general login, checking for 'recruiter' is fine.
|
| 57 |
-
if (roleData && roleData.role === 'recruiter') {
|
| 58 |
-
|
| 59 |
-
// ✅ Success
|
| 60 |
if (typeof onNavigate === 'function') {
|
| 61 |
-
onNavigate('admin-dashboard');
|
| 62 |
}
|
| 63 |
-
|
| 64 |
} else {
|
| 65 |
-
// ❌ Access Denied
|
| 66 |
await supabase.auth.signOut();
|
| 67 |
throw new Error("Access Denied. Authorized personnel only.");
|
| 68 |
}
|
|
@@ -80,7 +69,32 @@ export default function AdminLogin({ onNavigate }) {
|
|
| 80 |
alignItems: 'center', justifyContent: 'center', overflow: 'hidden',
|
| 81 |
backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif", padding: '1rem',
|
| 82 |
}}>
|
| 83 |
-
<style>{`@import url('..
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
{/* Background Shapes */}
|
| 86 |
<>
|
|
@@ -88,14 +102,14 @@ export default function AdminLogin({ onNavigate }) {
|
|
| 88 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#DC2626', bottom: '-80px', right: '-120px' }}></div>
|
| 89 |
</>
|
| 90 |
|
| 91 |
-
<motion.div
|
| 92 |
initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease: "easeOut" }}
|
| 93 |
style={{
|
| 94 |
width: '100%', maxWidth: '512px', borderRadius: '1rem', backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
| 95 |
backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)', border: '1px solid rgba(239, 68, 68, 0.3)',
|
| 96 |
padding: '2rem', zIndex: 10,
|
| 97 |
}}>
|
| 98 |
-
|
| 99 |
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
|
| 100 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold' }}>Admin Portal</h2>
|
| 101 |
<p style={{ color: '#d1d5db', marginTop: '0.25rem' }}>Manage CV submissions and applications</p>
|
|
@@ -114,12 +128,10 @@ export default function AdminLogin({ onNavigate }) {
|
|
| 114 |
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} disabled={loading} placeholder="Password" style={{ width: '100%', padding: '0.75rem 1rem 0.75rem 2.5rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid rgba(239, 68, 68, 0.3)`, borderRadius: '0.5rem', color: 'white', boxSizing: 'border-box' }} />
|
| 115 |
</div>
|
| 116 |
</div>
|
| 117 |
-
|
| 118 |
-
{/* --- UI TO DISPLAY ANY ERROR --- */}
|
| 119 |
{error && <p style={{ color: '#F87171', fontSize: '0.875rem', marginTop: '-0.5rem', marginBottom: '1.5rem', textAlign: 'center' }}>{error}</p>}
|
| 120 |
|
| 121 |
<motion.button type="submit" disabled={loading} whileHover={{ scale: loading ? 1 : 1.03 }} whileTap={{ scale: loading ? 1 : 0.98 }} style={{ width: '100%', backgroundColor: '#EF4444', color: 'white', fontWeight: 'bold', padding: '0.75rem 0', borderRadius: '0.5rem', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', opacity: loading ? 0.7 : 1 }}>
|
| 122 |
-
{/* --- UI FEEDBACK FOR LOADING --- */}
|
| 123 |
{loading && <SpinnerIcon />}
|
| 124 |
{loading ? 'Verifying...' : 'Continue'}
|
| 125 |
</motion.button>
|
|
|
|
| 3 |
import { motion } from 'framer-motion';
|
| 4 |
|
| 5 |
// --- SVG Icon Components ---
|
| 6 |
+
const UserIcon = () => (<svg style={{ width: '20px', height: '20px', color: 'rgba(255, 255, 255, 0.7)' }} viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" /></svg>);
|
| 7 |
+
const LockIcon = () => (<svg style={{ width: '20px', height: '20px', color: 'rgba(255, 255, 255, 0.7)' }} viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" /></svg>);
|
| 8 |
+
const SpinnerIcon = () => <motion.svg animate={{ rotate: 360 }} transition={{ duration: 1, repeat: Infinity, ease: "linear" }} style={{ width: '20px', height: '20px' }} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></motion.svg>;
|
|
|
|
| 9 |
|
| 10 |
export default function AdminLogin({ onNavigate }) {
|
| 11 |
const [email, setEmail] = useState('');
|
| 12 |
const [password, setPassword] = useState('');
|
|
|
|
|
|
|
| 13 |
const [loading, setLoading] = useState(false);
|
| 14 |
+
const [error, setError] = useState('');
|
| 15 |
|
|
|
|
| 16 |
const handleAdminLogin = async (e) => {
|
| 17 |
e.preventDefault();
|
| 18 |
setError('');
|
|
|
|
| 25 |
setLoading(true);
|
| 26 |
|
| 27 |
try {
|
| 28 |
+
// 1. Authenticate
|
| 29 |
const { data: { user }, error: authError } = await supabase.auth.signInWithPassword({
|
| 30 |
email: email,
|
| 31 |
password: password,
|
| 32 |
});
|
| 33 |
|
| 34 |
if (authError) throw authError;
|
| 35 |
+
if (!user) throw new Error("Login failed.");
|
| 36 |
|
| 37 |
+
// 2. Check Role
|
| 38 |
const { data: roleData, error: roleError } = await supabase
|
| 39 |
+
.from('user_roles')
|
| 40 |
.select('role')
|
| 41 |
+
.eq('user_id', user.id)
|
| 42 |
.single();
|
| 43 |
|
|
|
|
| 44 |
if (roleError && roleError.code !== 'PGRST116') {
|
| 45 |
+
throw new Error("Could not verify user role.");
|
| 46 |
}
|
| 47 |
|
| 48 |
+
// 3. Admin Check
|
| 49 |
+
// Accepts 'admin' or 'recruiter' based on your previous logic
|
| 50 |
+
if (roleData && (roleData.role === 'admin' || roleData.role === 'recruiter')) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
if (typeof onNavigate === 'function') {
|
| 52 |
+
onNavigate('admin-dashboard');
|
| 53 |
}
|
|
|
|
| 54 |
} else {
|
|
|
|
| 55 |
await supabase.auth.signOut();
|
| 56 |
throw new Error("Access Denied. Authorized personnel only.");
|
| 57 |
}
|
|
|
|
| 69 |
alignItems: 'center', justifyContent: 'center', overflow: 'hidden',
|
| 70 |
backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif", padding: '1rem',
|
| 71 |
}}>
|
| 72 |
+
<style>{`@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); input:-webkit-autofill { -webkit-text-fill-color: #fff !important; -webkit-box-shadow: 0 0 0px 1000px rgba(239, 68, 68, 0.1) inset !important; }`}</style>
|
| 73 |
+
|
| 74 |
+
{/* --- 🔴 FIXED BACK BUTTON --- */}
|
| 75 |
+
<button
|
| 76 |
+
onClick={() => onNavigate('login')}
|
| 77 |
+
style={{
|
| 78 |
+
position: 'fixed', // Fixed to stay on top
|
| 79 |
+
top: '20px',
|
| 80 |
+
left: '20px',
|
| 81 |
+
display: 'flex',
|
| 82 |
+
alignItems: 'center',
|
| 83 |
+
justifyContent: 'center',
|
| 84 |
+
gap: '8px',
|
| 85 |
+
padding: '12px 20px',
|
| 86 |
+
backgroundColor: '#510000', // Red for Admin
|
| 87 |
+
color: '#ffffffbe',
|
| 88 |
+
border: '2px solid #510000',
|
| 89 |
+
borderRadius: '10px',
|
| 90 |
+
fontWeight: '700',
|
| 91 |
+
cursor: 'pointer',
|
| 92 |
+
zIndex: 9999, // High z-index
|
| 93 |
+
boxShadow: '0 4px 10px rgba(0,0,0,0.3)'
|
| 94 |
+
}}
|
| 95 |
+
>
|
| 96 |
+
<span>⬅</span> Back
|
| 97 |
+
</button>
|
| 98 |
|
| 99 |
{/* Background Shapes */}
|
| 100 |
<>
|
|
|
|
| 102 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#DC2626', bottom: '-80px', right: '-120px' }}></div>
|
| 103 |
</>
|
| 104 |
|
| 105 |
+
<motion.div
|
| 106 |
initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease: "easeOut" }}
|
| 107 |
style={{
|
| 108 |
width: '100%', maxWidth: '512px', borderRadius: '1rem', backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
| 109 |
backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)', border: '1px solid rgba(239, 68, 68, 0.3)',
|
| 110 |
padding: '2rem', zIndex: 10,
|
| 111 |
}}>
|
| 112 |
+
|
| 113 |
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
|
| 114 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold' }}>Admin Portal</h2>
|
| 115 |
<p style={{ color: '#d1d5db', marginTop: '0.25rem' }}>Manage CV submissions and applications</p>
|
|
|
|
| 128 |
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} disabled={loading} placeholder="Password" style={{ width: '100%', padding: '0.75rem 1rem 0.75rem 2.5rem', backgroundColor: 'rgba(255, 255, 255, 0.1)', border: `1px solid rgba(239, 68, 68, 0.3)`, borderRadius: '0.5rem', color: 'white', boxSizing: 'border-box' }} />
|
| 129 |
</div>
|
| 130 |
</div>
|
| 131 |
+
|
|
|
|
| 132 |
{error && <p style={{ color: '#F87171', fontSize: '0.875rem', marginTop: '-0.5rem', marginBottom: '1.5rem', textAlign: 'center' }}>{error}</p>}
|
| 133 |
|
| 134 |
<motion.button type="submit" disabled={loading} whileHover={{ scale: loading ? 1 : 1.03 }} whileTap={{ scale: loading ? 1 : 0.98 }} style={{ width: '100%', backgroundColor: '#EF4444', color: 'white', fontWeight: 'bold', padding: '0.75rem 0', borderRadius: '0.5rem', border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', opacity: loading ? 0.7 : 1 }}>
|
|
|
|
| 135 |
{loading && <SpinnerIcon />}
|
| 136 |
{loading ? 'Verifying...' : 'Continue'}
|
| 137 |
</motion.button>
|
src/pages/AppliLogin.jsx
CHANGED
|
@@ -2,9 +2,9 @@ import { supabase } from '../supabaseClient';
|
|
| 2 |
import React, { useState } from 'react';
|
| 3 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 4 |
|
| 5 |
-
// --- SVG Icon Components
|
| 6 |
-
const EyeIcon = () => (
|
| 7 |
-
const EyeOffIcon = () => (
|
| 8 |
|
| 9 |
export default function AppliLogin({ onNavigate }) {
|
| 10 |
const [mode, setMode] = useState('login');
|
|
@@ -14,15 +14,15 @@ export default function AppliLogin({ onNavigate }) {
|
|
| 14 |
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
| 15 |
const [errors, setErrors] = useState({});
|
| 16 |
const [loading, setLoading] = useState(false);
|
| 17 |
-
|
| 18 |
-
//
|
| 19 |
-
const [notification, setNotification] = useState(null);
|
| 20 |
|
| 21 |
const validate = () => {
|
| 22 |
const newErrors = {};
|
| 23 |
if (!email.trim()) newErrors.email = 'Email is required.';
|
| 24 |
else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Email is invalid.';
|
| 25 |
-
|
| 26 |
if (mode !== 'forgot') {
|
| 27 |
if (!password) newErrors.password = 'Password is required.';
|
| 28 |
}
|
|
@@ -33,7 +33,6 @@ export default function AppliLogin({ onNavigate }) {
|
|
| 33 |
return newErrors;
|
| 34 |
};
|
| 35 |
|
| 36 |
-
// --- FULLY MODIFIED handleSubmit Function ---
|
| 37 |
const handleSubmit = async (e) => {
|
| 38 |
e.preventDefault();
|
| 39 |
const formErrors = validate();
|
|
@@ -41,61 +40,51 @@ export default function AppliLogin({ onNavigate }) {
|
|
| 41 |
setErrors(formErrors);
|
| 42 |
return;
|
| 43 |
}
|
| 44 |
-
|
| 45 |
setErrors({});
|
| 46 |
setLoading(true);
|
| 47 |
-
setNotification(null);
|
| 48 |
|
| 49 |
try {
|
| 50 |
if (mode === 'login') {
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
}
|
| 67 |
-
}
|
| 68 |
-
else if (mode === 'register') {
|
| 69 |
-
// FIXED: Added options to pass the 'applicant' role to our trigger
|
| 70 |
const { error } = await supabase.auth.signUp({
|
| 71 |
email,
|
| 72 |
password,
|
| 73 |
-
options: {
|
| 74 |
-
data: {
|
| 75 |
-
role: 'applicant'
|
| 76 |
-
}
|
| 77 |
-
}
|
| 78 |
});
|
| 79 |
if (error) throw error;
|
| 80 |
-
setNotification({ type: 'success', message: 'Registration successful! Please login
|
| 81 |
setMode('login');
|
| 82 |
} else if (mode === 'forgot') {
|
| 83 |
-
// NEW: Added password reset logic
|
| 84 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
| 85 |
-
redirectTo: window.location.origin,
|
| 86 |
});
|
| 87 |
if (error) throw error;
|
| 88 |
setNotification({ type: 'success', message: 'Password reset link sent! Check your email.' });
|
| 89 |
setMode('login');
|
| 90 |
}
|
| 91 |
} catch (error) {
|
| 92 |
-
// FIXED: Using integrated notification instead of alert()
|
| 93 |
setNotification({ type: 'error', message: error.message });
|
| 94 |
} finally {
|
| 95 |
setLoading(false);
|
| 96 |
}
|
| 97 |
};
|
| 98 |
-
|
| 99 |
const formVariants = {
|
| 100 |
hidden: { opacity: 0, y: 20 },
|
| 101 |
visible: { opacity: 1, y: 0 },
|
|
@@ -103,64 +92,71 @@ export default function AppliLogin({ onNavigate }) {
|
|
| 103 |
};
|
| 104 |
|
| 105 |
const notificationStyles = {
|
| 106 |
-
padding: '0.75rem 1rem',
|
| 107 |
-
|
| 108 |
-
borderRadius: '0.5rem',
|
| 109 |
-
fontSize: '0.875rem',
|
| 110 |
-
textAlign: 'center',
|
| 111 |
-
border: '1px solid',
|
| 112 |
-
};
|
| 113 |
-
|
| 114 |
-
const successStyles = {
|
| 115 |
-
...notificationStyles,
|
| 116 |
-
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
| 117 |
-
borderColor: 'rgba(5, 150, 105, 0.3)',
|
| 118 |
-
color: '#34D399',
|
| 119 |
-
};
|
| 120 |
-
|
| 121 |
-
const errorStyles = {
|
| 122 |
-
...notificationStyles,
|
| 123 |
-
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
| 124 |
-
borderColor: 'rgba(220, 38, 38, 0.3)',
|
| 125 |
-
color: '#F87171',
|
| 126 |
};
|
| 127 |
|
| 128 |
return (
|
| 129 |
<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" }}>
|
| 130 |
-
<style>{`@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); input:-webkit-autofill
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
<>
|
| 132 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '384px', height: '384px', backgroundColor: '#FBBF24', top: '-50px', left: '-100px' }}></div>
|
| 133 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#F59E0B', bottom: '-80px', right: '-120px' }}></div>
|
| 134 |
</>
|
| 135 |
-
|
|
|
|
| 136 |
initial={{ opacity: 0, scale: 0.95 }}
|
| 137 |
animate={{ opacity: 1, scale: 1 }}
|
| 138 |
-
transition={{ duration: 0.4
|
| 139 |
-
style={{ width: '100%', maxWidth: '512px', borderRadius: '1rem', backgroundColor: 'rgba(251, 191, 36, 0.1)', backdropFilter: 'blur(12px)',
|
| 140 |
-
|
| 141 |
-
{/*
|
| 142 |
<AnimatePresence>
|
| 143 |
{notification && (
|
| 144 |
<motion.div
|
| 145 |
-
initial={{ opacity: 0, y: -10 }}
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
| 149 |
>
|
| 150 |
{notification.message}
|
| 151 |
</motion.div>
|
| 152 |
)}
|
| 153 |
</AnimatePresence>
|
| 154 |
-
|
| 155 |
<AnimatePresence mode="wait">
|
| 156 |
-
<motion.div
|
| 157 |
-
|
| 158 |
-
variants={formVariants}
|
| 159 |
-
initial="hidden"
|
| 160 |
-
animate="visible"
|
| 161 |
-
exit="exit"
|
| 162 |
-
transition={{ duration: 0.3 }}
|
| 163 |
-
>
|
| 164 |
{/* --- Login Form --- */}
|
| 165 |
{mode === 'login' && (
|
| 166 |
<div>
|
|
@@ -168,74 +164,59 @@ export default function AppliLogin({ onNavigate }) {
|
|
| 168 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Applicant Portal</h2>
|
| 169 |
<form onSubmit={handleSubmit}>
|
| 170 |
<div style={{ marginBottom: '1rem' }}>
|
| 171 |
-
<label
|
| 172 |
-
<input type="email"
|
| 173 |
-
{errors.email && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem'
|
| 174 |
</div>
|
| 175 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 176 |
-
<label
|
| 177 |
-
<div style={{ position: 'relative'
|
| 178 |
-
<input type={isPasswordVisible ? 'text' : 'password'}
|
| 179 |
-
<
|
| 180 |
-
<button type="button" onClick={() => setIsPasswordVisible(!isPasswordVisible)} title={isPasswordVisible ? 'Hide Password' : 'Show Password'} style={{ padding: '0.25rem', backgroundColor: 'transparent', border: 'none', cursor: 'pointer', color: '#FCD34D' }}>
|
| 181 |
-
{isPasswordVisible ? <EyeOffIcon /> : <EyeIcon />}
|
| 182 |
-
</button>
|
| 183 |
-
</div>
|
| 184 |
</div>
|
| 185 |
-
{errors.password ? <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem'
|
| 186 |
</div>
|
| 187 |
-
<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',
|
| 188 |
-
{loading ? 'Signing In...' : 'Sign In'}
|
| 189 |
-
</motion.button>
|
| 190 |
</form>
|
| 191 |
-
<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',
|
| 192 |
</div>
|
| 193 |
)}
|
| 194 |
-
|
| 195 |
{/* --- Register Form --- */}
|
| 196 |
{mode === 'register' && (
|
| 197 |
<div>
|
| 198 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Create Account</h2>
|
| 199 |
<form onSubmit={handleSubmit}>
|
| 200 |
<div style={{ marginBottom: '1rem' }}>
|
| 201 |
-
<label
|
| 202 |
-
<input type="email"
|
| 203 |
-
{errors.email && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem', textAlign: 'left' }}>{errors.email}</p>}
|
| 204 |
</div>
|
| 205 |
<div style={{ marginBottom: '1rem' }}>
|
| 206 |
-
<label
|
| 207 |
-
<input type="password"
|
| 208 |
-
{errors.password && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem', textAlign: 'left' }}>{errors.password}</p>}
|
| 209 |
</div>
|
| 210 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 211 |
-
<label
|
| 212 |
-
<input type="password"
|
| 213 |
-
{errors.confirmPassword && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem', textAlign: 'left' }}>{errors.confirmPassword}</p>}
|
| 214 |
</div>
|
| 215 |
-
<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'
|
| 216 |
-
{loading ? 'Registering...' : 'Register'}
|
| 217 |
-
</motion.button>
|
| 218 |
</form>
|
| 219 |
-
<p style={{ fontSize: '0.75rem', textAlign: 'center', color: '#9ca3af', marginTop: '2rem' }}>Already have an account? <button onClick={() => setMode('login')} style={{ color: '#FCD34D', fontWeight: '600',
|
| 220 |
</div>
|
| 221 |
)}
|
| 222 |
|
| 223 |
-
{/* --- Forgot Password
|
| 224 |
{mode === 'forgot' && (
|
| 225 |
<div>
|
| 226 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Forgot Password</h2>
|
| 227 |
-
<p style={{ color: '#d1d5db', marginBottom: '1.5rem', textAlign: 'center' }}>Enter your email and we'll send you a reset link.</p>
|
| 228 |
<form onSubmit={handleSubmit}>
|
| 229 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 230 |
-
<label
|
| 231 |
-
<input type="email"
|
| 232 |
-
{errors.email && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem', textAlign: 'left' }}>{errors.email}</p>}
|
| 233 |
</div>
|
| 234 |
-
<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'
|
| 235 |
-
{loading ? 'Sending...' : 'Send Reset Link'}
|
| 236 |
-
</motion.button>
|
| 237 |
</form>
|
| 238 |
-
<p style={{ fontSize: '0.75rem', textAlign: 'center', color: '#9ca3af', marginTop: '2rem' }}><button onClick={() => setMode('login')} style={{ color: '#FCD34D', fontWeight: '600',
|
| 239 |
</div>
|
| 240 |
)}
|
| 241 |
</motion.div>
|
|
|
|
| 2 |
import React, { useState } from 'react';
|
| 3 |
import { motion, AnimatePresence } from 'framer-motion';
|
| 4 |
|
| 5 |
+
// --- SVG Icon Components ---
|
| 6 |
+
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>);
|
| 7 |
+
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>);
|
| 8 |
|
| 9 |
export default function AppliLogin({ onNavigate }) {
|
| 10 |
const [mode, setMode] = useState('login');
|
|
|
|
| 14 |
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
| 15 |
const [errors, setErrors] = useState({});
|
| 16 |
const [loading, setLoading] = useState(false);
|
| 17 |
+
|
| 18 |
+
// Notification State
|
| 19 |
+
const [notification, setNotification] = useState(null);
|
| 20 |
|
| 21 |
const validate = () => {
|
| 22 |
const newErrors = {};
|
| 23 |
if (!email.trim()) newErrors.email = 'Email is required.';
|
| 24 |
else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Email is invalid.';
|
| 25 |
+
|
| 26 |
if (mode !== 'forgot') {
|
| 27 |
if (!password) newErrors.password = 'Password is required.';
|
| 28 |
}
|
|
|
|
| 33 |
return newErrors;
|
| 34 |
};
|
| 35 |
|
|
|
|
| 36 |
const handleSubmit = async (e) => {
|
| 37 |
e.preventDefault();
|
| 38 |
const formErrors = validate();
|
|
|
|
| 40 |
setErrors(formErrors);
|
| 41 |
return;
|
| 42 |
}
|
| 43 |
+
|
| 44 |
setErrors({});
|
| 45 |
setLoading(true);
|
| 46 |
+
setNotification(null);
|
| 47 |
|
| 48 |
try {
|
| 49 |
if (mode === 'login') {
|
| 50 |
+
const { data, error } = await supabase.auth.signInWithPassword({
|
| 51 |
+
email,
|
| 52 |
+
password,
|
| 53 |
+
});
|
| 54 |
+
if (error) throw error;
|
| 55 |
+
|
| 56 |
+
// Check role
|
| 57 |
+
const role = data.user?.user_metadata?.role;
|
| 58 |
+
if (role === 'applicant') {
|
| 59 |
+
onNavigate('dashboard');
|
| 60 |
+
} else {
|
| 61 |
+
setNotification({ type: 'error', message: 'Unauthorized role detected.' });
|
| 62 |
+
await supabase.auth.signOut();
|
| 63 |
+
}
|
| 64 |
+
} else if (mode === 'register') {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
const { error } = await supabase.auth.signUp({
|
| 66 |
email,
|
| 67 |
password,
|
| 68 |
+
options: { data: { role: 'applicant' } }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
});
|
| 70 |
if (error) throw error;
|
| 71 |
+
setNotification({ type: 'success', message: 'Registration successful! Please login.' });
|
| 72 |
setMode('login');
|
| 73 |
} else if (mode === 'forgot') {
|
|
|
|
| 74 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
| 75 |
+
redirectTo: window.location.origin,
|
| 76 |
});
|
| 77 |
if (error) throw error;
|
| 78 |
setNotification({ type: 'success', message: 'Password reset link sent! Check your email.' });
|
| 79 |
setMode('login');
|
| 80 |
}
|
| 81 |
} catch (error) {
|
|
|
|
| 82 |
setNotification({ type: 'error', message: error.message });
|
| 83 |
} finally {
|
| 84 |
setLoading(false);
|
| 85 |
}
|
| 86 |
};
|
| 87 |
+
|
| 88 |
const formVariants = {
|
| 89 |
hidden: { opacity: 0, y: 20 },
|
| 90 |
visible: { opacity: 1, y: 0 },
|
|
|
|
| 92 |
};
|
| 93 |
|
| 94 |
const notificationStyles = {
|
| 95 |
+
padding: '0.75rem 1rem', marginBottom: '1rem', borderRadius: '0.5rem',
|
| 96 |
+
fontSize: '0.875rem', textAlign: 'center', border: '1px solid',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
};
|
| 98 |
|
| 99 |
return (
|
| 100 |
<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" }}>
|
| 101 |
+
<style>{`@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap'); input:-webkit-autofill { -webkit-text-fill-color: #fff !important; -webkit-box-shadow: 0 0 0px 1000px rgba(251, 191, 36, 0.1) inset !important; }`}</style>
|
| 102 |
+
|
| 103 |
+
{/* --- 🔴 FIXED BACK BUTTON (Z-INDEX 999) --- */}
|
| 104 |
+
<button
|
| 105 |
+
onClick={() => onNavigate('login')} // Ensure this matches your prop name
|
| 106 |
+
style={{
|
| 107 |
+
position: 'fixed', // Changed from absolute to fixed
|
| 108 |
+
top: '20px',
|
| 109 |
+
left: '20px',
|
| 110 |
+
display: 'flex',
|
| 111 |
+
alignItems: 'center',
|
| 112 |
+
justifyContent: 'center',
|
| 113 |
+
gap: '8px',
|
| 114 |
+
padding: '12px 20px',
|
| 115 |
+
backgroundColor: '#3a2f04',
|
| 116 |
+
color: '#cebe65',
|
| 117 |
+
border: '2px solid #3a2f04',
|
| 118 |
+
borderRadius: '10px',
|
| 119 |
+
fontWeight: '700',
|
| 120 |
+
cursor: 'pointer',
|
| 121 |
+
zIndex: 9999, // Super high Z-Index
|
| 122 |
+
boxShadow: '0 4px 10px rgba(0,0,0,0.3)'
|
| 123 |
+
}}
|
| 124 |
+
>
|
| 125 |
+
<span>⬅</span> Back
|
| 126 |
+
</button>
|
| 127 |
+
|
| 128 |
+
{/* Background Orbs */}
|
| 129 |
<>
|
| 130 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '384px', height: '384px', backgroundColor: '#FBBF24', top: '-50px', left: '-100px' }}></div>
|
| 131 |
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#F59E0B', bottom: '-80px', right: '-120px' }}></div>
|
| 132 |
</>
|
| 133 |
+
|
| 134 |
+
<motion.div
|
| 135 |
initial={{ opacity: 0, scale: 0.95 }}
|
| 136 |
animate={{ opacity: 1, scale: 1 }}
|
| 137 |
+
transition={{ duration: 0.4 }}
|
| 138 |
+
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 }}>
|
| 139 |
+
|
| 140 |
+
{/* Notification */}
|
| 141 |
<AnimatePresence>
|
| 142 |
{notification && (
|
| 143 |
<motion.div
|
| 144 |
+
initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -10 }}
|
| 145 |
+
style={{
|
| 146 |
+
...notificationStyles,
|
| 147 |
+
backgroundColor: notification.type === 'success' ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)',
|
| 148 |
+
borderColor: notification.type === 'success' ? 'rgba(5, 150, 105, 0.3)' : 'rgba(220, 38, 38, 0.3)',
|
| 149 |
+
color: notification.type === 'success' ? '#34D399' : '#F87171'
|
| 150 |
+
}}
|
| 151 |
>
|
| 152 |
{notification.message}
|
| 153 |
</motion.div>
|
| 154 |
)}
|
| 155 |
</AnimatePresence>
|
| 156 |
+
|
| 157 |
<AnimatePresence mode="wait">
|
| 158 |
+
<motion.div key={mode} variants={formVariants} initial="hidden" animate="visible" exit="exit" transition={{ duration: 0.3 }}>
|
| 159 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
{/* --- Login Form --- */}
|
| 161 |
{mode === 'login' && (
|
| 162 |
<div>
|
|
|
|
| 164 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Applicant Portal</h2>
|
| 165 |
<form onSubmit={handleSubmit}>
|
| 166 |
<div style={{ marginBottom: '1rem' }}>
|
| 167 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label>
|
| 168 |
+
<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' }} />
|
| 169 |
+
{errors.email && <p style={{ color: '#F87171', fontSize: '0.75rem', marginTop: '0.5rem' }}>{errors.email}</p>}
|
| 170 |
</div>
|
| 171 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 172 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Password</label>
|
| 173 |
+
<div style={{ position: 'relative' }}>
|
| 174 |
+
<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' }} />
|
| 175 |
+
<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>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
</div>
|
| 177 |
+
{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>}
|
| 178 |
</div>
|
| 179 |
+
<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>
|
|
|
|
|
|
|
| 180 |
</form>
|
| 181 |
+
<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>
|
| 182 |
</div>
|
| 183 |
)}
|
| 184 |
+
|
| 185 |
{/* --- Register Form --- */}
|
| 186 |
{mode === 'register' && (
|
| 187 |
<div>
|
| 188 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Create Account</h2>
|
| 189 |
<form onSubmit={handleSubmit}>
|
| 190 |
<div style={{ marginBottom: '1rem' }}>
|
| 191 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label>
|
| 192 |
+
<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' }} />
|
|
|
|
| 193 |
</div>
|
| 194 |
<div style={{ marginBottom: '1rem' }}>
|
| 195 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Password</label>
|
| 196 |
+
<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' }} />
|
|
|
|
| 197 |
</div>
|
| 198 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 199 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Confirm Password</label>
|
| 200 |
+
<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' }} />
|
|
|
|
| 201 |
</div>
|
| 202 |
+
<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>
|
|
|
|
|
|
|
| 203 |
</form>
|
| 204 |
+
<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>
|
| 205 |
</div>
|
| 206 |
)}
|
| 207 |
|
| 208 |
+
{/* --- Forgot Password --- */}
|
| 209 |
{mode === 'forgot' && (
|
| 210 |
<div>
|
| 211 |
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold', marginBottom: '1.5rem', textAlign: 'center' }}>Forgot Password</h2>
|
|
|
|
| 212 |
<form onSubmit={handleSubmit}>
|
| 213 |
<div style={{ marginBottom: '1.5rem' }}>
|
| 214 |
+
<label style={{ display: 'block', fontSize: '0.875rem', fontWeight: '500', marginBottom: '0.5rem', color: '#d1d5db' }}>Email</label>
|
| 215 |
+
<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' }} />
|
|
|
|
| 216 |
</div>
|
| 217 |
+
<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>
|
|
|
|
|
|
|
| 218 |
</form>
|
| 219 |
+
<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>
|
| 220 |
</div>
|
| 221 |
)}
|
| 222 |
</motion.div>
|