Skip to content

Commit 81beb37

Browse files
Line chart merge fix
2 parents 1b97f31 + a071ffb commit 81beb37

28 files changed

Lines changed: 3675 additions & 3463 deletions

src/actions/__tests__/authActions.js.test.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
setCurrentUser, // Import setCurrentUser action
1414
setHeaderData, // Import setHeaderData action
1515
} from '../authActions'; // Import actions from authActions
16-
import { SET_CURRENT_USER, SET_HEADER_DATA } from '../../constants/auth'; // Import constants
16+
import { SET_CURRENT_USER, SET_HEADER_DATA, STOP_FORCE_LOGOUT } from '../../constants/auth'; // Import constants
1717

1818

1919
const middlewares = [thunk]; // Define middlewares
@@ -35,7 +35,10 @@ describe('authActions', () => {
3535
httpService.post.mockResolvedValue({ data: { token } }); // Mock the httpService post method
3636
jwtDecode.mockReturnValue(decodedToken); // Mock the jwtDecode function
3737

38-
const expectedActions = [{ type: SET_CURRENT_USER, payload: decodedToken }]; // Define expected actions
38+
const expectedActions = [
39+
{ type: STOP_FORCE_LOGOUT }, // Ensure any existing timers are cleared
40+
{ type: SET_CURRENT_USER, payload: decodedToken },
41+
]; // Define expected actions
3942

4043
await store.dispatch(loginUser(credentials)); // Dispatch the loginUser action
4144
expect(store.getActions()).toEqual(expectedActions); // Assert the actions
@@ -68,7 +71,10 @@ describe('authActions', () => {
6871

6972
it('creates SET_CURRENT_USER with null when logoutUser is called', () => {
7073
const store = mockStore({}); // Create a mock store
71-
const expectedActions = [{ type: SET_CURRENT_USER, payload: null }]; // Define expected actions
74+
const expectedActions = [
75+
{ type: STOP_FORCE_LOGOUT }, // Clear any active force-logout timer
76+
{ type: SET_CURRENT_USER, payload: null },
77+
]; // Define expected actions
7278

7379
store.dispatch(logoutUser()); // Dispatch the logoutUser action
7480
expect(store.getActions()).toEqual(expectedActions); // Assert the actions

src/actions/authActions.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SET_CURRENT_USER,
99
SET_HEADER_DATA,
1010
START_FORCE_LOGOUT,
11+
STOP_FORCE_LOGOUT,
1112
} from '../constants/auth';
1213

1314
const { tokenKey } = config;
@@ -22,6 +23,21 @@ export const setHeaderData = data => ({
2223
payload: data,
2324
});
2425

26+
/**
27+
* Stops any active force logout timer and clears related state
28+
*/
29+
export const stopForceLogout = () => (dispatch, getState) => {
30+
const { auth } = getState();
31+
if (auth?.timerId) {
32+
try {
33+
clearTimeout(auth.timerId);
34+
} catch (e) {
35+
// Timer already cleared or invalid
36+
}
37+
}
38+
dispatch({ type: STOP_FORCE_LOGOUT });
39+
};
40+
2541
export const loginUser = credentials => dispatch => {
2642
return httpService
2743
.post(ENDPOINTS.LOGIN, credentials)
@@ -33,6 +49,8 @@ export const loginUser = credentials => dispatch => {
3349
localStorage.setItem(tokenKey, res.data.token);
3450
httpService.setjwt(res.data.token);
3551
const decoded = jwtDecode(res.data.token);
52+
// Ensure any existing timers from a previous session are cleared
53+
dispatch(stopForceLogout());
3654
dispatch(setCurrentUser(decoded));
3755
return { success: true };
3856
})
@@ -98,6 +116,8 @@ export const getHeaderData = userId => {
98116
};
99117

100118
export const logoutUser = () => dispatch => {
119+
// Clear any active force-logout timer before logging out
120+
dispatch(stopForceLogout());
101121
localStorage.removeItem(tokenKey);
102122
httpService.setjwt(false);
103123
dispatch(setCurrentUser(null));
@@ -134,6 +154,9 @@ export const startForceLogout = (delayMs = 20000) => (dispatch, getState) => {
134154
// eslint-disable-next-line no-console
135155
console.error('Error acknowledging permissions during force logout:', error);
136156
} finally {
157+
// Set flag to indicate user was force logged out due to permission changes
158+
// This helps distinguish "force logged out" vs "first login after permission change"
159+
sessionStorage.setItem('wasForceLoggedOut', 'true');
137160
dispatch(logoutUser());
138161
}
139162
}, delayMs);

src/components/Auth/PermissionWatcher.jsx

Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
22
import { useSelector, useDispatch } from 'react-redux';
33
import axios from 'axios';
44
import { ENDPOINTS } from '~/utils/URL';
5-
import { startForceLogout } from '../../actions/authActions';
5+
import { startForceLogout, stopForceLogout } from '../../actions/authActions';
66
import { useCountdown } from '../../hooks/useCountdown';
77
import PopUpBar from '../PopUpBar/PopUpBar';
88
import { getUserProfile } from '../../actions/userProfile';
@@ -11,31 +11,108 @@ function PermissionWatcher() {
1111
const dispatch = useDispatch();
1212
const { isAuthenticated, forceLogoutAt } = useSelector(state => state.auth || {});
1313
const userProfile = useSelector(state => state.userProfile);
14-
const isAcknowledged = userProfile?.permissions?.isAcknowledged !== false;
14+
const isAcknowledged = userProfile?.permissions?.isAcknowledged;
1515
const [isAckLoading, setIsAckLoading] = useState(false);
16-
// Get seconds remaining until force logout
1716
const secondsRemaining = useCountdown(forceLogoutAt);
17+
const [wasForceLoggedOut, setWasForceLoggedOut] = useState(false);
18+
const [flagReady, setFlagReady] = useState(false);
19+
const [initialAcknowledgedState, setInitialAcknowledgedState] = useState(null);
20+
const [isInitialLogin, setIsInitialLogin] = useState(false);
1821

19-
// Start the force logout countdown when conditions are met
2022
useEffect(() => {
21-
if (isAuthenticated && !isAcknowledged && !forceLogoutAt) {
22-
// eslint-disable-next-line no-console
23-
console.log('Starting force logout countdown due to unacknowledged permission changes');
24-
dispatch(startForceLogout(20000)); // 20 seconds countdown
23+
if (isAuthenticated) {
24+
try {
25+
const flag = sessionStorage.getItem('wasForceLoggedOut');
26+
setWasForceLoggedOut(flag === 'true');
27+
sessionStorage.removeItem('wasForceLoggedOut');
28+
} catch {}
29+
setIsInitialLogin(true);
30+
setInitialAcknowledgedState(null);
31+
} else {
32+
setIsInitialLogin(false);
33+
setInitialAcknowledgedState(null);
34+
setWasForceLoggedOut(false);
2535
}
26-
}, [isAuthenticated, isAcknowledged, forceLogoutAt, dispatch]);
27-
// Handle acknowledgment of permission changes
36+
setFlagReady(true);
37+
}, [isAuthenticated]);
38+
39+
useEffect(() => {
40+
if (!isAuthenticated || !flagReady) return;
41+
if (!userProfile) return;
42+
if (!isInitialLogin) return;
43+
44+
if (initialAcknowledgedState === null) {
45+
const safestate = isAcknowledged === undefined ? true : isAcknowledged;
46+
setInitialAcknowledgedState(safestate);
47+
return;
48+
}
49+
50+
const loggedInWithUnacknowledgedPermissions =
51+
isAcknowledged === false && !forceLogoutAt && !wasForceLoggedOut;
52+
53+
if (loggedInWithUnacknowledgedPermissions) {
54+
setIsInitialLogin(false);
55+
return;
56+
}
57+
58+
const loggedInAfterForceLogout =
59+
isAcknowledged === false && !forceLogoutAt && wasForceLoggedOut;
60+
61+
if (loggedInAfterForceLogout) {
62+
setIsInitialLogin(false);
63+
return;
64+
}
65+
66+
if (isAcknowledged !== false) {
67+
setIsInitialLogin(false);
68+
}
69+
}, [
70+
isAuthenticated,
71+
flagReady,
72+
isAcknowledged,
73+
forceLogoutAt,
74+
wasForceLoggedOut,
75+
dispatch,
76+
isInitialLogin,
77+
initialAcknowledgedState,
78+
userProfile,
79+
]);
80+
81+
useEffect(() => {
82+
if (!isAuthenticated || !flagReady) return;
83+
if (!userProfile) return;
84+
if (isInitialLogin) return;
85+
86+
const permissionsChangedMidSession =
87+
isAcknowledged === false && !forceLogoutAt && initialAcknowledgedState !== false;
88+
89+
if (permissionsChangedMidSession) {
90+
dispatch(startForceLogout(20000));
91+
return;
92+
}
93+
94+
if (isAcknowledged === true && forceLogoutAt) {
95+
dispatch(stopForceLogout());
96+
setInitialAcknowledgedState(true);
97+
}
98+
}, [
99+
isAuthenticated,
100+
flagReady,
101+
isAcknowledged,
102+
forceLogoutAt,
103+
dispatch,
104+
isInitialLogin,
105+
initialAcknowledgedState,
106+
userProfile,
107+
]);
108+
28109
const handleAcknowledge = async () => {
29110
try {
30111
setIsAckLoading(true);
31-
32112
if (!userProfile || !userProfile._id) {
33-
// eslint-disable-next-line no-console
34-
//console.error('User profile not available');
35113
setIsAckLoading(false);
36114
return;
37115
}
38-
39116
const { firstName: name, lastName, personalLinks, adminLinks, _id } = userProfile;
40117

41118
axios
@@ -44,40 +121,34 @@ function PermissionWatcher() {
44121
lastName,
45122
personalLinks,
46123
adminLinks,
47-
48124
isAcknowledged: true,
49125
})
50126
.then(() => {
51127
setIsAckLoading(false);
128+
setInitialAcknowledgedState(true);
129+
setIsInitialLogin(false);
52130
dispatch(getUserProfile(_id));
53131
})
54132
.catch(error => {
55-
// eslint-disable-next-line no-console
56-
// console.error('Error updating user profile:', error);
57133
setIsAckLoading(false);
58134
});
59135
} catch (error) {
60-
// eslint-disable-next-line no-console
61-
// console.error('Error acknowledging permission changes:', error);
62136
setIsAckLoading(false);
63137
}
64138
};
65139

66-
// Only render the popup when a force logout is in progress
67-
if (!forceLogoutAt) {
68-
return null;
69-
}
70-
return (
71-
!isAcknowledged && (
140+
if (forceLogoutAt && isAcknowledged === false) {
141+
return (
72142
<PopUpBar
73-
message={`Permissions changed—logging out in ${secondsRemaining}s. Timer will be stopped; please restart after login.`}
143+
message={`Permissions changed—logging out in ${secondsRemaining}s unless acknowledged.`}
74144
onClickClose={handleAcknowledge}
75-
textColor="red"
76145
isLoading={isAckLoading}
77-
button={false}
146+
button
78147
/>
79-
)
80-
);
148+
);
149+
}
150+
151+
return null;
81152
}
82153

83154
export default PermissionWatcher;

0 commit comments

Comments
 (0)