| @@ -45,6 +45,8 @@ export const handleLogin = data => { | |||||
| localStorage.setItem(windowCount, '0') | localStorage.setItem(windowCount, '0') | ||||
| localStorage.setItem(predictProductionQty, '0') | localStorage.setItem(predictProductionQty, '0') | ||||
| localStorage.setItem(predictUsageCount, '0') | localStorage.setItem(predictUsageCount, '0') | ||||
| localStorage.removeItem('expiredAlertShown') | |||||
| expiredAlertShownInMemory = false | |||||
| } | } | ||||
| } | } | ||||
| @@ -100,8 +102,9 @@ export const handleLogoutFunction = () => { | |||||
| localStorage.removeItem('transactionid') | localStorage.removeItem('transactionid') | ||||
| localStorage.removeItem('searchCriteria') | localStorage.removeItem('searchCriteria') | ||||
| //localStorage.removeItem(config.storageUserRoleKeyName) | //localStorage.removeItem(config.storageUserRoleKeyName) | ||||
| localStorage.removeItem('expiredAlertShown'); | |||||
| expiredAlertShownInMemory = false; | |||||
| // Do not clear expiredAlertShown / expiredAlertShownInMemory here: logout runs right after | |||||
| // the token-expired alert, and parallel 401s would each show another popup if we reset. | |||||
| // Reset those only on successful login (handleLogin). | |||||
| localStorage.removeItem(refreshIntervalName) | localStorage.removeItem(refreshIntervalName) | ||||
| localStorage.removeItem(windowCount) | localStorage.removeItem(windowCount) | ||||
| localStorage.removeItem(predictProductionQty) | localStorage.removeItem(predictProductionQty) | ||||
| @@ -147,7 +150,8 @@ export const SetupAxiosInterceptors = () => { | |||||
| }, | }, | ||||
| async (error) => { | async (error) => { | ||||
| // const { config, response: { status } } = error | // const { config, response: { status } } = error | ||||
| if (error.response.status === 401 && error.config.url !== apiPath + REFRESH_TOKEN) { | |||||
| const status = error.response?.status | |||||
| if (status === 401 && error.config?.url !== apiPath + REFRESH_TOKEN) { | |||||
| // Make a request to refresh the access token | // Make a request to refresh the access token | ||||
| const refreshToken = localStorage.getItem('refreshToken'); | const refreshToken = localStorage.getItem('refreshToken'); | ||||
| if (isRefreshToken) { | if (isRefreshToken) { | ||||
| @@ -210,7 +214,7 @@ export const SetupAxiosInterceptors = () => { | |||||
| await window.location.reload(); | await window.location.reload(); | ||||
| } | } | ||||
| if (error.response.status === 500){ | |||||
| if (error.response?.status === 500){ | |||||
| //setIsUploading(false); | //setIsUploading(false); | ||||
| } | } | ||||
| // console.log(error) | // console.log(error) | ||||
| @@ -1,6 +1,6 @@ | |||||
| import PropTypes from "prop-types"; | import PropTypes from "prop-types"; | ||||
| import React, { useState, useContext, useEffect, useRef } from "react"; | import React, { useState, useContext, useEffect, useRef } from "react"; | ||||
| import { useDispatch } from "react-redux"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | |||||
| import { useNavigate, Link } from "react-router-dom"; | import { useNavigate, Link } from "react-router-dom"; | ||||
| import { SysContext } from "components/SysSettingProvider"; | import { SysContext } from "components/SysSettingProvider"; | ||||
| @@ -65,6 +65,9 @@ function Header(props) { | |||||
| const { sysSetting } = useContext(SysContext); | const { sysSetting } = useContext(SysContext); | ||||
| const { window } = props; | const { window } = props; | ||||
| /** Subscribe to auth changes (LOGIN/LOGOUT/cross-tab storage); Header reads localStorage and must re-render. */ | |||||
| useSelector((state) => state.authRevision); | |||||
| const [mobileOpen, setMobileOpen] = useState(false); | const [mobileOpen, setMobileOpen] = useState(false); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| @@ -42,7 +42,6 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; | |||||
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { handleLogin } from "auth/index"; | import { handleLogin } from "auth/index"; | ||||
| import useJwt from "auth/jwt/useJwt"; | import useJwt from "auth/jwt/useJwt"; | ||||
| import { handleLogoutFunction } from 'auth/index'; | |||||
| import { FormattedMessage, useIntl } from "react-intl"; | import { FormattedMessage, useIntl } from "react-intl"; | ||||
| import { SysContext } from "components/SysSettingProvider" | import { SysContext } from "components/SysSettingProvider" | ||||
| @@ -85,7 +84,6 @@ const AuthLoginCustom = () => { | |||||
| return; | return; | ||||
| } | } | ||||
| loginInFlightRef.current = true; | loginInFlightRef.current = true; | ||||
| dispatch(handleLogoutFunction()); | |||||
| setOnLogin(true) | setOnLogin(true) | ||||
| useJwt | useJwt | ||||
| .login({ username: values.username, password: values.password }) | .login({ username: values.username, password: values.password }) | ||||
| @@ -1,3 +1,6 @@ | |||||
| import { useEffect } from 'react' | |||||
| import { useDispatch, useSelector } from 'react-redux' | |||||
| // project import | // project import | ||||
| import LoginRoutes from './LoginRoutes' | import LoginRoutes from './LoginRoutes' | ||||
| // import MainRoutes from './MainRoutes' | // import MainRoutes from './MainRoutes' | ||||
| @@ -22,6 +25,19 @@ import AfterLoginRoutes from './AfterLoginRoutes'; | |||||
| // ==============================|| ROUTING RENDER ||============================== // | // ==============================|| ROUTING RENDER ||============================== // | ||||
| export default function ThemeRoutes() { | export default function ThemeRoutes() { | ||||
| const dispatch = useDispatch() | |||||
| /** Re-run route config when auth changes (same-tab LOGIN/LOGOUT or another tab touching userData). */ | |||||
| useSelector((state) => state.authRevision) | |||||
| useEffect(() => { | |||||
| const onStorage = (e) => { | |||||
| if (e.key === 'userData') { | |||||
| dispatch({ type: 'AUTH_STORAGE_SYNC' }) | |||||
| } | |||||
| } | |||||
| window.addEventListener('storage', onStorage) | |||||
| return () => window.removeEventListener('storage', onStorage) | |||||
| }, [dispatch]) | |||||
| if (isUserLoggedIn()) { | if (isUserLoggedIn()) { | ||||
| //auto logout if token not valid | //auto logout if token not valid | ||||
| @@ -0,0 +1,15 @@ | |||||
| /** | |||||
| * Bumps when auth-related storage or Redux auth actions change so components | |||||
| * that read localStorage (e.g. Header) re-render. LOGIN/LOGOUT were previously | |||||
| * dispatched without any reducer, so the UI could stay stale vs localStorage. | |||||
| */ | |||||
| export default function authRevision(state = 0, action) { | |||||
| switch (action.type) { | |||||
| case 'LOGIN': | |||||
| case 'LOGOUT': | |||||
| case 'AUTH_STORAGE_SYNC': | |||||
| return state + 1; | |||||
| default: | |||||
| return state; | |||||
| } | |||||
| } | |||||
| @@ -3,9 +3,10 @@ import { combineReducers } from 'redux'; | |||||
| // project import | // project import | ||||
| import menu from './menu'; | import menu from './menu'; | ||||
| import authRevision from './authRevision'; | |||||
| // ==============================|| COMBINE REDUCERS ||============================== // | // ==============================|| COMBINE REDUCERS ||============================== // | ||||
| const reducers = combineReducers({ menu }); | |||||
| const reducers = combineReducers({ menu, authRevision }); | |||||
| export default reducers; | export default reducers; | ||||