diff --git a/rsconcept/frontend/src/backend/auth/api.ts b/rsconcept/frontend/src/backend/auth/api.ts index f2881134..55ffe30b 100644 --- a/rsconcept/frontend/src/backend/auth/api.ts +++ b/rsconcept/frontend/src/backend/auth/api.ts @@ -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; /** * Represents data needed to update password for current user. diff --git a/rsconcept/frontend/src/backend/auth/useLogin.tsx b/rsconcept/frontend/src/backend/auth/useLogin.tsx index 5a0108ef..6837e626 100644 --- a/rsconcept/frontend/src/backend/auth/useLogin.tsx +++ b/rsconcept/frontend/src/backend/auth/useLogin.tsx @@ -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 diff --git a/rsconcept/frontend/src/pages/LoginPage.tsx b/rsconcept/frontend/src/pages/LoginPage.tsx index 519b93bd..cbb3d7e5 100644 --- a/rsconcept/frontend/src/pages/LoginPage.tsx +++ b/rsconcept/frontend/src/pages/LoginPage.tsx @@ -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(undefined); function handleSubmit(event: React.FormEvent) { 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 ; } @@ -51,37 +62,33 @@ function LoginPage() { Концепт Портал setUsername(event.target.value)} + defaultValue={initialName} + onChange={resetErrors} /> setPassword(event.target.value)} + onChange={resetErrors} /> - +
- {error ? : null} + {!!loginError || !!validationError ? : null} ); }