Skip to content

Commit b4e22de

Browse files
committed
Merge remote-tracking branch 'upstream/release-1.15.0' into release-1.15.0
2 parents b7cafa7 + a9d11a5 commit b4e22de

File tree

5 files changed

+657
-8
lines changed

5 files changed

+657
-8
lines changed

apps/teachers/src/pages/login.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ const LoginComponent = () => {
102102

103103
setReceivedToken(receivedToken);
104104

105+
// Store tenantData for later use (e.g., in Header for switch account)
106+
if (receivedToken?.tenantData) {
107+
localStorage.setItem('tenantData', JSON.stringify(receivedToken.tenantData));
108+
}
109+
105110
setSwitchDialogOpen(true);
106111
};
107112

mfes/scp-teacher-repo/src/components/Header.tsx

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import logoLight from '../../public/images/logo-light.png';
1919
import menuIcon from '../assets/images/menuIcon.svg';
2020
import { useDirection } from '../hooks/useDirection';
2121
import useStore from '../store/store';
22+
import manageUserStore from '../store/manageUserStore';
2223
import ConfirmationModal from './ConfirmationModal';
2324
import StyledMenu from './StyledMenu';
2425
import { TENANT_DATA } from '../../app.config';
@@ -28,6 +29,11 @@ import {
2829
getUserFullName,
2930
} from '@/utils/Helper';
3031
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
32+
import SwitchAccountDialog from '@shared-lib-v2/SwitchAccount/SwitchAccount';
33+
import { getUserDetails } from '@/services/ProfileService';
34+
import { getAcademicYear } from '@/services/AcademicYearService';
35+
import { AcademicYear } from '@/utils/Interfaces';
36+
import Loader from '@shared-lib-v2/DynamicForm/components/Loader';
3137

3238
interface HeaderProps {
3339
toggleDrawer?: (newOpen: boolean) => () => void;
@@ -60,6 +66,27 @@ const Header: React.FC<HeaderProps> = ({ toggleDrawer, openDrawer }) => {
6066
const [darkMode, setDarkMode] = useState<string | null>(null);
6167
const { isRTL } = useDirection();
6268
const [adminInfo, setAdminInfo] = React.useState<any>();
69+
70+
// Switch Account Dialog states
71+
const [switchDialogOpen, setSwitchDialogOpen] = useState<boolean>(false);
72+
const [tenantData, setTenantData] = useState<any[]>([]);
73+
const [showSwitchButton, setShowSwitchButton] = useState<boolean>(false);
74+
const [loading, setLoading] = useState<boolean>(false);
75+
76+
// Store state setters
77+
const setUserIdStore = manageUserStore((state) => state.setUserId);
78+
const setUserRoleStore = useStore((state: any) => state.setUserRole);
79+
const setAccessToken = useStore((state: any) => state.setAccessToken);
80+
const setIsActiveYearSelected = useStore((state: any) => state.setIsActiveYearSelected);
81+
const setDistrictCode = manageUserStore((state: any) => state.setDistrictCode);
82+
const setDistrictId = manageUserStore((state: any) => state.setDistrictId);
83+
const setDistrictName = manageUserStore((state: any) => state.setDistrictName);
84+
const setStateCode = manageUserStore((state: any) => state.setStateCode);
85+
const setStateId = manageUserStore((state: any) => state.setStateId);
86+
const setStateName = manageUserStore((state: any) => state.setStateName);
87+
const setBlockCode = manageUserStore((state: any) => state.setBlockCode);
88+
const setBlockId = manageUserStore((state: any) => state.setBlockId);
89+
const setBlockName = manageUserStore((state: any) => state.setBlockName);
6390

6491
// Retrieve stored userId and language
6592
useEffect(() => {
@@ -179,11 +206,234 @@ const Header: React.FC<HeaderProps> = ({ toggleDrawer, openDrawer }) => {
179206
handleClose();
180207
setModalOpen(true);
181208
};
209+
210+
const getAcademicYearList = async () => {
211+
const academicYearList: AcademicYear[] = await getAcademicYear();
212+
if (academicYearList) {
213+
localStorage.setItem(
214+
'academicYearList',
215+
JSON.stringify(academicYearList)
216+
);
217+
const extractedAcademicYears = academicYearList?.map(
218+
({ id, session, isActive }) => ({ id, session, isActive })
219+
);
220+
const activeSession = extractedAcademicYears?.find(
221+
(item) => item.isActive
222+
);
223+
const activeSessionId = activeSession ? activeSession.id : '';
224+
localStorage.setItem('academicYearId', activeSessionId);
225+
setIsActiveYearSelected(true);
226+
227+
return activeSessionId;
228+
}
229+
};
230+
231+
const handleSwitchAccount = () => {
232+
handleClose();
233+
setSwitchDialogOpen(true);
234+
};
235+
236+
const callBackSwitchDialog = async (
237+
tenantId: string,
238+
tenantName: string,
239+
roleId: string,
240+
roleName: string
241+
) => {
242+
setSwitchDialogOpen(false);
243+
setLoading(true);
244+
245+
const token =
246+
typeof window !== 'undefined' && window.localStorage
247+
? localStorage.getItem('token')
248+
: '';
249+
const currentUserId = localStorage.getItem('userId');
250+
251+
// Find the tenant data for the selected tenant
252+
const selectedTenant = tenantData?.find(
253+
(tenant: any) => tenant.tenantId === tenantId
254+
);
255+
256+
if (tenantData && tenantData.length > 0) {
257+
localStorage.setItem('tenantName', tenantName);
258+
localStorage.setItem('tenantId', tenantId);
259+
260+
// Set templateId from tenant data
261+
localStorage.setItem('templtateId', selectedTenant?.templateId || '');
262+
263+
// Set channelId
264+
if (selectedTenant?.channelId) {
265+
localStorage.setItem('channelId', selectedTenant.channelId);
266+
}
267+
268+
// Set collectionFramework
269+
if (selectedTenant?.collectionFramework) {
270+
localStorage.setItem('collectionFramework', selectedTenant.collectionFramework);
271+
}
272+
273+
// Set uiConfig
274+
const uiConfig = selectedTenant?.params?.uiConfig;
275+
localStorage.setItem('uiConfig', JSON.stringify(uiConfig || {}));
276+
277+
// Set userProgram
278+
localStorage.setItem('userProgram', tenantName);
279+
} else {
280+
console.error('Tenant data not found.');
281+
}
282+
283+
localStorage.setItem('userId', currentUserId || '');
284+
setUserIdStore(currentUserId || '');
285+
286+
if (token && currentUserId) {
287+
// Set token cookie
288+
document.cookie = `token=${token}; path=/; secure; SameSite=Strict`;
289+
document.cookie = `authToken=${token}; path=/; secure; SameSite=Strict`;
290+
document.cookie = `userId=${currentUserId}; path=/; secure; SameSite=Strict`;
291+
292+
// Retrieve deviceID from local storage
293+
const deviceID = localStorage.getItem('deviceID');
294+
295+
if (deviceID) {
296+
try {
297+
// Update device notification
298+
const headers = {
299+
tenantId: tenantId,
300+
Authorization: `Bearer ${token}`,
301+
};
302+
303+
await UpdateDeviceNotification(
304+
{ deviceId: deviceID, action: 'add' },
305+
currentUserId,
306+
headers
307+
);
308+
309+
console.log('Device notification updated successfully');
310+
} catch (updateError) {
311+
console.error('Error updating device notification:', updateError);
312+
}
313+
}
314+
315+
localStorage.setItem('role', roleName);
316+
localStorage.setItem('roleName', roleName);
317+
localStorage.setItem('roleId', roleId || '');
318+
setuserRole(roleName);
319+
setUserRoleStore(roleName);
320+
setAccessToken(token);
321+
322+
const tenant = localStorage.getItem('tenantName');
323+
if (
324+
tenant?.toLocaleLowerCase() ===
325+
TENANT_DATA?.SECOND_CHANCE_PROGRAM?.toLowerCase() ||
326+
tenant?.toLocaleLowerCase() === TENANT_DATA?.PRATHAM_SCP?.toLowerCase() ||
327+
tenant?.toLocaleLowerCase() === TENANT_DATA?.YOUTHNET?.toLowerCase() ||
328+
tenant?.toLocaleLowerCase() === TENANT_DATA?.PRAGYANPATH?.toLowerCase()
329+
) {
330+
try {
331+
const userDetails = await getUserDetails(currentUserId, true);
332+
console.log(userDetails);
333+
334+
if (userDetails?.result?.userData) {
335+
const activeSessionId = await getAcademicYearList();
336+
const customFields = userDetails?.result?.userData?.customFields;
337+
if (customFields?.length) {
338+
// set customFields in userData
339+
const userDataString = localStorage.getItem('userData');
340+
const userData: any = userDataString
341+
? JSON.parse(userDataString)
342+
: null;
343+
if (userData) {
344+
userData.customFields = customFields;
345+
localStorage.setItem('userData', JSON.stringify(userData));
346+
}
347+
const state = customFields.find(
348+
(field: any) => field?.label === 'STATE'
349+
);
350+
const district = customFields.find(
351+
(field: any) => field?.label === 'DISTRICT'
352+
);
353+
const block = customFields.find(
354+
(field: any) => field?.label === 'BLOCK'
355+
);
356+
357+
if (state) {
358+
localStorage.setItem(
359+
'stateName',
360+
state?.selectedValues?.[0]?.value
361+
);
362+
setStateName(state?.selectedValues?.[0]?.value);
363+
setStateCode(state?.selectedValues?.[0]?.id);
364+
setStateId(state?.fieldId);
365+
}
366+
367+
if (district) {
368+
setDistrictName(district?.selectedValues?.[0]?.value);
369+
setDistrictCode(district?.selectedValues?.[0]?.id);
370+
setDistrictId(district?.fieldId);
371+
}
372+
373+
if (block) {
374+
setBlockName(block?.selectedValues?.[0]?.value);
375+
setBlockCode(block?.selectedValues?.[0]?.id);
376+
setBlockId(block?.fieldId);
377+
}
378+
}
379+
380+
// Route based on tenant and role
381+
// Using replace() to prevent back navigation to old program
382+
if (activeSessionId && tenant?.toLocaleLowerCase() === TENANT_DATA?.SECOND_CHANCE_PROGRAM?.toLowerCase()) {
383+
window.location.replace('/scp-teacher-repo/dashboard');
384+
} else if (tenant?.toLocaleLowerCase() === TENANT_DATA?.YOUTHNET?.toLowerCase()) {
385+
window.location.replace('/youthnet');
386+
} else if (tenant?.toLocaleLowerCase() === TENANT_DATA?.PRAGYANPATH?.toLowerCase()) {
387+
if (activeSessionId) {
388+
localStorage.setItem('academicYearId', activeSessionId);
389+
}
390+
391+
// Check if user has Lead role for PRAGYANPATH
392+
const hasLead = tenantData?.some((tenant: any) =>
393+
tenant.roles.some((role: any) => role.roleName.toLowerCase().includes("lead"))
394+
);
395+
396+
if (hasLead && roleName.toLowerCase().includes('lead')) {
397+
// For Lead role, set managrUserId for manager dashboard
398+
localStorage.setItem('managrUserId', currentUserId);
399+
window.location.replace('/youthnet/manager-dashboard');
400+
} else {
401+
// For other roles, redirect to youthNet MFE
402+
window.location.replace('/youthnet');
403+
}
404+
}
405+
console.log('userDetails', userDetails);
406+
}
407+
} catch (error) {
408+
console.error('Error fetching user details:', error);
409+
setLoading(false);
410+
}
411+
}
412+
}
413+
setLoading(false);
414+
};
182415
useEffect(() => {
183416
if (typeof window !== 'undefined' && window.localStorage) {
184417
const admin = localStorage.getItem('userData');
185418
if (admin && admin !== 'undefined')
186419
setAdminInfo(JSON?.parse(admin) || {});
420+
421+
// Load tenantData for switch account functionality
422+
const storedTenantData = localStorage.getItem('tenantData');
423+
if (storedTenantData) {
424+
try {
425+
const parsedTenantData = JSON.parse(storedTenantData);
426+
setTenantData(parsedTenantData);
427+
428+
// Show switch button if there are multiple tenants or multiple roles in any tenant
429+
const shouldShowButton =
430+
parsedTenantData.length > 1 ||
431+
parsedTenantData.some((tenant: any) => tenant?.roles?.length > 1);
432+
setShowSwitchButton(shouldShowButton);
433+
} catch (error) {
434+
console.error('Error parsing tenantData:', error);
435+
}
436+
}
187437
}
188438
}, []);
189439
return (
@@ -479,6 +729,28 @@ const Header: React.FC<HeaderProps> = ({ toggleDrawer, openDrawer }) => {
479729
{t('COMMON.LOGOUT')}
480730
</Button>
481731
</Box>
732+
733+
{/* Switch Account Button */}
734+
{showSwitchButton && (
735+
<>
736+
<Divider sx={{ color: '#D0C5B4' }} />
737+
<Box sx={{ px: '20px', pb: '20px' }}>
738+
<Button
739+
fullWidth
740+
variant="outlined"
741+
color="primary"
742+
onClick={handleSwitchAccount}
743+
sx={{
744+
fontSize: '16px',
745+
backgroundColor: 'white',
746+
border: '0.6px solid #1E1B16',
747+
}}
748+
>
749+
{t('COMMON.SWITCH_ACCOUNT', { defaultValue: 'Switch Account' })}
750+
</Button>
751+
</Box>
752+
</>
753+
)}
482754
</Box>
483755
</Menu>
484756
</StyledMenu>
@@ -502,8 +774,21 @@ const Header: React.FC<HeaderProps> = ({ toggleDrawer, openDrawer }) => {
502774
language={language}
503775
setLanguage={setLanguage}
504776
/>
777+
778+
{/* Switch Account Dialog */}
779+
<SwitchAccountDialog
780+
open={switchDialogOpen}
781+
onClose={() => setSwitchDialogOpen(false)}
782+
callbackFunction={callBackSwitchDialog}
783+
authResponse={tenantData}
784+
/>
505785
</Box>
506786
<Box sx={{ marginTop: '10px' }}></Box>
787+
788+
{/* Loading Indicator */}
789+
{loading && (
790+
<Loader showBackdrop={true} loadingText={t('COMMON.LOADING')} />
791+
)}
507792
</>
508793
);
509794
};

mfes/scp-teacher-repo/src/pages/_app.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { useEffect } from 'react';
2828
import { ToastContainer } from 'react-toastify';
2929
import { prefixer } from 'stylis';
3030
import rtlPlugin from 'stylis-plugin-rtl';
31-
import { fullWidthPages } from '../../app.config';
31+
import { fullWidthPages, TENANT_DATA } from '../../app.config';
3232
import nextI18NextConfig from '../../next-i18next.config.js';
3333
import { useDirection } from '../hooks/useDirection';
3434
import customTheme from '../styles/customTheme';
@@ -93,6 +93,41 @@ function App({ Component, pageProps }: AppProps) {
9393
const router = useRouter();
9494
const isFullWidthPage = fullWidthPages.includes(router.pathname);
9595

96+
// Route Guard: Only allow access if userProgram is "Second Chance Program"
97+
useEffect(() => {
98+
const checkAccess = () => {
99+
// Skip check for public pages
100+
const publicPages = ['/login', '/forgot-password', '/reset-password', '/create-password', '/unauthorized'];
101+
if (publicPages.includes(router.pathname)) {
102+
return;
103+
}
104+
105+
if (typeof window !== 'undefined') {
106+
const userProgram = localStorage.getItem('userProgram');
107+
const tenantName = localStorage.getItem('tenantName');
108+
109+
const isAuthorized =
110+
userProgram?.toLowerCase() === TENANT_DATA.SECOND_CHANCE_PROGRAM?.toLowerCase() ||
111+
userProgram?.toLowerCase() === TENANT_DATA.PRATHAM_SCP?.toLowerCase() ||
112+
tenantName?.toLowerCase() === TENANT_DATA.SECOND_CHANCE_PROGRAM?.toLowerCase() ||
113+
tenantName?.toLowerCase() === TENANT_DATA.PRATHAM_SCP?.toLowerCase();
114+
115+
if (!isAuthorized) {
116+
console.warn('Access denied: Invalid program for scp-teacher-repo');
117+
// Redirect to unauthorized or back to appropriate application
118+
router.push('/unauthorized');
119+
}
120+
}
121+
};
122+
123+
checkAccess();
124+
router.events.on('routeChangeComplete', checkAccess);
125+
126+
return () => {
127+
router.events.off('routeChangeComplete', checkAccess);
128+
};
129+
}, [router.pathname, router.events]);
130+
96131
useEffect(() => {
97132
const htmlElement = document.documentElement;
98133
if (i18n.language === 'ur') {

0 commit comments

Comments
 (0)