From 07bd221e7784c8c9d230c9640980e2cddd3883b8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 10 Apr 2026 18:52:27 +0530 Subject: [PATCH] feat: implement authentication state management with Zustand and add request interceptor for JWT injection --- frontend/bun.lock | 3 +++ frontend/package.json | 3 ++- frontend/src/api/apiClient.ts | 10 ++++++++++ frontend/src/store/useAuth.ts | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 frontend/src/store/useAuth.ts diff --git a/frontend/bun.lock b/frontend/bun.lock index 2c00351..8aaffc0 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -21,6 +21,7 @@ "react-router-dom": "^7.14.0", "tailwindcss": "^4.2.2", "zod": "^4.3.6", + "zustand": "^5.0.12", }, "devDependencies": { "@biomejs/biome": "^2.4.11", @@ -292,6 +293,8 @@ "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], diff --git a/frontend/package.json b/frontend/package.json index b68837a..f43f0bd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,8 @@ "react-hook-form": "^7.72.1", "react-router-dom": "^7.14.0", "tailwindcss": "^4.2.2", - "zod": "^4.3.6" + "zod": "^4.3.6", + "zustand": "^5.0.12" }, "devDependencies": { "@biomejs/biome": "^2.4.11", diff --git a/frontend/src/api/apiClient.ts b/frontend/src/api/apiClient.ts index 6fbc1c1..51b4c41 100644 --- a/frontend/src/api/apiClient.ts +++ b/frontend/src/api/apiClient.ts @@ -1,4 +1,5 @@ import axios, { type AxiosError } from "axios"; +import { useAuth } from "../store/useAuth"; const authApiClient = axios.create({ baseURL: `${import.meta.env.VITE_API_URL}/api/auth/`, @@ -30,4 +31,13 @@ authApiClient.interceptors.response.use( }, ); +// automatically attach access token to request +authApiClient.interceptors.request.use((config) => { + const token = useAuth.getState().accessToken; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + export default authApiClient; diff --git a/frontend/src/store/useAuth.ts b/frontend/src/store/useAuth.ts new file mode 100644 index 0000000..4233d41 --- /dev/null +++ b/frontend/src/store/useAuth.ts @@ -0,0 +1,36 @@ +import { create } from "zustand"; +import authApiClient from "../api/apiClient"; + +interface AuthState { + accessToken: string | null; + refreshToken: string | null; + isAuthenticated: boolean; + user: any | null; + login: (credentials: any) => Promise; + logout: () => Promise; +} + +export const useAuth = create((set) => ({ + accessToken: null, + refreshToken: null, + isAuthenticated: false, + user: null, + login: async (credentials: any) => { + const response = await authApiClient.post("login/", credentials); + set({ + accessToken: response.data.access, + refreshToken: response.data.refresh, + isAuthenticated: true, + user: response.data.user, + }); + }, + logout: async () => { + await authApiClient.post("logout/"); + set({ + accessToken: null, + refreshToken: null, + isAuthenticated: false, + user: null, + }); + }, +}));