From 1b98ee1b51e0bb2730f47d67c9e5f5f86b940eab Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Sat, 30 Aug 2025 10:51:51 +0300 Subject: [PATCH] Extract auth header into a separate component --- frontend/src/ui/components/Auth.tsx | 28 +++++++ frontend/src/ui/screen/LoginScreen.tsx | 19 ++--- frontend/src/ui/screen/RegisterScreen.tsx | 94 +++++++++++++++++++---- 3 files changed, 116 insertions(+), 25 deletions(-) diff --git a/frontend/src/ui/components/Auth.tsx b/frontend/src/ui/components/Auth.tsx index 2a52519..65da3a3 100644 --- a/frontend/src/ui/components/Auth.tsx +++ b/frontend/src/ui/components/Auth.tsx @@ -8,4 +8,32 @@ export function AuthContainer({ children }: { children?: React.ReactNode }) { ) +} + +export type IconType = "filled" | "outlined"; + +export interface AuthHeaderIcon { + name: string; + type: IconType +} + +export interface AuthHeaderProps { + title: string; + icon: string | AuthHeaderIcon; + subtitle: string; +} + +export function AuthHeader({ title, icon, subtitle }: AuthHeaderProps) { + const iconType = typeof icon == "string" ? "filled" : icon.type; + const iconName = typeof icon == "string" ? icon : icon.name; + + return ( +
+

+ {iconName} + {title} +

+

{subtitle}

+
+ ) } \ No newline at end of file diff --git a/frontend/src/ui/screen/LoginScreen.tsx b/frontend/src/ui/screen/LoginScreen.tsx index 3d5c11f..cf95ec2 100644 --- a/frontend/src/ui/screen/LoginScreen.tsx +++ b/frontend/src/ui/screen/LoginScreen.tsx @@ -1,13 +1,14 @@ import { useImmer } from "use-immer"; import { showChat, showRegister } from "../../navigation"; import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts"; -import { AuthContainer } from "../components/Auth"; +import { AuthContainer, AuthHeader } from "../components/Auth"; import type { ErrorResponse, LoginRequest, LoginResponse } from "../../core/types"; import { setUser } from "../../auth/api"; import { ensureKeysOnLogin } from "../../auth/crypto"; import { API_BASE_URL } from "../../core/config"; import { initializeProfile } from "../../userPanel/profile/profile"; import { useRef } from "react"; +import type { TextField } from "mdui/components/text-field"; export default function LoginScreen() { const [alerts, updateAlerts] = useImmer([]); @@ -16,18 +17,12 @@ export default function LoginScreen() { updateAlerts((alerts) => alerts.push({type: type, message: message})); } - const usernameElement = useRef(null); - const passwordElement = useRef(null); + const usernameElement = useRef(null); + const passwordElement = useRef(null); return ( -
-

- login - Добро пожаловать! -

-

Войдите в свой аккаунт

-
+
@@ -84,7 +79,7 @@ export default function LoginScreen() { icon="person--filled" autocomplete="username" required - ref={usernameElement}> + ref={usernameElement as React.RefObject}> + ref={passwordElement as React.RefObject}> Войти diff --git a/frontend/src/ui/screen/RegisterScreen.tsx b/frontend/src/ui/screen/RegisterScreen.tsx index 8940ecf..ecc8f77 100644 --- a/frontend/src/ui/screen/RegisterScreen.tsx +++ b/frontend/src/ui/screen/RegisterScreen.tsx @@ -1,20 +1,85 @@ +import { useImmer } from "use-immer"; import { showLogin } from "../../navigation"; -import { AuthContainer } from "../components/Auth"; +import { AuthContainer, AuthHeader } from "../components/Auth"; +import { AlertsContainer, type Alert, type AlertType } from "../components/Alerts"; +import { useRef } from "react"; +import { TextField } from "mdui/components/text-field"; +import type { ErrorResponse, RegisterRequest } from "../../core/types"; +import { API_BASE_URL } from "../../core/config"; +import { delay } from "../../utils/utils"; export default function RegisterScreen() { + const [alerts, updateAlerts] = useImmer([]); + + function showAlert(type: AlertType, message: string) { + updateAlerts((alerts) => alerts.push({type: type, message: message})); + } + + const usernameElement = useRef(null); + const passwordElement = useRef(null); + const confirmPasswordElement = useRef(null); + return ( -
-

- person_add - Регистрация -

-

Создайте новый аккаунт

-
+
-
+ -
+ { + e.preventDefault(); + + const username = usernameElement.current!.value.trim(); + const password = passwordElement.current!.value.trim(); + const confirmPassword = confirmPasswordElement.current!.value.trim(); + + if (!username || !password || !confirmPassword) { + showAlert("danger", "Пожалуйста, заполните все поля"); + return; + } + + if (password !== confirmPassword) { + showAlert("danger", "Пароли не совпадают"); + return; + } + + if (username.length < 3 || username.length > 20) { + showAlert("danger", "Имя пользователя должно быть от 3 до 20 символов"); + return; + } + + if (password.length < 5 || password.length > 50) { + showAlert("danger", "Пароль должен быть от 5 до 50 символов"); + return; + } + + try { + const request: RegisterRequest = { + username: username, + password: password, + confirm_password: confirmPassword + } + + const response = await fetch(`${API_BASE_URL}/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request) + }); + + if (response.ok) { + // Registration successful + showAlert("success", "Регистрация прошла успешно! Теперь вы можете войти."); + await delay(2000); + showLogin(); + } else { + const data: ErrorResponse = await response.json(); + showAlert("danger", data.message || "Ошибка при регистрации"); + } + } catch (error) { + showAlert("danger", "Ошибка соединения с сервером"); + } + }}> + required + ref={usernameElement as React.RefObject}> + required + ref={passwordElement as React.RefObject}> + required + ref={confirmPasswordElement as React.RefObject}> Зарегистрироваться