updates on the login

This commit is contained in:
2026-07-01 16:19:49 +05:30
parent f50d5a0c56
commit 39e97c3041
6 changed files with 147 additions and 49 deletions

View File

@@ -10,6 +10,7 @@ import { useNavigate } from 'react-router';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { generateToken, initFirebaseNotificationListener } from 'firebase_notification/notification'; import { generateToken, initFirebaseNotificationListener } from 'firebase_notification/notification';
import InternetStatus from 'components/updateNetworkStatus'; import InternetStatus from 'components/updateNetworkStatus';
import useInactivityLogout from 'hooks/useInactivityLogout';
// auth-provider // auth-provider
// import { JWTProvider as AuthProvider } from 'contexts/JWTContext'; // import { JWTProvider as AuthProvider } from 'contexts/JWTContext';
@@ -25,6 +26,8 @@ const App = () => {
}, [navigate]); }, [navigate]);
const AppContent = () => { const AppContent = () => {
useInactivityLogout();
useEffect(() => { useEffect(() => {
generateToken(); generateToken();
initFirebaseNotificationListener(); initFirebaseNotificationListener();

View File

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

View File

@@ -7,14 +7,10 @@ import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
// assets // assets
import { EditOutlined, LogoutOutlined, CommentOutlined } from '@ant-design/icons'; import { EditOutlined, LogoutOutlined, CommentOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router'; 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 ||============================== // // ==============================|| HEADER PROFILE - PROFILE TAB ||============================== //
const ProfileTab = ({ handleLogout }) => { const ProfileTab = ({ handleLogout }) => {
const dispatch = useDispatch();
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const navigate = useNavigate(); const navigate = useNavigate();
const handleListItemClick = (event, index) => { const handleListItemClick = (event, index) => {
@@ -48,18 +44,7 @@ const ProfileTab = ({ handleLogout }) => {
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Billing" /> <ListItemText primary="Billing" />
</ListItemButton> */} </ListItemButton> */}
<ListItemButton <ListItemButton selected={selectedIndex === 3} onClick={handleLogout}>
selected={selectedIndex === 3}
// onClick={handleLogout}
onClick={() => {
handleLogout();
dispatch(clearFcmToken()); // ✅ dispatch the action
dispatch(logoutUser()); // ✅ dispatch logout user as initial state
}}
// onClick={()=>{
// navigate('/login')
// }}
>
<ListItemIcon> <ListItemIcon>
<LogoutOutlined /> <LogoutOutlined />
</ListItemIcon> </ListItemIcon>

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { useNavigate } from 'react-router'; import { useQueryClient } from '@tanstack/react-query';
// material-ui // material-ui
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
@@ -23,6 +23,7 @@ import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { clearFcmToken } from 'store/reducers/fcmSlice'; import { clearFcmToken } from 'store/reducers/fcmSlice';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { logoutUser } from 'store/reducers/loginUserSlice'; import { logoutUser } from 'store/reducers/loginUserSlice';
import { performSessionLogout } from 'utils/session';
// tab panel wrapper // tab panel wrapper
function TabPanel({ children, value, index, ...other }) { function TabPanel({ children, value, index, ...other }) {
@@ -50,30 +51,11 @@ function a11yProps(index) {
const Profile = () => { const Profile = () => {
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const queryClient = useQueryClient();
// const { logout, user } = useAuth(); const handleLogout = () => {
const handleLogout = async () => { performSessionLogout({ queryClient, dispatch, clearFcmToken, logoutUser });
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 anchorRef = useRef(null); const anchorRef = useRef(null);
@@ -173,16 +155,7 @@ const Profile = () => {
</Grid> </Grid>
<Grid item> <Grid item>
<Tooltip title="Logout"> <Tooltip title="Logout">
<IconButton <IconButton size="large" sx={{ color: 'text.primary' }} onClick={handleLogout}>
size="large"
sx={{ color: 'text.primary' }}
// onClick={handleLogout}>
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
}}
>
<LogoutOutlined /> <LogoutOutlined />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@@ -15,6 +15,7 @@ import { closeGlobalToast, GlobalToast } from 'components/nearle_components/Glob
import Visibility from '@mui/icons-material/Visibility'; import Visibility from '@mui/icons-material/Visibility';
import VisibilityOff from '@mui/icons-material/VisibilityOff'; import VisibilityOff from '@mui/icons-material/VisibilityOff';
import { setLoginUser } from 'store/reducers/loginUserSlice'; import { setLoginUser } from 'store/reducers/loginUserSlice';
import { markSessionStart } from 'utils/session';
const Login = () => { const Login = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -96,6 +97,7 @@ const Login = () => {
localStorage.setItem('applocationid', userinfo.applocationid); localStorage.setItem('applocationid', userinfo.applocationid);
localStorage.setItem('userid', userinfo.userid); localStorage.setItem('userid', userinfo.userid);
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken); localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
markSessionStart();
fetchAppLocations(userinfo.userid); fetchAppLocations(userinfo.userid);
navigate('/nearle/dispatch'); navigate('/nearle/dispatch');
} else { } else {
@@ -117,6 +119,7 @@ const Login = () => {
localStorage.setItem('applocationid', userinfo.applocationid); localStorage.setItem('applocationid', userinfo.applocationid);
localStorage.setItem('userid', userinfo.userid); localStorage.setItem('userid', userinfo.userid);
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken); localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
markSessionStart();
closeGlobalToast(); // to close the pin snackbar closeGlobalToast(); // to close the pin snackbar
navigate('/nearle/dispatch'); navigate('/nearle/dispatch');

38
src/utils/session.js Normal file
View File

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