auth context and auth api

This commit is contained in:
DavidK004 2025-12-10 14:41:50 +01:00
parent efb6392fc7
commit cc3b912fbb
8 changed files with 185 additions and 10 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
VITE_API_URL=http://127.0.0.1:8000

View File

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

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

View 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>
);
};

View 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,
});

View 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");
},
});
};