diff --git a/src/App.js b/src/App.js
index 62f9497..ba4e4e8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -10,6 +10,7 @@ import { useNavigate } from 'react-router';
import { useEffect } from 'react';
import { generateToken, initFirebaseNotificationListener } from 'firebase_notification/notification';
import InternetStatus from 'components/updateNetworkStatus';
+import useInactivityLogout from 'hooks/useInactivityLogout';
// auth-provider
// import { JWTProvider as AuthProvider } from 'contexts/JWTContext';
@@ -25,6 +26,8 @@ const App = () => {
}, [navigate]);
const AppContent = () => {
+ useInactivityLogout();
+
useEffect(() => {
generateToken();
initFirebaseNotificationListener();
diff --git a/src/hooks/useInactivityLogout.js b/src/hooks/useInactivityLogout.js
new file mode 100644
index 0000000..69b4a24
--- /dev/null
+++ b/src/hooks/useInactivityLogout.js
@@ -0,0 +1,96 @@
+import { useEffect, useRef } from 'react';
+import { useQueryClient } from '@tanstack/react-query';
+import { useDispatch } from 'react-redux';
+import { clearFcmToken } from 'store/reducers/fcmSlice';
+import { logoutUser } from 'store/reducers/loginUserSlice';
+import {
+ ABSOLUTE_SESSION_TIMEOUT_MS,
+ ACTIVITY_STORAGE_KEY,
+ AUTH_PRESENCE_KEY,
+ INACTIVITY_TIMEOUT_MS,
+ SESSION_START_STORAGE_KEY,
+ isSessionActive,
+ markActivity,
+ markSessionStart,
+ performSessionLogout
+} from 'utils/session';
+
+const ACTIVITY_EVENTS = ['mousemove', 'mousedown', 'keydown', 'scroll', 'touchstart', 'wheel'];
+const ACTIVITY_WRITE_THROTTLE_MS = 5000;
+const IDLE_CHECK_INTERVAL_MS = 15000;
+
+// Two independent timers, both enforced from localStorage so every tab on the
+// origin agrees:
+// - INACTIVITY_TIMEOUT_MS: logs out after 15 minutes with no interaction, so
+// a laptop left unlocked doesn't leave the console open indefinitely.
+// - ABSOLUTE_SESSION_TIMEOUT_MS: a hard 30-minute cap since login, even if
+// the user has been continuously active, so localStorage (auth keys, FCM
+// token, cached zone list) never lingers on disk longer than that.
+// A logout in one tab (auth key cleared) is picked up by the others via the
+// 'storage' event.
+const useInactivityLogout = () => {
+ const queryClient = useQueryClient();
+ const dispatch = useDispatch();
+ const lastWriteRef = useRef(0);
+
+ useEffect(() => {
+ const doLogout = () => performSessionLogout({ queryClient, dispatch, clearFcmToken, logoutUser });
+
+ const handleActivity = () => {
+ const now = Date.now();
+ if (now - lastWriteRef.current < ACTIVITY_WRITE_THROTTLE_MS) return;
+ lastWriteRef.current = now;
+ markActivity();
+ };
+
+ const handleStorage = (event) => {
+ if (event.key === AUTH_PRESENCE_KEY && !event.newValue) {
+ doLogout();
+ }
+ };
+
+ // Guards against the browser restoring a cached (bfcache) copy of a
+ // protected page via the back/forward button after logout happened.
+ const handlePageShow = (event) => {
+ if (event.persisted && !isSessionActive()) {
+ window.location.replace('/login');
+ }
+ };
+
+ if (isSessionActive()) {
+ if (!localStorage.getItem(ACTIVITY_STORAGE_KEY)) markActivity();
+ // Sessions that were already open before this feature shipped won't have
+ // a start time yet — give them a fresh 30-minute window instead of
+ // treating them as already expired.
+ if (!localStorage.getItem(SESSION_START_STORAGE_KEY)) markSessionStart();
+ }
+
+ ACTIVITY_EVENTS.forEach((eventName) => window.addEventListener(eventName, handleActivity, { passive: true }));
+ window.addEventListener('storage', handleStorage);
+ window.addEventListener('pageshow', handlePageShow);
+
+ const intervalId = setInterval(() => {
+ if (!isSessionActive()) return;
+
+ const lastActivity = Number(localStorage.getItem(ACTIVITY_STORAGE_KEY)) || Date.now();
+ if (Date.now() - lastActivity >= INACTIVITY_TIMEOUT_MS) {
+ doLogout();
+ return;
+ }
+
+ const sessionStart = Number(localStorage.getItem(SESSION_START_STORAGE_KEY)) || Date.now();
+ if (Date.now() - sessionStart >= ABSOLUTE_SESSION_TIMEOUT_MS) {
+ doLogout();
+ }
+ }, IDLE_CHECK_INTERVAL_MS);
+
+ return () => {
+ ACTIVITY_EVENTS.forEach((eventName) => window.removeEventListener(eventName, handleActivity));
+ window.removeEventListener('storage', handleStorage);
+ window.removeEventListener('pageshow', handlePageShow);
+ clearInterval(intervalId);
+ };
+ }, [queryClient, dispatch]);
+};
+
+export default useInactivityLogout;
diff --git a/src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js b/src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js
index b95cf02..92d8b1d 100644
--- a/src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js
+++ b/src/layout/MainLayout/Header/HeaderContent/Profile/ProfileTab.js
@@ -7,14 +7,10 @@ import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
// assets
import { EditOutlined, LogoutOutlined, CommentOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router';
-import { useDispatch } from 'react-redux';
-import { clearFcmToken } from 'store/reducers/fcmSlice';
-import { logoutUser } from 'store/reducers/loginUserSlice';
// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== //
const ProfileTab = ({ handleLogout }) => {
- const dispatch = useDispatch();
const [selectedIndex, setSelectedIndex] = useState(0);
const navigate = useNavigate();
const handleListItemClick = (event, index) => {
@@ -48,18 +44,7 @@ const ProfileTab = ({ handleLogout }) => {
*/}
- {
- handleLogout();
- dispatch(clearFcmToken()); // ✅ dispatch the action
- dispatch(logoutUser()); // ✅ dispatch logout user as initial state
- }}
- // onClick={()=>{
- // navigate('/login')
- // }}
- >
+
diff --git a/src/layout/MainLayout/Header/HeaderContent/Profile/index.js b/src/layout/MainLayout/Header/HeaderContent/Profile/index.js
index 532d4d8..f02bec4 100644
--- a/src/layout/MainLayout/Header/HeaderContent/Profile/index.js
+++ b/src/layout/MainLayout/Header/HeaderContent/Profile/index.js
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import { useRef, useState } from 'react';
-import { useNavigate } from 'react-router';
+import { useQueryClient } from '@tanstack/react-query';
// material-ui
import { useTheme } from '@mui/material/styles';
@@ -23,6 +23,7 @@ import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { clearFcmToken } from 'store/reducers/fcmSlice';
import { useDispatch } from 'react-redux';
import { logoutUser } from 'store/reducers/loginUserSlice';
+import { performSessionLogout } from 'utils/session';
// tab panel wrapper
function TabPanel({ children, value, index, ...other }) {
@@ -50,30 +51,11 @@ function a11yProps(index) {
const Profile = () => {
const theme = useTheme();
- const navigate = useNavigate();
const dispatch = useDispatch();
+ const queryClient = useQueryClient();
- // const { logout, user } = useAuth();
- const handleLogout = async () => {
- try {
- // await logout();
-
- // navigate(`/login`, {
- // state: {
- // from: ''
- // }
- // });
- localStorage.removeItem('firstname');
- localStorage.removeItem('appuserid');
- localStorage.removeItem('authname');
- localStorage.removeItem('roleid');
- localStorage.removeItem('tenantid');
- localStorage.clear();
-
- navigate('/login');
- } catch (err) {
- console.error(err);
- }
+ const handleLogout = () => {
+ performSessionLogout({ queryClient, dispatch, clearFcmToken, logoutUser });
};
const anchorRef = useRef(null);
@@ -173,16 +155,7 @@ const Profile = () => {
-
- onClick={() => {
- handleLogout();
- dispatch(clearFcmToken()); // ✅ dispatch the action dispatch(logoutUser()); // ✅ dispatch logout user as initial state dispatch(logoutUser()); // ✅ dispatch logout user as initial state
- dispatch(logoutUser()); // ✅ dispatch logout user as initial state
- }}
- >
+
diff --git a/src/pages/nearle/login.js b/src/pages/nearle/login.js
index 9cdbc7a..eea09dc 100644
--- a/src/pages/nearle/login.js
+++ b/src/pages/nearle/login.js
@@ -15,6 +15,7 @@ import { closeGlobalToast, GlobalToast } from 'components/nearle_components/Glob
import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { setLoginUser } from 'store/reducers/loginUserSlice';
+import { markSessionStart } from 'utils/session';
const Login = () => {
const dispatch = useDispatch();
@@ -96,6 +97,7 @@ const Login = () => {
localStorage.setItem('applocationid', userinfo.applocationid);
localStorage.setItem('userid', userinfo.userid);
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
+ markSessionStart();
fetchAppLocations(userinfo.userid);
navigate('/nearle/dispatch');
} else {
@@ -117,6 +119,7 @@ const Login = () => {
localStorage.setItem('applocationid', userinfo.applocationid);
localStorage.setItem('userid', userinfo.userid);
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
+ markSessionStart();
closeGlobalToast(); // to close the pin snackbar
navigate('/nearle/dispatch');
diff --git a/src/utils/session.js b/src/utils/session.js
new file mode 100644
index 0000000..878a1b7
--- /dev/null
+++ b/src/utils/session.js
@@ -0,0 +1,38 @@
+// Shared session/logout contract used by both the manual "Logout" buttons
+// and the automatic inactivity logout (see hooks/useInactivityLogout.js).
+// Keeping this in one place means every logout path clears the same things
+// the same way, instead of each caller hand-rolling its own localStorage cleanup.
+
+export const ACTIVITY_STORAGE_KEY = 'lastActivityTime';
+export const SESSION_START_STORAGE_KEY = 'sessionStartTime';
+export const AUTH_PRESENCE_KEY = 'authname';
+export const INACTIVITY_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes, resets on user activity
+export const ABSOLUTE_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes, hard cap since login regardless of activity
+
+export const markActivity = () => {
+ localStorage.setItem(ACTIVITY_STORAGE_KEY, String(Date.now()));
+};
+
+// Called once at login to start the absolute-lifetime clock for the session.
+export const markSessionStart = () => {
+ localStorage.setItem(SESSION_START_STORAGE_KEY, String(Date.now()));
+};
+
+export const isSessionActive = () => Boolean(localStorage.getItem(AUTH_PRESENCE_KEY));
+
+// Wipes local auth state + the in-memory query cache, then hard-navigates to
+// /login. A real navigation (not react-router) is deliberate: it throws away
+// the entire JS heap (Redux store, component state, any variable holding
+// fetched data) so nothing sensitive survives in memory after logout, and it
+// prevents the back button from resurrecting a stale authenticated page from
+// the render tree.
+export const performSessionLogout = ({ queryClient, dispatch, clearFcmToken, logoutUser } = {}) => {
+ try {
+ queryClient?.clear();
+ if (dispatch && clearFcmToken) dispatch(clearFcmToken());
+ if (dispatch && logoutUser) dispatch(logoutUser());
+ } finally {
+ localStorage.clear();
+ window.location.replace('/login');
+ }
+};