import { Box, Container, Flex, Heading } from '@radix-ui/themes'; import { useMutation } from '@tanstack/react-query'; import { useLocation, useNavigate } from 'react-router'; import { Slide, toast, ToastContainer } from 'react-toastify'; import * as v from 'valibot'; import { useResponseErrorHandler } from '../../hooks/ResponseHelper'; import { useApi } from '../../providers/ApiProvider'; import { formHook } from '../../providers/FormProvider'; import type { Route } from './+types/login'; import { SearchParamKeys } from '../../lib/constants'; import { useEffect, useState } from 'react'; import { AxiosError } from 'axios'; const loginFormSchema = v.object({ username: v.pipe(v.string(), v.trim(), v.minLength(1, 'Username is required')), password: v.pipe(v.string(), v.minLength(1, 'Password is required')), }); // eslint-disable-next-line no-empty-pattern export function meta({}: Route.MetaArgs): Route.MetaDescriptors { return [{ title: 'Login | YANPM' }]; } // TODO: remember me export default function LoginRoute() { const navigate = useNavigate(); const location = useLocation(); const { tanstackApiClient } = useApi(); const { defaultResponseErrorHandler } = useResponseErrorHandler(); const [previousSearchParamMessage, setPreviousSearchParamMessage] = useState(''); const { mutateAsync: login, isPending } = useMutation({ ...tanstackApiClient.mutation('post', '/api/auth/login').mutationOptions, onSuccess: async () => { const searchParams = new URLSearchParams(location.search); const redirectTo = searchParams.get(SearchParamKeys.Redirect); if (redirectTo) { navigate(redirectTo); return; } navigate('/'); }, onError: (error) => { if (defaultResponseErrorHandler(error, { disableUnauthorizedHandling: true })) return; if (error instanceof AxiosError && error.status === 401) { toast.error('Invalid username or password.', { position: 'top-center', autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: false, progress: undefined, theme: 'colored', }); return; } console.error('Login failed:', error); }, }); const form = formHook.useAppForm({ defaultValues: { username: '', password: '', }, validators: { onBlur: loginFormSchema, onSubmit: loginFormSchema, }, onSubmit: async ({ value }) => { toast.dismiss(); return await login({ body: { password: value.password, username: value.username } }).catch(() => {}); }, }); useEffect(() => { const searchParams = new URLSearchParams(location.search); const message = searchParams.get(SearchParamKeys.Message); if (message && message !== previousSearchParamMessage) { setPreviousSearchParamMessage(message); toast.info(message, { position: 'top-center', autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: false, progress: undefined, theme: 'colored', toastId: 'login-route-info-message', }); } }, [location.search]); return ( <> Sign In
{ e.preventDefault(); form.handleSubmit(); }} > ( <> field.handleChange(e.target.value)} /> )} /> ( <> field.handleChange(e.target.value)} showPasswordToggle /> )} />
); }