feat : init Project
This commit is contained in:
224
nest-intra/src/pages/LoginPage.tsx
Normal file
224
nest-intra/src/pages/LoginPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user