auth context and auth api
This commit is contained in:
parent
efb6392fc7
commit
cc3b912fbb
35
src/App.tsx
35
src/App.tsx
@ -1,15 +1,32 @@
|
|||||||
import { RouterProvider } from 'react-router-dom'
|
import { RouterProvider } from "react-router-dom";
|
||||||
import './App.css'
|
import "./App.css";
|
||||||
import router from './router/router'
|
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() {
|
function App() {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<AuthProvider>
|
||||||
<RouterProvider router={router} />
|
<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