feature/frontend-login #10

Merged
GW_MC merged 24 commits from feature/frontend-login into master 2025-12-20 19:01:07 +08:00
3 changed files with 39 additions and 4 deletions
Showing only changes of commit 1d1a469fe0 - Show all commits

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
export enum SearchParamKeys {
Redirect = 'redirect',
Message = 'message',
}

View File

@@ -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<string>('');
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 (
<>
<Flex align="center" justify="center" style={{ minHeight: 'calc(100vh - 64px)' }}>
@@ -110,7 +140,7 @@ export default function LoginRoute() {
)}
/>
<div style={{ marginTop: 18, display: 'flex', gap: 8 }}>
<div style={{ marginTop: 18, display: 'flex', gap: 8, justifySelf: 'center' }}>
<form.SubmitButton
loading={isPending}
label={{