+ Приведены все функции приложения в рабочий вид

+ Наведен порядок в файлах проекта
+ Наведен порядок в документации
+ Настроены скрипты установки, развертки и так далее, расширен MakeFile
This commit is contained in:
2025-11-02 06:09:55 +09:00
parent 367e1c932e
commit 2e535513b5
6103 changed files with 7040 additions and 1027861 deletions

View File

@@ -0,0 +1,131 @@
// src/app/auth/login/page.tsx
'use client'
import { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
type FormData = { username: string; password: string }
export default function LoginPage() {
const router = useRouter()
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>()
const [apiError, setApiError] = useState<string | null>(null)
useEffect(() => {
if (typeof window !== 'undefined' && localStorage.getItem('token')) {
router.push('/dashboard')
}
}, [router])
async function onSubmit(data: FormData) {
setApiError(null)
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/auth/login/`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}
)
if (!res.ok) {
const json = await res.json()
setApiError(json.detail || 'Ошибка входа')
return
}
const { access } = await res.json()
localStorage.setItem('token', access)
router.push('/dashboard')
} catch {
setApiError('Сетевая ошибка')
}
}
return (
<div className="min-vh-100 d-flex align-items-center justify-content-center bg-light">
<div className="container">
<div className="row justify-content-center">
<div className="col-md-6 col-lg-5">
<div className="card shadow-lg border-0 rounded-4">
<div className="card-body p-5">
<div className="text-center mb-4">
<img
src="/assets/img/CAT.png"
alt="CatLink"
width="80"
height="80"
className="mb-3"
/>
<h2 className="fw-bold text-primary">Добро пожаловать!</h2>
<p className="text-muted">Войдите в свой аккаунт CatLink</p>
</div>
{apiError && (
<div className="alert alert-danger" role="alert">
{apiError}
</div>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="username" className="form-label">Имя пользователя</label>
<input
id="username"
type="text"
placeholder="Введите имя пользователя"
className={`form-control form-control-lg ${errors.username ? 'is-invalid' : ''}`}
{...register('username', { required: 'Введите имя пользователя' })}
/>
{errors.username && (
<div className="invalid-feedback">{errors.username.message}</div>
)}
</div>
<div className="mb-4">
<label htmlFor="password" className="form-label">Пароль</label>
<input
id="password"
type="password"
placeholder="Введите пароль"
className={`form-control form-control-lg ${errors.password ? 'is-invalid' : ''}`}
{...register('password', { required: 'Введите пароль' })}
/>
{errors.password && (
<div className="invalid-feedback">{errors.password.message}</div>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="btn btn-primary btn-lg w-100 mb-3"
>
{isSubmitting ? (
<>
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
Входим...
</>
) : (
'Войти'
)}
</button>
</form>
<div className="text-center">
<p className="text-muted mb-0">
Нет аккаунта?{' '}
<Link href="/auth/register" className="text-primary text-decoration-none fw-bold">
Зарегистрироваться
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}