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

+ Наведен порядок в файлах проекта
+ Наведен порядок в документации
+ Настроены скрипты установки, развертки и так далее, расширен 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>
)
}

View File

@@ -0,0 +1,237 @@
'use client'
import React, { useState, useEffect } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import Image from 'next/image'
export default function RegisterPage() {
const [formData, setFormData] = useState({
username: '',
email: '',
first_name: '',
last_name: '',
password: '',
password2: ''
})
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const router = useRouter()
useEffect(() => {
// Автозаполнение email из главной страницы
const quickStartEmail = localStorage.getItem('quickStartEmail')
if (quickStartEmail) {
setFormData(prev => ({ ...prev, email: quickStartEmail }))
localStorage.removeItem('quickStartEmail')
}
}, [])
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
...formData,
[e.target.name]: e.target.value
})
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError(null)
if (formData.password !== formData.password2) {
setError('Пароли не совпадают')
setLoading(false)
return
}
try {
const API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
const response = await fetch(`${API}/api/auth/register/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
if (response.ok) {
// Регистрация прошла успешно, перенаправляем на страницу входа
router.push('/auth/login?message=registration_success')
} else {
const errorData = await response.json()
setError(errorData.message || 'Ошибка регистрации')
}
} catch (err) {
setError('Ошибка соединения с сервером')
} finally {
setLoading(false)
}
}
return (
<div className="min-vh-100 d-flex align-items-center bg-light">
<div className="container">
<div className="row justify-content-center">
<div className="col-md-8 col-lg-6 col-xl-5">
<div className="card shadow-sm border-0">
<div className="card-body p-5">
{/* Логотип */}
<div className="text-center mb-4">
<Image
src="/assets/img/CAT.png"
alt="CatLink"
width={64}
height={64}
className="mb-3"
/>
<h2 className="fw-bold text-primary">Создать аккаунт</h2>
<p className="text-muted">Присоединяйтесь к CatLink сегодня</p>
</div>
{/* Форма регистрации */}
<form onSubmit={handleSubmit}>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<div className="row">
<div className="col-sm-6 mb-3">
<label htmlFor="first_name" className="form-label">
Имя
</label>
<input
type="text"
className="form-control"
id="first_name"
name="first_name"
value={formData.first_name}
onChange={handleChange}
required
/>
</div>
<div className="col-sm-6 mb-3">
<label htmlFor="last_name" className="form-label">
Фамилия
</label>
<input
type="text"
className="form-control"
id="last_name"
name="last_name"
value={formData.last_name}
onChange={handleChange}
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="username" className="form-label">
Имя пользователя
</label>
<input
type="text"
className="form-control"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Только латинские буквы, цифры и _"
required
/>
</div>
<div className="mb-3">
<label htmlFor="email" className="form-label">
Email
</label>
<input
type="email"
className="form-control"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">
Пароль
</label>
<input
type="password"
className="form-control"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
minLength={8}
required
/>
</div>
<div className="mb-4">
<label htmlFor="password2" className="form-label">
Подтвердите пароль
</label>
<input
type="password"
className="form-control"
id="password2"
name="password2"
value={formData.password2}
onChange={handleChange}
minLength={8}
required
/>
</div>
<button
type="submit"
className="btn btn-primary w-100 py-2 mb-3"
disabled={loading}
>
{loading ? (
<>
<span className="spinner-border spinner-border-sm me-2"></span>
Создание аккаунта...
</>
) : (
'Создать аккаунт'
)}
</button>
<div className="text-center">
<span className="text-muted">Уже есть аккаунт? </span>
<Link href="/auth/login" className="text-decoration-none">
Войти
</Link>
</div>
</form>
</div>
</div>
{/* Дополнительная информация */}
<div className="text-center mt-4">
<p className="text-muted small">
Создавая аккаунт, вы соглашаетесь с{' '}
<Link href="/terms" className="text-decoration-none">
Условиями использования
</Link>{' '}
и{' '}
<Link href="/privacy" className="text-decoration-none">
Политикой конфиденциальности
</Link>
</p>
</div>
</div>
</div>
</div>
</div>
)
}