moex-vibe/apps/frontend/src/pages/LoginPage.tsx
Sergey Krylov c2478277e6
Some checks failed
CI / lint (pull_request) Failing after 1m45s
CI / test (pull_request) Failing after 1m42s
CI / build (pull_request) Failing after 1m31s
CI / lint (push) Failing after 1m46s
CI / test (push) Failing after 1m34s
CI / build (push) Failing after 1m35s
feat: add authentication system with JWT + RBAC
Backend:
- AuthModule with register, login, logout, refresh, profile endpoints
- JwtAuthGuard (global, opt-out via @Public) + RolesGuard (@Roles)
- PrismaModule with SQLite via @prisma/adapter-libsql (Prisma 7)
- Auth configuration in configuration.ts

Frontend:
- Auth context with session restoration via refresh cookie
- Login, Register, Profile pages with ProtectedRoute
- Auto-refresh on 401 with token rotation
- API client refactored for auth headers

Russian text: auth flows translated to Russian
All text translated: auth pages, profile, layout, loading states
2026-06-13 22:24:46 +03:00

97 lines
3.0 KiB
TypeScript
Raw 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.

import { useState, type FormEvent } from 'react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
export function LoginPage() {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { login } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const redirect = searchParams.get('redirect') || '/';
async function handleSubmit(e: FormEvent) {
e.preventDefault();
setError('');
setLoading(true);
try {
await login(email, password);
navigate(redirect);
} catch (err) {
setError(err instanceof Error ? err.message : 'Ошибка входа');
} finally {
setLoading(false);
}
}
return (
<div style={{ maxWidth: 400, margin: '60px auto' }}>
<h1 style={{ marginBottom: 24, fontSize: 24, fontWeight: 700 }}>Вход</h1>
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{error && (
<div style={{ color: 'var(--color-negative)', fontSize: 14 }}>{error}</div>
)}
<div>
<label style={{ display: 'block', marginBottom: 4, fontSize: 14, color: 'var(--color-text-secondary)' }}>
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
style={inputStyle}
placeholder="email@example.com"
/>
</div>
<div>
<label style={{ display: 'block', marginBottom: 4, fontSize: 14, color: 'var(--color-text-secondary)' }}>
Пароль
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
style={inputStyle}
placeholder="••••••••"
/>
</div>
<button type="submit" disabled={loading} style={buttonStyle}>
{loading ? 'Вход...' : 'Войти'}
</button>
<p style={{ textAlign: 'center', fontSize: 14, color: 'var(--color-text-secondary)' }}>
Нет аккаунта?{' '}
<Link to={`/register${redirect !== '/' ? `?redirect=${encodeURIComponent(redirect)}` : ''}`} style={{ color: 'var(--color-primary)' }}>
Зарегистрироваться
</Link>
</p>
</form>
</div>
);
}
const inputStyle: React.CSSProperties = {
width: '100%',
padding: '10px 12px',
border: '1px solid #e0e0e0',
borderRadius: 'var(--border-radius)',
fontSize: 16,
outline: 'none',
boxSizing: 'border-box',
};
const buttonStyle: React.CSSProperties = {
padding: '12px 24px',
background: 'var(--color-primary)',
color: '#fff',
border: 'none',
borderRadius: 'var(--border-radius)',
fontSize: 16,
fontWeight: 600,
cursor: 'pointer',
};