Files
2026-05-13 14:20:41 +00:00

175 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
// Проверяем авторизацию при загрузке
useEffect(() => {
async function checkAuth() {
try {
const response = await fetch('/api/auth/me', { credentials: 'same-origin' });
if (response.ok) {
router.push('/dashboard');
}
} catch (err) {
// Не авторизован, остаёмся на странице входа
}
}
checkAuth();
}, [router]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Ошибка входа');
}
// CSRF токен теперь в cookie с SameSite защитой
// Next.js автоматически отправит его с последующими запросами
router.push('/dashboard');
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Неизвестная ошибка');
setLoading(false);
}
}
return (
<div style={styles.container}>
<div style={styles.card}>
<h1 style={styles.title}>Uno Click</h1>
<p style={styles.subtitle}>Войдите для продолжения</p>
{error && <div style={styles.error}>{error}</div>}
<form onSubmit={handleSubmit}>
<div style={styles.formGroup}>
<label htmlFor="email" style={styles.label}>Email</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="test.user@uno-click.local"
style={styles.input}
/>
</div>
<div style={styles.formGroup}>
<label htmlFor="password" style={styles.label}>Пароль</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="test123"
style={styles.input}
/>
</div>
<button
type="submit"
disabled={loading}
style={{
...styles.button,
...(loading ? styles.buttonDisabled : {}),
}}
>
{loading ? 'Вход...' : 'Войти'}
</button>
</form>
</div>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
container: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
background: '#f5f5f5',
},
card: {
background: 'white',
padding: '40px',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
width: '100%',
maxWidth: '400px',
},
title: {
fontSize: '24px',
fontWeight: 600,
marginBottom: '8px',
color: '#333',
},
subtitle: {
color: '#666',
marginBottom: '32px',
fontSize: '14px',
},
formGroup: {
marginBottom: '20px',
},
label: {
display: 'block',
marginBottom: '6px',
fontSize: '14px',
color: '#333',
fontWeight: 500,
},
input: {
width: '100%',
padding: '12px 14px',
border: '1px solid #ddd',
borderRadius: '6px',
fontSize: '14px',
transition: 'border-color 0.2s',
},
button: {
width: '100%',
padding: '12px',
background: '#007bff',
color: 'white',
border: 'none',
borderRadius: '6px',
fontSize: '14px',
fontWeight: 500,
cursor: 'pointer',
transition: 'background 0.2s',
},
buttonDisabled: {
background: '#ccc',
cursor: 'not-allowed',
},
error: {
background: '#fee',
color: '#c00',
padding: '12px',
borderRadius: '6px',
marginBottom: '20px',
fontSize: '14px',
},
};