auth context and auth api
This commit is contained in:
parent
efb6392fc7
commit
cc3b912fbb
37
src/App.tsx
37
src/App.tsx
@ -1,15 +1,32 @@
|
||||
import { RouterProvider } from 'react-router-dom'
|
||||
import './App.css'
|
||||
import router from './router/router'
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import "./App.css";
|
||||
import router from "./router/router";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { AuthProvider } from "./context/AuthContext";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function App() {
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<RouterProvider router={router}/>
|
||||
</>
|
||||
)
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={3000}
|
||||
hideProgressBar={false}
|
||||
newestOnTop={false}
|
||||
closeOnClick
|
||||
rtl={false}
|
||||
pauseOnFocusLoss
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="colored"
|
||||
/>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
||||
18
src/api/authApi.ts
Normal file
18
src/api/authApi.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {
|
||||
type LoginPayload,
|
||||
type LoginResponse,
|
||||
type MeResponse,
|
||||
} from "../components/shared/types/AuthTypes";
|
||||
import axiosInstance from "./axiosInstance";
|
||||
|
||||
export const loginRequest = async (data: LoginPayload) => {
|
||||
const res = await axiosInstance.post<LoginResponse>("/api/auth/login", data);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const fetchMe = async () => {
|
||||
const res = await axiosInstance.get<MeResponse>("/api/auth/me");
|
||||
return res.data.user;
|
||||
};
|
||||
|
||||
|
||||
27
src/api/axiosInstance.ts
Normal file
27
src/api/axiosInstance.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import axios from "axios";
|
||||
|
||||
const baseURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
baseURL,
|
||||
timeout: 30 * 1000,
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem("access_token");
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error),
|
||||
);
|
||||
axiosInstance.interceptors.response.use(undefined, async (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem("access_token");
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
export default axiosInstance;
|
||||
31
src/components/shared/types/AuthTypes.ts
Normal file
31
src/components/shared/types/AuthTypes.ts
Normal file
@ -0,0 +1,31 @@
|
||||
export interface school_index_object {
|
||||
id: number;
|
||||
school_index: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
index: school_index_object;
|
||||
school_index: number;
|
||||
email: string;
|
||||
role: string;
|
||||
is_superuser: boolean;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export interface LoginPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface MeResponse {
|
||||
user: User;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
52
src/context/AuthContext.tsx
Normal file
52
src/context/AuthContext.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-toastify";
|
||||
import { useCurrentUser } from "../hooks/auth/useCurrentUser";
|
||||
import { useLogin } from "../hooks/auth/useLogin";
|
||||
import type {
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
User,
|
||||
} from "../components/shared/types/AuthTypes";
|
||||
import { createContext, useContext } from "react";
|
||||
import type { OnlyChildrenProps } from "../components/shared/types/OnlyChildrenProps";
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
login: (data: LoginPayload) => Promise<LoginResponse>;
|
||||
logout: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) throw new Error("useAuth must be used within AuthProvider");
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AuthProvider = ({ children }: OnlyChildrenProps) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: user, isLoading } = useCurrentUser();
|
||||
const loginMutation = useLogin();
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem("access_token");
|
||||
queryClient.removeQueries({ queryKey: ["me"] });
|
||||
toast.info("Logged out");
|
||||
window.location.href = "/login";
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
user: user ?? null,
|
||||
login: loginMutation.mutateAsync,
|
||||
logout,
|
||||
isLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
11
src/hooks/auth/useCurrentUser.ts
Normal file
11
src/hooks/auth/useCurrentUser.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { fetchMe } from "../../api/authApi";
|
||||
|
||||
export const useCurrentUser = () =>
|
||||
useQuery({
|
||||
queryKey: ["me"],
|
||||
queryFn: fetchMe,
|
||||
enabled: !!localStorage.getItem("access_token"),
|
||||
retry: false,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
18
src/hooks/auth/useLogin.ts
Normal file
18
src/hooks/auth/useLogin.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { loginRequest } from "../../api/authApi";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const useLogin = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: loginRequest,
|
||||
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem("access_token", data.access_token);
|
||||
},
|
||||
|
||||
onError: (err: any) => {
|
||||
toast.error(err.response?.data?.message || "Login failed");
|
||||
},
|
||||
});
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user