feat: implement frontend login functionality with form handling and error management
This commit is contained in:
@@ -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
|
||||
|
||||
18
apps/frontend/app/providers/FormProvider.tsx
Normal file
18
apps/frontend/app/providers/FormProvider.tsx
Normal 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,
|
||||
});
|
||||
38
apps/frontend/app/providers/LayoutProvider.tsx
Normal file
38
apps/frontend/app/providers/LayoutProvider.tsx
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user