feature/WNPT-533 #1

Merged
afra.khalaj merged 4 commits from feature/WNPT-533 into develop 2026-08-03 07:18:26 -04:00
4 changed files with 48 additions and 39 deletions
Showing only changes of commit ca4d60cc45 - Show all commits

View file

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

View file

@ -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<LoginSuccessPayload>) => {
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(),
},
});

View file

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

View file

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