Добавлен middleware для обработки авторизации, страница входа с формой и валидацией, а также стили для страницы авторизации. Обновлены зависимости js-cookie и @types/js-cookie.
This commit is contained in:
88
src/app/auth/page.tsx
Normal file
88
src/app/auth/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
55
src/styles/auth.module.css
Normal file
55
src/styles/auth.module.css
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user