feat : init Project

This commit is contained in:
Thibault Pouch
2026-02-27 10:15:40 +01:00
parent 4bfc10fd98
commit 992b4340bd
20 changed files with 5790 additions and 21 deletions

View File

@@ -0,0 +1,224 @@
import { useState, useCallback } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { DEMO_CREDENTIALS } from '../data/mockData';
export default function LoginPage() {
const { login, isAuthenticated } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
// Redirect if already logged in
const from = (location.state as { from?: { pathname: string } })?.from?.pathname || '/intranet';
if (isAuthenticated) {
navigate(from, { replace: true });
return null;
}
const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
setError('');
if (!email.trim()) {
setError('Email is required.');
return;
}
setLoading(true);
const result = await login(email.trim(), password);
setLoading(false);
if (result.success) {
navigate(from, { replace: true });
} else {
setError(result.error || 'Login failed.');
}
},
[email, password, login, navigate, from]
);
const handleQuickLogin = useCallback(
async (credEmail: string) => {
setEmail(credEmail);
setError('');
setLoading(true);
const result = await login(credEmail, 'demo');
setLoading(false);
if (result.success) {
navigate(from, { replace: true });
} else {
setError(result.error || 'Login failed.');
}
},
[login, navigate, from]
);
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--color-bg)',
padding: '2rem',
}}
>
<div style={{ width: '100%', maxWidth: '400px' }}>
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: '2.5rem' }}>
<div
style={{
fontFamily: 'var(--font-heading)',
color: 'var(--color-amber)',
fontSize: '1.6rem',
letterSpacing: '0.08em',
marginBottom: '0.5rem',
}}
>
INTRANET
</div>
<div
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-text-muted)',
fontSize: '0.7rem',
letterSpacing: '0.15em',
}}
>
CROWMATE STUDIO STAFF ACCESS
</div>
</div>
{/* Login form */}
<div
style={{
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
padding: '2rem',
borderRadius: '8px',
}}
>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: '1.25rem' }}>
<label
style={{
display: 'block',
fontFamily: 'var(--font-mono)',
fontSize: '0.7rem',
color: 'var(--color-text-muted)',
letterSpacing: '0.1em',
marginBottom: '0.4rem',
}}
>
EMAIL
</label>
<input
type="email"
className={`input-terminal${error ? ' error' : ''}`}
placeholder="staff@crowmate.dev"
value={email}
onChange={(e) => {
setEmail(e.target.value);
setError('');
}}
disabled={loading}
autoFocus
/>
</div>
<div style={{ marginBottom: '1.25rem' }}>
<label
style={{
display: 'block',
fontFamily: 'var(--font-mono)',
fontSize: '0.7rem',
color: 'var(--color-text-muted)',
letterSpacing: '0.1em',
marginBottom: '0.4rem',
}}
>
PASSWORD
</label>
<input
type="password"
className="input-terminal"
placeholder="Enter password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
/>
</div>
{error && (
<div
style={{
color: 'var(--color-red)',
fontFamily: 'var(--font-mono)',
fontSize: '0.75rem',
marginBottom: '1rem',
padding: '0.5rem 0.75rem',
background: 'rgba(220,38,38,0.06)',
border: '1px solid rgba(220,38,38,0.2)',
borderRadius: '4px',
}}
>
{error}
</div>
)}
<button
type="submit"
className="btn-terminal btn-amber"
disabled={loading}
style={{ width: '100%', justifyContent: 'center', opacity: loading ? 0.6 : 1 }}
>
{loading ? 'Authenticating...' : '> Login'}
</button>
</form>
</div>
{/* Quick login */}
<div style={{ marginTop: '1.5rem' }}>
<div
style={{
fontFamily: 'var(--font-mono)',
color: 'var(--color-text-muted)',
fontSize: '0.65rem',
letterSpacing: '0.1em',
textAlign: 'center',
marginBottom: '0.75rem',
}}
>
QUICK LOGIN (DEMO)
</div>
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center' }}>
<button
className="btn-terminal"
style={{ padding: '0.35rem 0.8rem', fontSize: '0.7rem' }}
onClick={() => handleQuickLogin(DEMO_CREDENTIALS.admin)}
disabled={loading}
>
Admin (Kestrel)
</button>
<button
className="btn-terminal"
style={{ padding: '0.35rem 0.8rem', fontSize: '0.7rem' }}
onClick={() => handleQuickLogin(DEMO_CREDENTIALS.staff)}
disabled={loading}
>
Staff (Vesper)
</button>
</div>
</div>
</div>
</div>
);
}