feat: add ProtectedRoute and LoginPage

This commit is contained in:
2026-05-07 20:12:55 +03:00
parent 713eee7912
commit b52e3685ac
2 changed files with 137 additions and 0 deletions
+110
View File
@@ -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;
+27
View File
@@ -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;