From b52e3685ac67dc782885146e1de21da28a7c65ec Mon Sep 17 00:00:00 2001 From: houseassassin Date: Thu, 7 May 2026 20:12:55 +0300 Subject: [PATCH] feat: add ProtectedRoute and LoginPage --- src/admin/auth/LoginPage.jsx | 110 ++++++++++++++++++++++++++++++ src/admin/auth/ProtectedRoute.jsx | 27 ++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/admin/auth/LoginPage.jsx create mode 100644 src/admin/auth/ProtectedRoute.jsx diff --git a/src/admin/auth/LoginPage.jsx b/src/admin/auth/LoginPage.jsx new file mode 100644 index 0000000..17ecc8f --- /dev/null +++ b/src/admin/auth/LoginPage.jsx @@ -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 ( +
+
+
+ + + Осколкам.Нет + +
+ +

+ Вход в панель +

+ +
+
+ + 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)'} + /> +
+ +
+ + 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)'} + /> +
+ + {error && ( +

{error}

+ )} + + +
+
+
+ ); +}; + +export default LoginPage; diff --git a/src/admin/auth/ProtectedRoute.jsx b/src/admin/auth/ProtectedRoute.jsx new file mode 100644 index 0000000..9291613 --- /dev/null +++ b/src/admin/auth/ProtectedRoute.jsx @@ -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 ( +
+ Загрузка... +
+ ); + } + + if (!session) { + return ; + } + + return ; +}; + +export default ProtectedRoute;