Добавлен middleware для обработки авторизации, страница входа с формой и валидацией, а также стили для страницы авторизации. Обновлены зависимости js-cookie и @types/js-cookie.

This commit is contained in:
Redsandyg
2025-06-03 12:07:03 +03:00
parent 9b1cbc0300
commit 6ab1a42be7
6 changed files with 180 additions and 0 deletions

88
src/app/auth/page.tsx Normal file
View File

@@ -0,0 +1,88 @@
"use client";
import { useState, useEffect } from "react";
import Cookies from "js-cookie";
import styles from "../../styles/auth.module.css";
function hasToken() {
if (typeof document === "undefined") return false;
return Cookies.get('access_token') !== undefined;
}
export default function AuthPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
if (hasToken()) {
window.location.href = "/";
}
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
if (!email || !password) {
setError("Пожалуйста, заполните все поля");
setLoading(false);
return;
}
try {
const res = await fetch("/api/token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
login: email,
password: password,
}),
});
if (!res.ok) {
const data = await res.json();
setError(data.detail || "Ошибка авторизации");
setLoading(false);
return;
}
const data = await res.json();
// Сохраняем токен в куки на 60 минут через js-cookie
Cookies.set('access_token', data.access_token, { expires: 1/24, path: '/', sameSite: 'strict'});
setError("");
window.location.href = "/";
} catch (err) {
setError("Ошибка сети или сервера");
} finally {
setLoading(false);
}
};
return (
<div className={styles.authContainer}>
<h1 className={styles.authTitle}>Вход в систему</h1>
<form className={styles.authForm} onSubmit={handleSubmit}>
<input
className={styles.authInput}
type="text"
placeholder="Логин"
value={email}
onChange={e => setEmail(e.target.value)}
autoComplete="username"
/>
<input
className={styles.authInput}
type="password"
placeholder="Пароль"
value={password}
onChange={e => setPassword(e.target.value)}
autoComplete="current-password"
/>
{error && <div className={styles.authError}>{error}</div>}
<button className={styles.authButton} type="submit" disabled={loading}>
{loading ? "Вход..." : "Войти"}
</button>
</form>
</div>
);
}

View File

@@ -17,6 +17,7 @@ const navItems: NavItem[] = [
const Navigation: React.FC = () => {
const pathname = usePathname();
if (pathname === "/auth") return null;
return (
<nav className={styles.nav}>
<div className={styles.logo}>RE:Premium Partner</div>

View File

@@ -0,0 +1,55 @@
.authContainer {
max-width: 400px;
margin: 60px auto;
background: #fff;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0,0,0,0.07);
padding: 32px 32px 24px 32px;
display: flex;
flex-direction: column;
align-items: center;
}
.authTitle {
font-size: 2rem;
font-weight: 700;
margin-bottom: 24px;
color: #222;
}
.authForm {
width: 100%;
display: flex;
flex-direction: column;
gap: 18px;
}
.authInput {
padding: 12px 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
outline: none;
transition: border 0.2s;
}
.authInput:focus {
border: 1.5px solid #0070f3;
}
.authButton {
background: #0070f3;
color: #fff;
border: none;
border-radius: 8px;
padding: 12px 0;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.authButton:hover {
background: #005bb5;
}
.authError {
color: #e53935;
font-size: 0.95rem;
margin-top: -10px;
margin-bottom: 10px;
text-align: center;
}