feat: implement frontend login functionality with form handling and error management

This commit is contained in:
GW_MC
2025-12-19 18:33:34 +08:00
parent 5060c84f28
commit 227256e0e0
17 changed files with 765 additions and 27 deletions

View File

@@ -1,9 +1,10 @@
import { createContext, use, useContext, type PropsWithChildren } from 'react';
import { createContext, use, type PropsWithChildren } from 'react';
import { createTanstackApi, createApi } from '../lib/api';
import axios from 'axios';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useNavigate } from 'react-router';
type ApiProviderProps = PropsWithChildren<{}>;
type ApiProviderProps = PropsWithChildren<object>;
type ApiContextType = {
apiClient: ReturnType<typeof createApi>;
tanstackApiClient: ReturnType<typeof createTanstackApi>;
@@ -31,11 +32,33 @@ const queryClient = new QueryClient();
*/
export const ApiProvider: React.FC<ApiProviderProps> = ({ children }) => {
const navigate = useNavigate();
const axiosInstance = axios.create({
withCredentials: true,
});
const internalAxiosInstance = axios.create({
withCredentials: true,
});
internalAxiosInstance.interceptors.response.use(
(response) => response,
(error) => {
// only handle 403 errors
if (!error.response) return Promise.reject(error);
if (error.response.status === 401) {
// redirect to login page
return navigate('/login', {});
}
return Promise.reject(error);
}
);
const apiClient = createApi(axiosInstance);
const tanstackApiClient = createTanstackApi(axiosInstance);
const tanstackApiClient = createTanstackApi(internalAxiosInstance);
return (
<QueryClientProvider client={queryClient}>
<ApiContext

View File

@@ -0,0 +1,18 @@
import { createFormHook, createFormHookContexts } from '@tanstack/react-form';
import { TextField, TextFieldErrorMessage } from '../components/Form/TextField';
import { ResetButton, SubmitButton } from '../components/Form/Button';
const { fieldContext, formContext } = createFormHookContexts();
export const formHook = createFormHook({
fieldComponents: {
TextField,
TextFieldErrorMessage,
},
formComponents: {
SubmitButton,
ResetButton,
},
fieldContext,
formContext,
});

View File

@@ -0,0 +1,38 @@
import { createContext, use, useState, type PropsWithChildren } from 'react';
import type { NavItem } from '../components/layout/types';
type LayoutProviderProps = PropsWithChildren<object>;
type LayoutContextType = {
activeTab: NavItem;
setActiveTab: (tab: NavItem) => void;
isMobileMenuOpen: boolean;
setIsMobileMenuOpen: (open: boolean) => void;
};
const LayoutContext = createContext<LayoutContextType | null>(null);
export const LayoutProvider: React.FC<LayoutProviderProps> = ({ children }) => {
const [activeTab, setActiveTab] = useState<NavItem>('Dashboard');
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
return (
<LayoutContext
value={{
activeTab,
setActiveTab,
isMobileMenuOpen,
setIsMobileMenuOpen,
}}
>
{children}
</LayoutContext>
);
};
export function useLayout() {
const context = use(LayoutContext);
if (!context) {
throw new Error('useLayout must be used within a LayoutProvider');
}
return context;
}