Spaces:
Sleeping
Sleeping
File size: 7,194 Bytes
ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 01c8f1f ea9ca44 9d6cc86 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 | import { supabase } from '../supabaseClient';
import React, { useState } from 'react';
import { motion } from 'framer-motion';
// --- SVG Icon Components ---
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>);
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>);
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>;
export default function AdminLogin({ onNavigate }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleAdminLogin = async (e) => {
e.preventDefault();
setError('');
if (!email.trim() || !password) {
setError('Email and password are required.');
return;
}
setLoading(true);
try {
// 1. Authenticate
const { data: { user }, error: authError } = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
if (authError) throw authError;
if (!user) throw new Error("Login failed.");
// 2. Check Role
const { data: roleData, error: roleError } = await supabase
.from('user_roles')
.select('role')
.eq('user_id', user.id)
.single();
if (roleError && roleError.code !== 'PGRST116') {
throw new Error("Could not verify user role.");
}
// 3. Admin Check
// Accepts 'admin' or 'recruiter' based on your previous logic
if (roleData && (roleData.role === 'admin' || roleData.role === 'recruiter')) {
if (typeof onNavigate === 'function') {
onNavigate('admin-dashboard');
}
} else {
await supabase.auth.signOut();
throw new Error("Access Denied. Authorized personnel only.");
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div style={{
position: 'relative', minHeight: '100vh', width: '100%', display: 'flex',
alignItems: 'center', justifyContent: 'center', overflow: 'hidden',
backgroundColor: '#020617', color: 'white', fontFamily: "'Montserrat', sans-serif", padding: '1rem',
}}>
<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 --- */}
<button
onClick={() => onNavigate('login')}
style={{
position: 'fixed', // Fixed to stay on top
top: '20px',
left: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
padding: '12px 20px',
backgroundColor: '#510000', // Red for Admin
color: '#ffffffbe',
border: '2px solid #510000',
borderRadius: '10px',
fontWeight: '700',
cursor: 'pointer',
zIndex: 9999, // High z-index
boxShadow: '0 4px 10px rgba(0,0,0,0.3)'
}}
>
<span>⬅</span> Back
</button>
{/* Background Shapes */}
<>
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '384px', height: '384px', backgroundColor: '#EF4444', top: '-50px', left: '-100px' }}></div>
<div style={{ position: 'absolute', borderRadius: '50%', filter: 'blur(80px)', opacity: 0.4, width: '320px', height: '320px', backgroundColor: '#DC2626', bottom: '-80px', right: '-120px' }}></div>
</>
<motion.div
initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5, ease: "easeOut" }}
style={{
width: '100%', maxWidth: '512px', borderRadius: '1rem', backgroundColor: 'rgba(239, 68, 68, 0.1)',
backdropFilter: 'blur(12px)', WebkitBackdropFilter: 'blur(12px)', border: '1px solid rgba(239, 68, 68, 0.3)',
padding: '2rem', zIndex: 10,
}}>
<div style={{ textAlign: 'center', marginBottom: '2rem' }}>
<h2 style={{ fontSize: '1.875rem', fontWeight: 'bold' }}>Admin Portal</h2>
<p style={{ color: '#d1d5db', marginTop: '0.25rem' }}>Manage CV submissions and applications</p>
</div>
<form onSubmit={handleAdminLogin}>
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ position: 'relative' }}>
<div style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', zIndex: 1 }}><UserIcon /></div>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} disabled={loading} placeholder="admin@email.com" 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' }} />
</div>
</div>
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ position: 'relative' }}>
<div style={{ position: 'absolute', left: '0.75rem', top: '50%', transform: 'translateY(-50%)', zIndex: 1 }}><LockIcon /></div>
<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' }} />
</div>
</div>
{error && <p style={{ color: '#F87171', fontSize: '0.875rem', marginTop: '-0.5rem', marginBottom: '1.5rem', textAlign: 'center' }}>{error}</p>}
<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 }}>
{loading && <SpinnerIcon />}
{loading ? 'Verifying...' : 'Continue'}
</motion.button>
</form>
</motion.div>
</div>
);
} |