diff --git a/apps/frontend/app/hooks/ResponseHelper.tsx b/apps/frontend/app/hooks/ResponseHelper.tsx index 61aa295..517a3ac 100644 --- a/apps/frontend/app/hooks/ResponseHelper.tsx +++ b/apps/frontend/app/hooks/ResponseHelper.tsx @@ -2,6 +2,7 @@ import { Text } from '@radix-ui/themes'; import { AxiosError } from 'axios'; import { useLocation, useNavigate } from 'react-router'; import { toast } from 'react-toastify'; +import { SearchParamKeys } from '../lib/constants'; export enum ResponseErrorToastId { NetworkError = 'network-error', @@ -74,8 +75,8 @@ const defaultResponseErrorHandler = // store current path for redirect after login const currentPath = location.pathname + location.search; const searchParam = new URLSearchParams(); - searchParam.set('redirect', currentPath); - searchParam.set('message', 'Session expired, please log in again'); + searchParam.set(SearchParamKeys.Redirect, currentPath); + searchParam.set(SearchParamKeys.Message, 'Session expired, please log in again'); navigate(`/login?${searchParam.toString()}`); return true; } diff --git a/apps/frontend/app/lib/constants.ts b/apps/frontend/app/lib/constants.ts new file mode 100644 index 0000000..aa17dc8 --- /dev/null +++ b/apps/frontend/app/lib/constants.ts @@ -0,0 +1,4 @@ +export enum SearchParamKeys { + Redirect = 'redirect', + Message = 'message', +} diff --git a/apps/frontend/app/routes/auth/login.tsx b/apps/frontend/app/routes/auth/login.tsx index b2cd892..c46ad88 100644 --- a/apps/frontend/app/routes/auth/login.tsx +++ b/apps/frontend/app/routes/auth/login.tsx @@ -1,12 +1,14 @@ import { Box, Container, Flex, Heading } from '@radix-ui/themes'; import { useMutation } from '@tanstack/react-query'; -import { useNavigate } from 'react-router'; +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'; const loginFormSchema = v.object({ username: v.pipe(v.string(), v.trim(), v.minLength(1, 'Username is required')), @@ -18,14 +20,23 @@ 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) => { @@ -50,6 +61,25 @@ export default function LoginRoute() { }, }); + 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 ( <> @@ -110,7 +140,7 @@ export default function LoginRoute() { )} /> -
+