From 37f51ee88710868a77b4645294cf32862f55e7c4 Mon Sep 17 00:00:00 2001 From: l3wdfut4pwr Date: Tue, 31 Mar 2026 13:52:16 +0300 Subject: add jwt --- src/components/header/authdialog/LoginForm.tsx | 117 +++++++++++++++++++--- src/components/header/authdialog/RegisterForm.tsx | 1 + src/components/header/authdialog/register.ts | 76 +++++++++----- src/lib/contexts/Auth.context.tsx | 29 +++++- src/lib/contexts/Global.context.tsx | 2 + 5 files changed, 184 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/components/header/authdialog/LoginForm.tsx b/src/components/header/authdialog/LoginForm.tsx index 0a72f67..ceadbcd 100644 --- a/src/components/header/authdialog/LoginForm.tsx +++ b/src/components/header/authdialog/LoginForm.tsx @@ -1,23 +1,112 @@ +'use client'; +import { useState } from 'react'; import { Button } from '@/components/ui/button'; import GoogleIcon from '../../../../public/icons/google.svg'; import { InputField } from '@/components/ui/inputfield'; -import { Input } from '@/components/ui/input'; +import { useAuthContext } from '@/lib/contexts/Auth.context'; + export default function LoginForm() { + const [errors, setErrors] = useState({}); + const [loading, setLoading] = useState(false); + const { setUser } = useAuthContext(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setErrors({}); + + const formData = new FormData(e.currentTarget); + const emailOrUsername = + formData.get('emailOrUsername')?.toString() || ''; + const password = formData.get('password')?.toString() || ''; + + const newErrors: any = {}; + if (!emailOrUsername) + newErrors.emailOrUsername = 'Введите никнейм или email'; + if (!password) newErrors.password = 'Введите пароль'; + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + setLoading(false); + return; + } + + const body = new URLSearchParams(); + body.append('username', emailOrUsername); + body.append('password', password); + + try { + const res = await fetch('http://localhost:8000/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: body.toString(), + }); + + const data = await res.json(); + + if (!res.ok) { + setErrors({ general: 'Неверный логин или пароль' }); + } else if (data.access_token) { + localStorage.setItem('token', data.access_token); + + const meRes = await fetch('http://localhost:8000/api/me', { + headers: { Authorization: `Bearer ${data.access_token}` }, + }); + const meData = await meRes.json(); + setUser(meData); + } + } finally { + setLoading(false); + } + }; + return ( - <> -
- -
- - +
+ + +
+
+ + {errors.emailOrUsername && ( +

+ {errors.emailOrUsername} +

+ )} +
+ +
+ + {errors.password && ( +

+ {errors.password} +

+ )} + {!errors.password && errors.general && ( +

+ {errors.general} +

+ )}
- - + + +
); } diff --git a/src/components/header/authdialog/RegisterForm.tsx b/src/components/header/authdialog/RegisterForm.tsx index 3c2c164..c8ddb55 100644 --- a/src/components/header/authdialog/RegisterForm.tsx +++ b/src/components/header/authdialog/RegisterForm.tsx @@ -5,6 +5,7 @@ import GoogleIcon from '../../../../public/icons/google.svg'; import { InputField } from '@/components/ui/inputfield'; import { validate, registerUser } from './register'; import { useAuthContext } from '@/lib/contexts/Auth.context'; +import Image from 'next/image'; export default function RegisterForm() { const [errors, setErrors] = useState({}); diff --git a/src/components/header/authdialog/register.ts b/src/components/header/authdialog/register.ts index bcbc21b..ba8bceb 100644 --- a/src/components/header/authdialog/register.ts +++ b/src/components/header/authdialog/register.ts @@ -1,44 +1,37 @@ +// register.ts export const validate = ( username: string, email: string, password: string, passwordConfirm: string, ) => { - const newErrors: any = {}; + const newErrors: Record = {}; - if (!username) { - newErrors.username = 'Введите никнейм'; - } else if (username.length < 3) { - newErrors.username = 'Минимум 3 символа.'; - } else if (username.length > 32) { - newErrors.username = 'Максимум 32 символа'; - } + if (!username) newErrors.username = 'Введите никнейм'; + else if (username.length < 3) newErrors.username = 'Минимум 3 символа.'; + else if (username.length > 32) newErrors.username = 'Максимум 32 символа'; - if (!email) { - newErrors.email = 'Введите email.'; - } else { + if (!email) newErrors.email = 'Введите email.'; + else { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) newErrors.email = 'Некорректный email'; if (email.length > 254) newErrors.email = 'Слишком длинный email.'; } - if (!password) { - newErrors.password = 'Введите пароль.'; - } else if (password.length < 8) { + if (!password) newErrors.password = 'Введите пароль.'; + else if (password.length < 8) newErrors.password = 'Необходимо минимум 8 символов.'; - } else { + else { const hasLetter = /[A-Za-z]/.test(password); const hasNumber = /\d/.test(password); const hasSymbol = /[^\w\s]/.test(password); - if (!(hasLetter && hasNumber && hasSymbol)) { newErrors.password = 'Попробуйте сочетание букв, цифр и символов.'; } } - if (password !== passwordConfirm) { - newErrors.passwordConfirm = 'пароли не совпадают.'; - } + if (password !== passwordConfirm) + newErrors.passwordConfirm = 'Пароли не совпадают.'; return newErrors; }; @@ -54,16 +47,51 @@ export const registerUser = async ( headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, email, password }), }); - const data = await res.json(); if (!res.ok) { - const { field, message } = data.detail; - return { error: { [field]: message } }; + const { field, message } = data.detail || {}; + return { + data: null, + error: field + ? { [field]: message } + : { general: 'Ошибка регистрации' }, + }; } - return { data }; + const body = new URLSearchParams(); + body.append('username', username); + body.append('password', password); + + const loginRes = await fetch('http://localhost:8000/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + }); + + const loginData = await loginRes.json(); + + if (!loginRes.ok) { + return { + data: null, + error: { + general: + loginData.detail || 'Ошибка входа после регистрации', + }, + }; + } + + localStorage.setItem('token', loginData.access_token); + localStorage.setItem('refresh_token', loginData.refresh_token); + + const meRes = await fetch('http://localhost:8000/api/me', { + headers: { Authorization: `Bearer ${loginData.access_token}` }, + }); + + const meData = await meRes.json(); + + return { data: meData, error: null }; } catch (err: any) { - return { error: { general: err.message } }; + return { data: null, error: { general: err.message } }; } }; diff --git a/src/lib/contexts/Auth.context.tsx b/src/lib/contexts/Auth.context.tsx index d93ca92..29e2005 100644 --- a/src/lib/contexts/Auth.context.tsx +++ b/src/lib/contexts/Auth.context.tsx @@ -1,5 +1,6 @@ 'use client'; -import React, { createContext, use, useState, useContext } from 'react'; +import React, { createContext, useState, useContext, useEffect } from 'react'; + type User = { id: number; username: string; @@ -8,15 +9,36 @@ type User = { interface AuthContextType { user: User | null; setUser: (user: User | null) => void; + logout: () => void; } -const AuthContext = createContext(null); +const AuthContext = createContext(null); export const AuthContextProvider = ({ children }: React.PropsWithChildren) => { const [user, setUser] = useState(null); + useEffect(() => { + const token = localStorage.getItem('token'); + if (!token) return; + + fetch('http://localhost:8000/api/me', { + headers: { Authorization: `Bearer ${token}` }, + }) + .then((res) => { + if (!res.ok) throw new Error('Not authenticated'); + return res.json(); + }) + .then((userData) => setUser(userData)) + .catch(() => setUser(null)); + }, []); + + const logout = () => { + localStorage.removeItem('token'); + setUser(null); + }; + return ( - + {children} ); @@ -32,3 +54,4 @@ export const useAuthContext = () => { }; export const useUser = () => useAuthContext().user; +export const useLogout = () => useAuthContext().logout; diff --git a/src/lib/contexts/Global.context.tsx b/src/lib/contexts/Global.context.tsx index 3f8b61b..c903456 100644 --- a/src/lib/contexts/Global.context.tsx +++ b/src/lib/contexts/Global.context.tsx @@ -1,3 +1,5 @@ +import react from 'react'; +import { authcontextprovider } from './auth.context'; import React from 'react'; import { AuthContextProvider } from './Auth.context'; -- cgit v1.3-3-g829e