From ca4d60cc45ce4318e8b0bee7748345eb4407053e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afra=20=E2=80=8C?= Date: Mon, 3 Aug 2026 14:41:19 +0330 Subject: [PATCH] fix: auth route issue fixed --- src/App.tsx | 8 +---- src/features/auth/authSlice.ts | 64 ++++++++++++++++++---------------- src/services/authApi.ts | 2 ++ src/utils/authToken.ts | 13 ++++++- 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 761c200..5ebbe36 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,13 @@ import { useEffect } from 'react'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; -import { useAppDispatch, useAppSelector } from '@/utils/hooks'; -import { hydrateAuth } from '@/features/auth/authSlice'; +import { useAppSelector } from '@/utils/hooks'; import AdminLayout from '@/components/layout/AdminLayout'; function App() { - const dispatch = useAppDispatch(); const location = useLocation(); const navigate = useNavigate(); const isAuthenticated = useAppSelector((state) => state.auth.isAuthenticated); - useEffect(() => { - dispatch(hydrateAuth()); - }, [dispatch]); - useEffect(() => { if (!isAuthenticated && location.pathname !== '/login' && location.pathname !== '/verify-otp') { navigate('/login', { replace: true }); diff --git a/src/features/auth/authSlice.ts b/src/features/auth/authSlice.ts index 1376559..e957a16 100644 --- a/src/features/auth/authSlice.ts +++ b/src/features/auth/authSlice.ts @@ -1,17 +1,42 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; import type { AuthState, LoginSuccessPayload } from '@/features/auth/authTypes'; import { STORAGE_KEYS } from '@/utils/storage'; -import { getAccessToken, storeAuthTokens } from '@/utils/authToken'; +import { clearAuthTokens, getAccessToken, isAccessTokenExpired, storeAuthTokens } from '@/utils/authToken'; -const initialState: AuthState = { - user: null, - accessToken: null, - isAuthenticated: false, +const clearPersistedAuthState = () => { + localStorage.removeItem(STORAGE_KEYS.accessToken); + localStorage.removeItem(STORAGE_KEYS.user); + localStorage.removeItem(STORAGE_KEYS.authState); + clearAuthTokens(); +}; + +const loadPersistedAuthState = (): AuthState => { + const loggedOutState: AuthState = { user: null, accessToken: null, isAuthenticated: false }; + const storedAuth = localStorage.getItem(STORAGE_KEYS.authState); + const storedAccessToken = getAccessToken(); + + if (!storedAuth) { + return loggedOutState; + } + + try { + const parsed = JSON.parse(storedAuth) as AuthState; + const accessToken = parsed.accessToken || storedAccessToken; + + if (accessToken && parsed.user && !isAccessTokenExpired()) { + return { user: parsed.user, accessToken, isAuthenticated: true }; + } + } catch { + // fall through to the logged-out default below + } + + clearPersistedAuthState(); + return loggedOutState; }; const authSlice = createSlice({ name: 'auth', - initialState, + initialState: loadPersistedAuthState(), reducers: { loginSuccess: (state, action: PayloadAction) => { state.user = action.payload.user; @@ -38,32 +63,9 @@ const authSlice = createSlice({ state.accessToken = null; state.isAuthenticated = false; - localStorage.removeItem(STORAGE_KEYS.accessToken); - localStorage.removeItem(STORAGE_KEYS.user); - localStorage.removeItem(STORAGE_KEYS.authState); - }, - hydrateAuth: (state) => { - const storedAuth = localStorage.getItem(STORAGE_KEYS.authState); - const storedAccessToken = getAccessToken(); - - if (!storedAuth) { - return; - } - - try { - const parsed = JSON.parse(storedAuth) as AuthState; - - if ((parsed.accessToken || storedAccessToken) && parsed.user) { - state.user = parsed.user; - state.accessToken = parsed.accessToken || storedAccessToken; - state.isAuthenticated = true; - } - } catch { - state.user = null; - state.accessToken = null; - state.isAuthenticated = false; - } + clearPersistedAuthState(); }, + hydrateAuth: () => loadPersistedAuthState(), }, }); diff --git a/src/services/authApi.ts b/src/services/authApi.ts index 418cfc5..015bd39 100644 --- a/src/services/authApi.ts +++ b/src/services/authApi.ts @@ -36,6 +36,7 @@ export const authApi = { storeAuthTokens({ access_token: response.data.access_token, refresh_token: response.data.refresh_token, + expires_in: response.data.expires_in, }); return response.data; @@ -57,6 +58,7 @@ export const authApi = { storeAuthTokens({ access_token: response.data.access_token, refresh_token: response.data.refresh_token, + expires_in: response.data.expires_in, }); return response.data; diff --git a/src/utils/authToken.ts b/src/utils/authToken.ts index 203abb8..55841c8 100644 --- a/src/utils/authToken.ts +++ b/src/utils/authToken.ts @@ -1,5 +1,6 @@ const ACCESS_TOKEN_KEY = 'access_token'; const REFRESH_TOKEN_KEY = 'refresh_token'; +const EXPIRES_AT_KEY = 'access_token_expires_at'; export const getAccessToken = (): string | null => { return localStorage.getItem(ACCESS_TOKEN_KEY); @@ -9,12 +10,22 @@ export const getRefreshToken = (): string | null => { return localStorage.getItem(REFRESH_TOKEN_KEY); }; -export const storeAuthTokens = (tokens: { access_token: string; refresh_token: string }) => { +export const storeAuthTokens = (tokens: { access_token: string; refresh_token: string; expires_in?: number }) => { localStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token); localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token); + + if (tokens.expires_in) { + localStorage.setItem(EXPIRES_AT_KEY, String(Date.now() + tokens.expires_in * 1000)); + } +}; + +export const isAccessTokenExpired = (): boolean => { + const expiresAt = localStorage.getItem(EXPIRES_AT_KEY); + return expiresAt !== null && Date.now() >= Number(expiresAt); }; export const clearAuthTokens = () => { localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); + localStorage.removeItem(EXPIRES_AT_KEY); };