F: Use zod validation for login form
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run

This commit is contained in:
Ivan 2025-01-30 21:01:36 +03:00
parent 7dda79e701
commit c97b8997ce
3 changed files with 47 additions and 38 deletions

View File

@ -1,4 +1,5 @@
import { queryOptions } from '@tanstack/react-query';
import { z } from 'zod';
import { axiosGet, axiosPost } from '@/backend/apiTransport';
import { DELAYS } from '@/backend/configuration';
@ -8,10 +9,15 @@ import { information } from '@/utils/labels';
/**
* Represents login data, used to authenticate users.
*/
export interface IUserLoginDTO {
username: string;
password: string;
}
export const UserLoginSchema = z.object({
username: z.string(),
password: z.string()
});
/**
* Represents login data, used to authenticate users.
*/
export type IUserLoginDTO = z.infer<typeof UserLoginSchema>;
/**
* Represents data needed to update password for current user.

View File

@ -2,7 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { libraryApi } from '@/backend/library/api';
import { authApi } from './api';
import { authApi, IUserLoginDTO } from './api';
export const useLogin = () => {
const client = useQueryClient();
@ -13,11 +13,7 @@ export const useLogin = () => {
onSuccess: () => client.removeQueries({ queryKey: [libraryApi.baseKey] })
});
return {
login: (
username: string, //
password: string,
onSuccess?: () => void
) => mutation.mutate({ username, password }, { onSuccess }),
login: (data: IUserLoginDTO, onSuccess?: () => void) => mutation.mutate(data, { onSuccess }),
isPending: mutation.isPending,
error: mutation.error,
reset: mutation.reset

View File

@ -2,10 +2,11 @@
import axios from 'axios';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { urls } from '@/app/urls';
import { UserLoginSchema } from '@/backend/auth/api';
import { useAuthSuspense } from '@/backend/auth/useAuth';
import { useLogin } from '@/backend/auth/useLogin';
import ExpectedAnonymous from '@/components/ExpectedAnonymous';
@ -19,30 +20,40 @@ import { resources } from '@/utils/constants';
function LoginPage() {
const router = useConceptNavigation();
const query = useQueryStrings();
const initialName = query.get('username') ?? '';
const { isAnonymous } = useAuthSuspense();
const { login, isPending, error, reset } = useLogin();
const [username, setUsername] = useState(query.get('username') ?? '');
const [password, setPassword] = useState('');
useEffect(() => {
reset();
}, [username, password, reset]);
const { login, isPending, error: loginError, reset } = useLogin();
const [validationError, setValidationError] = useState<ErrorData | undefined>(undefined);
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!isPending) {
login(username, password, () => {
if (router.canBack()) {
router.back();
} else {
router.push(urls.library);
}
const formData = new FormData(event.currentTarget);
const result = UserLoginSchema.safeParse({
username: formData.get('username'),
password: formData.get('password')
});
if (!result.success) {
setValidationError(result.error);
} else {
login(result.data, () => {
if (router.canBack()) {
router.back();
} else {
router.push(urls.library);
}
});
}
}
}
function resetErrors() {
reset();
setValidationError(undefined);
}
if (!isAnonymous) {
return <ExpectedAnonymous />;
}
@ -51,37 +62,33 @@ function LoginPage() {
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
<TextInput
id='username'
label='Логин или email'
name='username'
autoComplete='username'
label='Логин или email'
autoFocus
required
allowEnter
spellCheck={false}
value={username}
onChange={event => setUsername(event.target.value)}
defaultValue={initialName}
onChange={resetErrors}
/>
<TextInput
id='password'
name='password'
type='password'
label='Пароль'
autoComplete='current-password'
label='Пароль'
required
allowEnter
value={password}
onChange={event => setPassword(event.target.value)}
onChange={resetErrors}
/>
<SubmitButton
text='Войти'
className='self-center w-[12rem] mt-3'
loading={isPending}
disabled={!username || !password}
/>
<SubmitButton text='Войти' className='self-center w-[12rem] mt-3' loading={isPending} />
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{error ? <ProcessError error={error} /> : null}
{!!loginError || !!validationError ? <ProcessError error={loginError ?? validationError} /> : null}
</form>
);
}