feat: add ProtectedRoute and LoginPage
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Shield } from 'lucide-react';
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
const LoginPage = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const from = location.state?.from?.pathname || '/admin/kanban';
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setSubmitting(true);
|
||||
setError('');
|
||||
const { error } = await login(email, password);
|
||||
if (error) {
|
||||
setError('Неверный email или пароль');
|
||||
setSubmitting(false);
|
||||
} else {
|
||||
navigate(from, { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
const inputStyle = {
|
||||
width: '100%', padding: '14px',
|
||||
background: 'var(--bg-lighter)',
|
||||
border: '2px solid var(--border-light)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--text-main)',
|
||||
fontSize: '1rem',
|
||||
outline: 'none',
|
||||
fontFamily: 'var(--font-body)',
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
const labelStyle = {
|
||||
display: 'block', marginBottom: '8px',
|
||||
fontSize: '0.85rem', fontWeight: 700,
|
||||
color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.5px',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh', display: 'flex',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
background: 'var(--bg-light)',
|
||||
}}>
|
||||
<div className="glass-panel" style={{ padding: '50px', width: '100%', maxWidth: '420px', margin: '20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '40px' }}>
|
||||
<Shield size={26} color="var(--accent-blue)" />
|
||||
<span style={{ fontFamily: 'var(--font-heading)', fontSize: '1.4rem', fontWeight: 800 }}>
|
||||
Осколкам.<span style={{ color: 'var(--accent-blue)' }}>Нет</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h2 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.8rem', marginBottom: '30px', color: 'var(--text-main)' }}>
|
||||
Вход в панель
|
||||
</h2>
|
||||
|
||||
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
||||
<div>
|
||||
<label style={labelStyle}>Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
style={inputStyle}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent-blue)'}
|
||||
onBlur={(e) => e.target.style.borderColor = 'var(--border-light)'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label style={labelStyle}>Пароль</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
style={inputStyle}
|
||||
onFocus={(e) => e.target.style.borderColor = 'var(--accent-blue)'}
|
||||
onBlur={(e) => e.target.style.borderColor = 'var(--border-light)'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p style={{ color: '#ef4444', fontSize: '0.9rem', margin: 0 }}>{error}</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="btn-primary"
|
||||
style={{ marginTop: '8px', opacity: submitting ? 0.7 : 1, cursor: submitting ? 'not-allowed' : 'pointer' }}
|
||||
>
|
||||
{submitting ? 'Входим...' : 'Войти'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Navigate, Outlet, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from './useAuth';
|
||||
|
||||
const ProtectedRoute = () => {
|
||||
const { session, loading } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex', justifyContent: 'center', alignItems: 'center',
|
||||
height: '100vh', fontFamily: 'var(--font-heading)', color: 'var(--text-muted)',
|
||||
}}>
|
||||
Загрузка...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return <Navigate to="/admin/login" state={{ from: location }} replace />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
Reference in New Issue
Block a user