From afc763c522ab33659de00c55f797dadf5a31e3d7 Mon Sep 17 00:00:00 2001 From: Kirill Date: Fri, 22 May 2026 12:11:36 +0500 Subject: [PATCH] feat(client): auth page with 3 tabs (password/code/oauth) --- client/src/pages/auth/ui/AuthPage.tsx | 173 ++++++++++++++++++++------ 1 file changed, 135 insertions(+), 38 deletions(-) diff --git a/client/src/pages/auth/ui/AuthPage.tsx b/client/src/pages/auth/ui/AuthPage.tsx index ea35191..3f7e3b4 100644 --- a/client/src/pages/auth/ui/AuthPage.tsx +++ b/client/src/pages/auth/ui/AuthPage.tsx @@ -2,8 +2,9 @@ import { useEffect, useState } from 'react' import Alert from '@mui/material/Alert' import Box from '@mui/material/Box' import Button from '@mui/material/Button' -import Divider from '@mui/material/Divider' import Stack from '@mui/material/Stack' +import Tab from '@mui/material/Tab' +import Tabs from '@mui/material/Tabs' import TextField from '@mui/material/TextField' import Typography from '@mui/material/Typography' import { useMutation } from '@tanstack/react-query' @@ -21,7 +22,6 @@ type AuthResponse = { email: string displayName?: string | null avatar?: string | null - avatarType?: string | null avatarStyle?: string | null } } @@ -38,19 +38,28 @@ function getApiErrorMessage(err: unknown): string | null { export function AuthPage() { const [message, setMessage] = useState(null) const [oauthError, setOauthError] = useState(null) + const [tab, setTab] = useState(0) + const [isRegister, setIsRegister] = useState(false) const [searchParams, setSearchParams] = useSearchParams() const navigate = useNavigate() const user = useUnit($user) + const { register, watch } = useForm<{ email: string + password: string + passwordConfirm: string + displayName: string code: string }>({ - defaultValues: { email: '', code: '' }, + defaultValues: { email: '', password: '', passwordConfirm: '', displayName: '', code: '' }, mode: 'onChange', }) const email = watch('email') + const password = watch('password') + const passwordConfirm = watch('passwordConfirm') const code = watch('code') + useEffect(() => { if (user) navigate('/', { replace: true }) }, [navigate, user]) @@ -62,6 +71,26 @@ export function AuthPage() { setSearchParams({}, { replace: true }) }, [searchParams, setSearchParams]) + const loginMutation = useMutation({ + mutationFn: async () => { + const { data } = await apiClient.post('auth/login', { email, password }) + tokenSet(data.token) + navigate('/', { replace: true }) + }, + }) + + const registerMutation = useMutation({ + mutationFn: async () => { + const { data } = await apiClient.post('auth/register', { + email, + password, + displayName: watch('displayName') || undefined, + }) + tokenSet(data.token) + navigate('/', { replace: true }) + }, + }) + const requestCode = useMutation({ mutationFn: async () => { await apiClient.post('auth/request-code', { email }) @@ -73,12 +102,18 @@ export function AuthPage() { mutationFn: async () => { const { data } = await apiClient.post('auth/verify-code', { email, code }) tokenSet(data.token) - setMessage(`Вход выполнен: ${data.user.email}`) navigate('/', { replace: true }) }, }) - const errMsg = getApiErrorMessage(requestCode.error || verifyCode.error) + const errMsg = + getApiErrorMessage(loginMutation.error) || + getApiErrorMessage(registerMutation.error) || + getApiErrorMessage(requestCode.error) || + getApiErrorMessage(verifyCode.error) + + const passwordError = + isRegister && passwordConfirm && password !== passwordConfirm ? 'Пароли не совпадают' : null return ( @@ -86,45 +121,107 @@ export function AuthPage() { Вход / регистрация - {message && ( - - {message} - - )} + {message && {message}} {oauthError && ( - setOauthError(null)}> - {oauthError} - - )} - {errMsg && ( - - {errMsg} - + setOauthError(null)}>{oauthError} )} + {errMsg && {errMsg}} - - Email + код - - - - - + setTab(v)} sx={{ mb: 3 }}> + + + + + + {tab === 0 && ( + + + + + + + + + {isRegister && ( + + )} + + + + {isRegister && ( + + )} + + {isRegister ? ( + + ) : ( + + )} - + )} - - или + {tab === 1 && ( + + + + + + + + + )} - - + {tab === 2 && ( + + + + )} ) }