@argusoft/medplat-app-shell 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/package.json +139 -141
  2. package/src/GlobalErrorBoundary.jsx +31 -0
  3. package/src/SilentErrorFallback.jsx +68 -0
  4. package/src/TrackingProviderWrapper.jsx +40 -0
  5. package/src/_tests_/__mocks__/MockTranslationProvider.jsx +21 -0
  6. package/src/_tests_/__mocks__/ckeditor.js +45 -0
  7. package/src/_tests_/__mocks__/fileMock.js +1 -0
  8. package/src/_tests_/__mocks__/useranalytics.js +5 -0
  9. package/src/_tests_/views/components/Dashboard/DashboardUI.test.jsx +137 -0
  10. package/src/_tests_/views/components/Dashboard/DashboardUIMock.js +877 -0
  11. package/src/_tests_/views/components/ForgotPassword/ForgotPassword.test.jsx +314 -0
  12. package/src/_tests_/views/components/LocationDirective/LocationDirective.test.jsx.disable +229 -0
  13. package/src/_tests_/views/components/LocationDirective/mockLocationDirective.js +810 -0
  14. package/src/_tests_/views/components/LocationType/MockLocationType.js +42259 -0
  15. package/src/_tests_/views/components/LocationType/addlocationtype.test.jsx.disable +276 -0
  16. package/src/_tests_/views/components/LocationType/editlocationtype.test.jsx.disable +262 -0
  17. package/src/_tests_/views/components/LocationType/locationtype.test.jsx.disable +148 -0
  18. package/src/_tests_/views/components/Profile/UpdateProfileModalData.js +4396 -0
  19. package/src/_tests_/views/components/Profile/updateprofilemodal.test.jsx +282 -0
  20. package/src/_tests_/views/components/SideBar/MockSideBar.js +1379 -0
  21. package/src/_tests_/views/components/SideBar/SideBar.test.jsx +98 -0
  22. package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/AddManageSystemConfig.test.jsx.disable +164 -0
  23. package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/UpdateManageSystemConfig.test.jsx.disable +157 -0
  24. package/src/_tests_/views/components/SystemConfig/MockSystemConfig.js +1280 -0
  25. package/src/_tests_/views/components/SystemConfig/SystemConfig.test.jsx.disable +165 -0
  26. package/src/_tests_/views/components/login/Login.test.jsx +276 -0
  27. package/src/_tests_/views/components/login/MockAuthorise.js +2414 -0
  28. package/src/_tests_/views/components/login/ServiceResponse.js +595 -0
  29. package/src/_tests_/views/components/user/MockManageUser.js +7965 -0
  30. package/src/_tests_/views/components/user/manageuser.test.jsx.disable +989 -0
  31. package/src/_tests_/views/components/user/mockUsersData.js +582 -0
  32. package/src/assets/img/OASISLogin.png +0 -0
  33. package/src/assets/img/bahaarNew.png +0 -0
  34. package/src/assets/img/dnhdd4K.png +0 -0
  35. package/src/assets/img/govtofup.png +0 -0
  36. package/src/assets/img/sewarural4K.png +0 -0
  37. package/src/assets/img/techo4K.png +0 -0
  38. package/src/assets/img/up4K.png +0 -0
  39. package/src/common/HolidayList.jsx +573 -0
  40. package/src/common/MalaciaousInputUtil.js +23 -0
  41. package/src/common/SafeHtml.jsx +17 -0
  42. package/src/common/VersionManager.jsx +109 -0
  43. package/src/common/constants/PerformanceDashboard.js +514 -0
  44. package/src/common/constants/app.constant.js +781 -0
  45. package/src/common/constants/cccVerificationConstants.js +18 -0
  46. package/src/common/constants/fhsrConstant.js +33 -0
  47. package/src/common/constants/gvk-verification.constant.js +76 -0
  48. package/src/common/constants/search.constant.js +23 -0
  49. package/src/common/constants/teleconsulatationConstant.jsx +1339 -0
  50. package/src/common/directives/SearchTemplate.jsx +784 -0
  51. package/src/common/directives/SearchTemplate.scss +14 -0
  52. package/src/common/dynamicView/DynamicView.jsx +353 -0
  53. package/src/common/dynamicView/InputFieldComponent.jsx +1501 -0
  54. package/src/common/dynamicView/InputViewComponent.jsx +298 -0
  55. package/src/common/dynamicView/InputViewComponent.scss +15 -0
  56. package/src/common/env.js +5 -0
  57. package/src/common/filters/locationNameFilter.js +26 -0
  58. package/src/common/fontAwesomeIcons/FontAwesomeIcons.jsx +27 -0
  59. package/src/common/fontAwesomeIcons/FontAwesomeIconsNames.js +1968 -0
  60. package/src/common/fontPreferences/fontSizeProvider.jsx +34 -0
  61. package/src/common/fontPreferences/fontSizeSelector.jsx +116 -0
  62. package/src/common/getAssignedFeature/getAssignedFeature.js +32 -0
  63. package/src/common/interceptors/AxiosInterceptor.js +216 -0
  64. package/src/common/languageTranslator/TranslationContext.js +5 -0
  65. package/src/common/languageTranslator/TranslationProvider.jsx +24 -0
  66. package/src/common/languageTranslator/i18n.js +49 -0
  67. package/src/common/services/AuthenticateService.js +116 -0
  68. package/src/common/services/DownloadFile.js +35 -0
  69. package/src/common/services/ForgotPassword.js +18 -0
  70. package/src/common/services/FormConfiguratorService.js +195 -0
  71. package/src/common/services/GlobalApis.js +84 -0
  72. package/src/common/services/InterceptorNavigationService.js +17 -0
  73. package/src/common/services/LocationService.js +65 -0
  74. package/src/common/services/LocationType.js +11 -0
  75. package/src/common/services/QueryBuilder.js +36 -0
  76. package/src/common/services/Roles.js +28 -0
  77. package/src/common/services/SyncWithServer.js +15 -0
  78. package/src/common/services/SystemConfig.js +15 -0
  79. package/src/common/services/TranslationService.js +70 -0
  80. package/src/common/services/TwoFactorService.js +7 -0
  81. package/src/common/services/Users.js +91 -0
  82. package/src/common/services/Webtasks.js +27 -0
  83. package/src/common/services/util/Convert-pad-data-to-API-format.jsx +167 -0
  84. package/src/common/services/util/Convert-to-UI-format.jsx +82 -0
  85. package/src/common/services/util/EmptyPrescriptionPadData.jsx +11 -0
  86. package/src/common/services/util/GeneralUtil.js +456 -0
  87. package/src/common/services/util/Prescription-pad-util.js +339 -0
  88. package/src/common/services/util/PrescriptionPadData.js +67 -0
  89. package/src/common/services/util/PrescriptionpadCommonUtil.js +96 -0
  90. package/src/common/services/util/ReportFieldUtil.jsx +398 -0
  91. package/src/common/services/util/WebSocketContext.jsx +261 -0
  92. package/src/common/syncWithServer/SyncWithServerDialog.jsx +170 -0
  93. package/src/common/syncWithServer/SyncWithServerDialogSkeleton.jsx +67 -0
  94. package/src/common/tests/CustomWrapper.jsx +49 -0
  95. package/src/common/tests/TranslationWrapper.jsx +38 -0
  96. package/src/common/themeProvider/ColorInputs.jsx +97 -0
  97. package/src/common/themeProvider/EditableColorInput.jsx +128 -0
  98. package/src/common/themeProvider/ThemeEditor.jsx +319 -0
  99. package/src/common/themeProvider/ThemeProvider.jsx +210 -0
  100. package/src/common/themeProvider/themeConfig.js +558 -0
  101. package/src/common/toaster/toaster.jsx +30 -0
  102. package/src/firebaseConfig.js +24 -0
  103. package/src/global.scss +221 -0
  104. package/src/hooks/.gitkeep +0 -0
  105. package/src/hooks/useAESEncryption.js +56 -0
  106. package/src/hooks/useCaching.js +43 -0
  107. package/src/hooks/useDebounce.js +34 -0
  108. package/src/hooks/useDebounceFn.js +50 -0
  109. package/src/hooks/useDownloadPdf.js +358 -0
  110. package/src/hooks/useDownloadXlsx.js +55 -0
  111. package/src/hooks/useListValueFieldValues.js +30 -0
  112. package/src/hooks/useLocationHierarchies.js +63 -0
  113. package/src/hooks/useLocationHierarchyTranslate.js +16 -0
  114. package/src/hooks/useOnline.js +27 -0
  115. package/src/hooks/usePagination.js +63 -0
  116. package/src/hooks/useRefreshToken.js +87 -0
  117. package/src/hooks/useScript.js +25 -0
  118. package/src/hooks/useStopwatch.js +75 -0
  119. package/src/hooks/useTrackEvent.js +22 -0
  120. package/src/hooks/useWebAudioRecorder.js +115 -0
  121. package/src/layout/LoaderComponet.jsx +22 -0
  122. package/src/layout/LoaderContext.jsx +29 -0
  123. package/src/layout/mainLayout/AdaptiveZoom.jsx +27 -0
  124. package/src/layout/mainLayout/Chatbot.jsx +243 -0
  125. package/src/layout/mainLayout/Layout.jsx +445 -0
  126. package/src/layout/mainLayout/Profile/UpdateProfileModal.jsx +684 -0
  127. package/src/layout/mainLayout/header/LogoutModal.jsx +131 -0
  128. package/src/layout/mainLayout/header/Navbar.jsx +1677 -0
  129. package/src/layout/mainLayout/header/Navbar.scss +4 -0
  130. package/src/layout/mainLayout/header/index.js +0 -0
  131. package/src/layout/mainLayout/sidebar/SideBar.jsx +1402 -0
  132. package/src/layout/mainLayout/sidebar/Sidebar.css +159 -0
  133. package/src/layout/mainLayout/sidebar/index.js +0 -0
  134. package/src/logo.svg +1 -0
  135. package/src/reportWebVitals.js +13 -0
  136. package/src/setupFirebaseMessaging.js +28 -0
  137. package/src/setupTests.js +8 -0
  138. package/src/store/actions/AuthenticationActions.js +0 -0
  139. package/src/store/actions/ReportsActions.js +0 -0
  140. package/src/store/actions/TranslationAction.js +0 -0
  141. package/src/store/index.js +8 -0
  142. package/src/store/reducer.js +46 -0
  143. package/src/store/reducers/AuthenticationReducer.js +50 -0
  144. package/src/store/reducers/CalendarEventReducer.js +41 -0
  145. package/src/store/reducers/ConditionClipboardReducer.js +45 -0
  146. package/src/store/reducers/FeatureReducer.js +27 -0
  147. package/src/store/reducers/FormConfiguratorReducer.js +38 -0
  148. package/src/store/reducers/LoadingReducer.js +20 -0
  149. package/src/store/reducers/MembersAuthenticationReducer.js +28 -0
  150. package/src/store/reducers/PrescriptionPadReducer.js +329 -0
  151. package/src/store/reducers/QuestionaireReducer.js +29 -0
  152. package/src/store/reducers/ReportsReducer.js +24 -0
  153. package/src/store/reducers/SkeletonReducer.js +20 -0
  154. package/src/store/reducers/ThemeReducer.js +106 -0
  155. package/src/store/reducers/TranslationReducer.js +126 -0
  156. package/src/store/reducers/dashboardEditorSlice.js +77 -0
  157. package/src/store/reducers/districtHealthDashboardSlice.js +58 -0
  158. package/src/store/reducers/immunizationSlice.js +234 -0
  159. package/src/store/slices/dashboardPagesSlice.js +51 -0
  160. package/src/utils/.gitkeep +0 -0
  161. package/src/utils/FormConstants.js +2629 -0
  162. package/src/utils/GujaratTopoChart.jsx +483 -0
  163. package/src/utils/UUIDgenerator.js +8 -0
  164. package/src/utils/appointment-utils/appointment-utils.js +123 -0
  165. package/src/utils/feature.js +42 -0
  166. package/src/utils/getThemeColor.js +12 -0
  167. package/src/utils/localStorageHelper.js +11 -0
  168. package/src/utils/notifications/enable-push-notifications.js +27 -0
  169. package/src/utils/resolveAppliedStyle.js +11 -0
  170. package/src/utils/themeConfigs.js +1483 -0
  171. package/src/views/custom-components/.gitkeep +0 -0
  172. package/src/views/custom-components/AgIconButton/RIf.jsx +14 -0
  173. package/src/views/custom-components/AgIconButton/button.jsx +108 -0
  174. package/src/views/custom-components/AgIconButton/waterDrop.jsx +95 -0
  175. package/src/views/custom-components/AgIconButton/waterDrop.scss +37 -0
  176. package/src/views/custom-components/AlertPlaceholder.jsx +32 -0
  177. package/src/views/custom-components/AllFaIconsSelector.jsx +56 -0
  178. package/src/views/custom-components/CkEditor/CkEditor.js +102 -0
  179. package/src/views/custom-components/CustomAccordion.jsx +72 -0
  180. package/src/views/custom-components/CustomActionIcons.jsx +118 -0
  181. package/src/views/custom-components/CustomAutoComplete.jsx +188 -0
  182. package/src/views/custom-components/CustomCheckBox.jsx +60 -0
  183. package/src/views/custom-components/CustomConfirmationModal.jsx +118 -0
  184. package/src/views/custom-components/CustomCountrySelect.jsx +129 -0
  185. package/src/views/custom-components/CustomDatePicker.jsx +122 -0
  186. package/src/views/custom-components/CustomDropdown.jsx +191 -0
  187. package/src/views/custom-components/CustomFileUpload.jsx +387 -0
  188. package/src/views/custom-components/CustomFullCalendar.jsx +514 -0
  189. package/src/views/custom-components/CustomInfiniteScroll.jsx +126 -0
  190. package/src/views/custom-components/CustomRadioComponent.jsx +65 -0
  191. package/src/views/custom-components/CustomStatsComponent.jsx +114 -0
  192. package/src/views/custom-components/CustomSvgUpload.jsx +170 -0
  193. package/src/views/custom-components/CustomSwitch.jsx +37 -0
  194. package/src/views/custom-components/CustomTabPanel.jsx +19 -0
  195. package/src/views/custom-components/CustomTextArea.jsx +62 -0
  196. package/src/views/custom-components/CustomTextArea.scss +27 -0
  197. package/src/views/custom-components/CustomTextField.jsx +116 -0
  198. package/src/views/custom-components/CustomToggleSwitch.jsx +138 -0
  199. package/src/views/custom-components/CustomTooltip.jsx +51 -0
  200. package/src/views/custom-components/CustomZoomImage.jsx +134 -0
  201. package/src/views/custom-components/CustomizedTable/CustomizedTableV2.jsx +1407 -0
  202. package/src/views/custom-components/CustomizedTable/VirtualizeTableBody.jsx +295 -0
  203. package/src/views/custom-components/CustomizedTable/helper.jsx +159 -0
  204. package/src/views/custom-components/CustomizedTable.jsx +532 -0
  205. package/src/views/custom-components/EditInputField.jsx +174 -0
  206. package/src/views/custom-components/FieldDescription.jsx +22 -0
  207. package/src/views/custom-components/FileDisplayComponent.jsx +138 -0
  208. package/src/views/custom-components/FormItem.jsx +53 -0
  209. package/src/views/custom-components/GenericChart.jsx +80 -0
  210. package/src/views/custom-components/InfoBadge.jsx +60 -0
  211. package/src/views/custom-components/PostgresEditor.jsx +801 -0
  212. package/src/views/custom-components/ResizableEditAutocompleteField.jsx +249 -0
  213. package/src/views/custom-components/ResizableEditInputField.jsx +215 -0
  214. package/src/views/custom-components/ResizeableEditSelectField.jsx +197 -0
  215. package/src/views/custom-components/SideOverlay.jsx +113 -0
  216. package/src/views/custom-components/SideOverlay.scss +42 -0
  217. package/src/views/custom-components/calendar.scss +571 -0
  218. package/src/views/feature-components/.gitkeep +0 -0
  219. package/src/views/feature-components/Dashboard/DashboardUI.jsx +1043 -0
  220. package/src/views/feature-components/Dashboard/DhnddModal/AshaDataQualityVerificationModal.jsx +278 -0
  221. package/src/views/feature-components/Dashboard/PinFeatureModal.jsx +143 -0
  222. package/src/views/feature-components/Dashboard/QuickLinks.jsx +163 -0
  223. package/src/views/feature-components/Dashboard/Taskbar.jsx +56 -0
  224. package/src/views/feature-components/Dashboard/WebtasksFilterForm.jsx +109 -0
  225. package/src/views/feature-components/Dashboard/WidgetCard.jsx +161 -0
  226. package/src/views/feature-components/Dashboard/actionModal.jsx +263 -0
  227. package/src/views/feature-components/Dashboard/ekavachModal/HealthWorkerIncorrectDetailsModal.jsx +332 -0
  228. package/src/views/feature-components/Dashboard/ekavachModal/MoMaternalDeathVerifcationModal.jsx +275 -0
  229. package/src/views/feature-components/Dashboard/ekavachModal/MoVerficationForChildScreeningMoadal.jsx +566 -0
  230. package/src/views/feature-components/FeatureUsageAnalytics/FeatureUsageAnalytics.jsx +989 -0
  231. package/src/views/feature-components/Features/NewServerManagement.jsx +217 -0
  232. package/src/views/feature-components/Features/ServerManagement.scss +120 -0
  233. package/src/views/feature-components/ForgotPassword/ForgotPassword.jsx +226 -0
  234. package/src/views/feature-components/LocationDirective/LocationDirective.jsx +992 -0
  235. package/src/views/feature-components/LocationDirective/LocationDirectiveV2.jsx +909 -0
  236. package/src/views/feature-components/NotFound.jsx +66 -0
  237. package/src/views/feature-components/Onboarding/Onboarding.jsx +1400 -0
  238. package/src/views/feature-components/Skeletons.js +115 -0
  239. package/src/views/feature-components/Unauthorized.jsx +48 -0
  240. package/src/views/feature-components/VerifyRoute.jsx +88 -0
  241. package/src/views/feature-components/YearlyRecap/YearlyRecap.jsx +357 -0
  242. package/src/views/feature-components/YearlyRecap/components/RecapSlide.jsx +183 -0
  243. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationContext.js +5 -0
  244. package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationProvider.jsx +26 -0
  245. package/src/views/feature-components/YearlyRecap/languageTranslator/i18n.js +46 -0
  246. package/src/views/feature-components/YearlyRecap/languageTranslator/translations.json +167 -0
  247. package/src/views/feature-components/YearlyRecap/slides/IntroSlide.jsx +233 -0
  248. package/src/views/feature-components/YearlyRecap/slides/MaternalHealthSlide.jsx +146 -0
  249. package/src/views/feature-components/YearlyRecap/slides/MetricSlide.jsx +227 -0
  250. package/src/views/feature-components/YearlyRecap/slides/OutroSlide.jsx +701 -0
  251. package/src/views/feature-components/YearlyRecap/slides/ReachSlide.jsx +273 -0
  252. package/src/views/feature-components/login/Login.jsx +840 -0
  253. package/src/views/feature-components/login/Login.scss +154 -0
  254. package/src/views/feature-components/login/LoginConfigurator.jsx +1149 -0
  255. package/src/views/feature-components/login/TwoFactorSetupModal.jsx +411 -0
  256. package/src/views/feature-components/login/simplifyMenu.js +45 -0
  257. package/src/views/feature-components/system-config/ManageSystemConfigs.jsx +284 -0
  258. package/src/views/feature-components/system-config/SystemConfig.jsx +299 -0
  259. package/src/views/feature-components/users/ChangePasswordModal.jsx +243 -0
  260. package/src/views/feature-components/users/PasswordField.jsx +56 -0
  261. package/dist/index.css +0 -1
  262. package/dist/index.js +0 -32001
  263. package/dist/index.js.map +0 -1
@@ -0,0 +1,411 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Box, Button, CircularProgress, Modal, Typography, IconButton, Tooltip } from '@mui/material';
3
+ import ShieldOutlinedIcon from '@mui/icons-material/ShieldOutlined';
4
+ import CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';
5
+ import ContentCopyRoundedIcon from '@mui/icons-material/ContentCopyRounded';
6
+ import GppMaybeRoundedIcon from '@mui/icons-material/GppMaybeRounded';
7
+ import WatchLaterOutlinedIcon from '@mui/icons-material/WatchLaterOutlined';
8
+ import QRCode from 'react-qr-code';
9
+ import PropTypes from 'prop-types';
10
+ import { motion, AnimatePresence } from 'framer-motion';
11
+ import { setupTwoFactor, verifyTwoFactor } from '@/common/services/TwoFactorService';
12
+ import { showToast } from '@/common/toaster/toaster';
13
+ import CustomTextField from '@/views/custom-components/CustomTextField';
14
+ import { useForm } from 'react-hook-form';
15
+ import { yupResolver } from '@hookform/resolvers/yup';
16
+ import * as Yup from 'yup';
17
+
18
+ const codeSchema = Yup.object().shape({
19
+ code: Yup.string()
20
+ .required('Enter the 6 digit code')
21
+ .matches(/^\d{6}$/, 'Code must be 6 digits'),
22
+ });
23
+
24
+ // Any TOTP authenticator app works, so the instructions stay generic.
25
+ const steps = [
26
+ 'Open your authenticator app (e.g. Google Authenticator, Microsoft Authenticator, Authy).',
27
+ 'Tap the + button and scan the QR code below.',
28
+ 'Enter the 6-digit code shown in the app to confirm.',
29
+ ];
30
+
31
+ /**
32
+ * TwoFactorSetupModal
33
+ *
34
+ * Shown right after a successful login for a user whose role requires Google Authenticator based 2FA
35
+ * but who has not yet completed enrolment. The step is skippable: skipping still leaves the user logged
36
+ * in and the step reappears on the next login until enrolment is completed.
37
+ *
38
+ * @module TwoFactorSetupModal
39
+ * @param {Object} props
40
+ * @param {boolean} props.open - Whether the modal is open.
41
+ * @param {number} props.userId - Id of the logged in user.
42
+ * @param {string} props.userName - Username shown for context.
43
+ * @param {function} props.onSkip - Called when the user skips setup for now.
44
+ * @param {function} props.onVerified - Called after the code is successfully verified.
45
+ * @returns {JSX.Element}
46
+ */
47
+ const TwoFactorSetupModal = ({ open, userId, userName, onSkip, onVerified }) => {
48
+ const [setupData, setSetupData] = useState(null);
49
+ const [loading, setLoading] = useState(false);
50
+ const [verifying, setVerifying] = useState(false);
51
+ // 'scan' while enrolling, 'confirmSkip' when confirming a skip, 'success' once verified,
52
+ // 'skipped' once the user has chosen to skip for now.
53
+ const [phase, setPhase] = useState('scan');
54
+
55
+ const {
56
+ control,
57
+ handleSubmit,
58
+ formState: { errors },
59
+ } = useForm({
60
+ defaultValues: { code: '' },
61
+ resolver: yupResolver(codeSchema),
62
+ mode: 'onChange',
63
+ });
64
+
65
+ useEffect(() => {
66
+ const fetchSetup = async () => {
67
+ if (!open || !userId) return;
68
+ try {
69
+ setLoading(true);
70
+ const { data } = await setupTwoFactor(userId);
71
+ setSetupData(data);
72
+ } catch (error) {
73
+ console.error(error);
74
+ showToast({ type: 'error', message: 'Could not generate the authenticator QR code' });
75
+ } finally {
76
+ setLoading(false);
77
+ }
78
+ };
79
+ fetchSetup();
80
+ }, [open, userId]);
81
+
82
+ // After the success or skipped animation, continue into the app automatically.
83
+ useEffect(() => {
84
+ if (phase !== 'success' && phase !== 'skipped') return undefined;
85
+ const proceed = phase === 'success' ? onVerified : onSkip;
86
+ const timer = setTimeout(() => proceed?.(), 2200);
87
+ return () => clearTimeout(timer);
88
+ }, [phase, onVerified, onSkip]);
89
+
90
+ const onVerify = async ({ code }) => {
91
+ try {
92
+ setVerifying(true);
93
+ const { data } = await verifyTwoFactor(userId, code);
94
+ if (data?.verified) {
95
+ setPhase('success');
96
+ } else {
97
+ showToast({ type: 'error', message: 'Invalid code. Please enter the current code from the app.' });
98
+ }
99
+ } catch (error) {
100
+ console.error(error);
101
+ showToast({ type: 'error', message: 'Could not verify the code' });
102
+ } finally {
103
+ setVerifying(false);
104
+ }
105
+ };
106
+
107
+ const handleCopy = async () => {
108
+ try {
109
+ await navigator.clipboard.writeText(setupData?.secret || '');
110
+ showToast({ type: 'success', message: 'Key copied to clipboard' });
111
+ } catch {
112
+ showToast({ type: 'error', message: 'Could not copy the key' });
113
+ }
114
+ };
115
+
116
+ return (
117
+ <Modal
118
+ open={open}
119
+ onClose={phase === 'scan' ? () => setPhase('confirmSkip') : undefined}
120
+ data-testid="two-factor-setup-modal"
121
+ >
122
+ <div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
123
+ <motion.div
124
+ initial={{ opacity: 0, y: 24, scale: 0.96 }}
125
+ animate={{ opacity: 1, y: 0, scale: 1 }}
126
+ transition={{ type: 'spring', stiffness: 220, damping: 22 }}
127
+ className="w-full max-w-md"
128
+ >
129
+ <Box className="w-full rounded-2xl bg-white shadow-2xl overflow-hidden">
130
+ <AnimatePresence mode="wait">
131
+ {phase === 'scan' ? (
132
+ <motion.div
133
+ key="scan"
134
+ initial={{ opacity: 0 }}
135
+ animate={{ opacity: 1 }}
136
+ exit={{ opacity: 0, scale: 0.98 }}
137
+ transition={{ duration: 0.2 }}
138
+ >
139
+ {/* Header */}
140
+ <div className="flex flex-col items-center px-8 pt-8 pb-2 bg-gradient-to-b from-blue-50 to-white">
141
+ <motion.div
142
+ initial={{ scale: 0.6, opacity: 0 }}
143
+ animate={{ scale: 1, opacity: 1 }}
144
+ transition={{ type: 'spring', stiffness: 260, damping: 18 }}
145
+ className="flex items-center justify-center w-14 h-14 rounded-full bg-[#007ACC] text-white shadow-lg shadow-blue-200 mb-3"
146
+ >
147
+ <ShieldOutlinedIcon />
148
+ </motion.div>
149
+ <Typography className="text-lg font-bold text-gray-800">Secure your account</Typography>
150
+ <Typography className="text-sm text-gray-500 text-center mt-1">
151
+ Your role requires two-factor authentication using an authenticator app
152
+ {userName ? ` for ${userName}` : ''}.
153
+ </Typography>
154
+ </div>
155
+
156
+ <div className="px-8 py-4">
157
+ {/* Steps */}
158
+ <ol className="space-y-2 mb-4">
159
+ {steps.map((text, idx) => (
160
+ <li key={idx} className="flex items-start gap-3">
161
+ <span className="flex-shrink-0 flex items-center justify-center w-5 h-5 rounded-full bg-blue-100 text-[#007ACC] text-xs font-semibold mt-0.5">
162
+ {idx + 1}
163
+ </span>
164
+ <Typography className="text-sm text-gray-600">{text}</Typography>
165
+ </li>
166
+ ))}
167
+ </ol>
168
+
169
+ {/* QR */}
170
+ <div className="flex flex-col items-center">
171
+ {loading ? (
172
+ <Box className="h-[196px] flex items-center justify-center">
173
+ <CircularProgress size={28} />
174
+ </Box>
175
+ ) : setupData?.otpAuthUri ? (
176
+ <motion.div
177
+ initial={{ opacity: 0, scale: 0.9 }}
178
+ animate={{ opacity: 1, scale: 1 }}
179
+ transition={{ duration: 0.3 }}
180
+ className="bg-white p-4 rounded-xl border border-slate-100 shadow-sm"
181
+ >
182
+ <QRCode value={setupData.otpAuthUri} size={172} />
183
+ </motion.div>
184
+ ) : (
185
+ <Typography variant="body2" color="error">
186
+ Unable to load the QR code.
187
+ </Typography>
188
+ )}
189
+
190
+ {setupData?.secret && (
191
+ <div className="w-full mt-4">
192
+ <Typography className="text-xs text-gray-400 text-center mb-1">
193
+ Can&apos;t scan? Enter this key manually
194
+ </Typography>
195
+ <div className="flex items-center justify-center gap-2 rounded-lg bg-slate-50 border border-slate-100 px-3 py-2">
196
+ <Typography
197
+ className="font-mono text-sm break-all text-gray-700"
198
+ data-testid="two-factor-secret"
199
+ >
200
+ {setupData.secret}
201
+ </Typography>
202
+ <Tooltip title="Copy key">
203
+ <IconButton size="small" onClick={handleCopy} data-testid="two-factor-copy-key">
204
+ <ContentCopyRoundedIcon fontSize="small" />
205
+ </IconButton>
206
+ </Tooltip>
207
+ </div>
208
+ </div>
209
+ )}
210
+ </div>
211
+
212
+ {/* Code entry */}
213
+ <form onSubmit={handleSubmit(onVerify)} className="mt-5 space-y-4">
214
+ <CustomTextField
215
+ control={control}
216
+ name="code"
217
+ placeholder="••••••"
218
+ errors={errors}
219
+ fullWidth
220
+ inputProps={{
221
+ maxLength: 6,
222
+ inputMode: 'numeric',
223
+ pattern: '[0-9]*',
224
+ style: {
225
+ textAlign: 'center',
226
+ letterSpacing: '0.5em',
227
+ fontSize: '1.2rem',
228
+ fontWeight: 600,
229
+ },
230
+ onInput: (e) => {
231
+ e.target.value = e.target.value.replace(/\D/g, '');
232
+ },
233
+ }}
234
+ dataTestId="two-factor-code-field"
235
+ />
236
+ <Box className="flex items-center justify-between gap-3 pb-6">
237
+ <Button
238
+ variant="text"
239
+ color="inherit"
240
+ size="small"
241
+ onClick={() => setPhase('confirmSkip')}
242
+ className="text-gray-500 normal-case"
243
+ data-testid="two-factor-skip-button"
244
+ >
245
+ Skip for now
246
+ </Button>
247
+ <Button
248
+ variant="contained"
249
+ size="medium"
250
+ type="submit"
251
+ disabled={verifying || loading || !setupData?.otpAuthUri}
252
+ className="rounded-full px-6 normal-case font-medium"
253
+ data-testid="two-factor-verify-button"
254
+ >
255
+ {verifying ? <CircularProgress size={20} color="inherit" /> : 'Verify & Continue'}
256
+ </Button>
257
+ </Box>
258
+ </form>
259
+ </div>
260
+ </motion.div>
261
+ ) : phase === 'confirmSkip' ? (
262
+ <motion.div
263
+ key="confirmSkip"
264
+ initial={{ opacity: 0, scale: 0.98 }}
265
+ animate={{ opacity: 1, scale: 1 }}
266
+ exit={{ opacity: 0, scale: 0.98 }}
267
+ transition={{ duration: 0.2 }}
268
+ className="flex flex-col items-center text-center px-8 py-10"
269
+ >
270
+ <motion.div
271
+ initial={{ scale: 0.6, opacity: 0 }}
272
+ animate={{ scale: 1, opacity: 1 }}
273
+ transition={{ type: 'spring', stiffness: 260, damping: 16 }}
274
+ className="flex items-center justify-center w-16 h-16 rounded-full bg-amber-50 text-amber-500 mb-4"
275
+ >
276
+ <GppMaybeRoundedIcon sx={{ fontSize: 40 }} />
277
+ </motion.div>
278
+ <Typography className="text-lg font-bold text-gray-800">Skip two-factor setup?</Typography>
279
+ <Typography className="text-sm text-gray-500 mt-2 max-w-sm leading-relaxed">
280
+ Two-factor authentication adds a strong extra layer of protection. Your account stays secure even if
281
+ your password is ever compromised. It only takes a minute, and we strongly recommend setting it up
282
+ now.
283
+ </Typography>
284
+ <div className="w-full flex flex-col gap-2 mt-6">
285
+ <Button
286
+ variant="contained"
287
+ size="medium"
288
+ onClick={() => setPhase('scan')}
289
+ className="rounded-full normal-case font-medium"
290
+ data-testid="two-factor-setup-now-button"
291
+ >
292
+ Set it up now
293
+ </Button>
294
+ <Button
295
+ variant="text"
296
+ color="inherit"
297
+ size="small"
298
+ onClick={() => setPhase('skipped')}
299
+ className="text-gray-500 normal-case"
300
+ data-testid="two-factor-skip-anyway-button"
301
+ >
302
+ Skip anyway
303
+ </Button>
304
+ </div>
305
+ </motion.div>
306
+ ) : phase === 'success' ? (
307
+ <motion.div
308
+ key="success"
309
+ initial={{ opacity: 0 }}
310
+ animate={{ opacity: 1 }}
311
+ className="flex flex-col items-center text-center px-8 py-12"
312
+ >
313
+ <div className="relative flex items-center justify-center mb-5">
314
+ <motion.span
315
+ className="absolute inline-flex h-20 w-20 rounded-full bg-green-200"
316
+ initial={{ scale: 0.6, opacity: 0.7 }}
317
+ animate={{ scale: 1.6, opacity: 0 }}
318
+ transition={{ duration: 1.1, repeat: Infinity }}
319
+ />
320
+ <motion.div
321
+ initial={{ scale: 0, rotate: -20 }}
322
+ animate={{ scale: 1, rotate: 0 }}
323
+ transition={{ type: 'spring', stiffness: 260, damping: 14 }}
324
+ className="relative text-green-500"
325
+ >
326
+ <CheckCircleRoundedIcon sx={{ fontSize: 80 }} />
327
+ </motion.div>
328
+ </div>
329
+ <motion.div
330
+ initial={{ opacity: 0, y: 8 }}
331
+ animate={{ opacity: 1, y: 0 }}
332
+ transition={{ delay: 0.15 }}
333
+ >
334
+ <Typography className="text-xl font-bold text-gray-800">You&apos;re all set!</Typography>
335
+ <Typography className="text-sm text-gray-500 mt-2 max-w-xs">
336
+ Two-factor authentication is now enabled. You&apos;ll be asked for a code from your authenticator
337
+ app each time you sign in.
338
+ </Typography>
339
+ </motion.div>
340
+ <div className="flex items-center gap-2 mt-6 text-gray-400">
341
+ <CircularProgress size={14} color="inherit" />
342
+ <Typography className="text-xs">Taking you to your dashboard…</Typography>
343
+ </div>
344
+ <Button
345
+ variant="contained"
346
+ size="medium"
347
+ onClick={() => onVerified?.()}
348
+ className="rounded-full px-8 mt-6 normal-case font-medium"
349
+ data-testid="two-factor-continue-button"
350
+ >
351
+ Continue
352
+ </Button>
353
+ </motion.div>
354
+ ) : (
355
+ <motion.div
356
+ key="skipped"
357
+ initial={{ opacity: 0 }}
358
+ animate={{ opacity: 1 }}
359
+ className="flex flex-col items-center text-center px-8 py-12"
360
+ >
361
+ <motion.div
362
+ initial={{ scale: 0, rotate: -10 }}
363
+ animate={{ scale: 1, rotate: 0 }}
364
+ transition={{ type: 'spring', stiffness: 260, damping: 14 }}
365
+ className="flex items-center justify-center w-20 h-20 rounded-full bg-amber-50 text-amber-500 mb-5"
366
+ >
367
+ <WatchLaterOutlinedIcon sx={{ fontSize: 52 }} />
368
+ </motion.div>
369
+ <motion.div
370
+ initial={{ opacity: 0, y: 8 }}
371
+ animate={{ opacity: 1, y: 0 }}
372
+ transition={{ delay: 0.15 }}
373
+ >
374
+ <Typography className="text-xl font-bold text-gray-800">Maybe later</Typography>
375
+ <Typography className="text-sm text-gray-500 mt-2 max-w-xs">
376
+ No problem. We&apos;ll ask you to set up two-factor authentication again the next time you sign
377
+ in. You can secure your account anytime.
378
+ </Typography>
379
+ </motion.div>
380
+ <div className="flex items-center gap-2 mt-6 text-gray-400">
381
+ <CircularProgress size={14} color="inherit" />
382
+ <Typography className="text-xs">Taking you to your dashboard…</Typography>
383
+ </div>
384
+ <Button
385
+ variant="contained"
386
+ size="medium"
387
+ onClick={() => onSkip?.()}
388
+ className="rounded-full px-8 mt-6 normal-case font-medium"
389
+ data-testid="two-factor-skip-continue-button"
390
+ >
391
+ Continue
392
+ </Button>
393
+ </motion.div>
394
+ )}
395
+ </AnimatePresence>
396
+ </Box>
397
+ </motion.div>
398
+ </div>
399
+ </Modal>
400
+ );
401
+ };
402
+
403
+ TwoFactorSetupModal.propTypes = {
404
+ open: PropTypes.bool,
405
+ userId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
406
+ userName: PropTypes.string,
407
+ onSkip: PropTypes.func,
408
+ onVerified: PropTypes.func,
409
+ };
410
+
411
+ export default TwoFactorSetupModal;
@@ -0,0 +1,45 @@
1
+ import { showToast } from '@/common/toaster/toaster';
2
+ /**
3
+ * Simplifies and flattens a hierarchical menu structure into a linear format.
4
+ *
5
+ * @param {Object|Object[]} menuItem - The menu item or array of menu items to process.
6
+ * @param {Object} linearMenuItems - The object to store flattened menu items indexed by `navigationState`.
7
+ * @returns {void}
8
+ */
9
+
10
+ export const simplifyMenu = (menuItem, linearMenuItems) => {
11
+ if (!menuItem) return;
12
+
13
+ if (Array.isArray(menuItem)) {
14
+ menuItem.forEach((entity) => {
15
+ simplifyMenu(entity, linearMenuItems);
16
+ });
17
+ } else {
18
+ const menuItemWithoutParent = { ...menuItem };
19
+
20
+ if (menuItemWithoutParent.navigationState) {
21
+ linearMenuItems[menuItemWithoutParent.navigationState] = {
22
+ ...menuItemWithoutParent,
23
+ };
24
+ }
25
+
26
+ if (menuItemWithoutParent.featureJson) {
27
+ try {
28
+ menuItemWithoutParent.featureJson = JSON.parse(menuItem.featureJson || '');
29
+ } catch (error) {
30
+ console.error(error);
31
+ showToast({
32
+ message: `Error while parsing JSON featureJson:${error?.message}`,
33
+ type: 'error',
34
+ });
35
+ menuItemWithoutParent.featureJson = {};
36
+ }
37
+ }
38
+
39
+ if (menuItemWithoutParent.subGroups && Array.isArray(menuItemWithoutParent.subGroups)) {
40
+ menuItemWithoutParent.subGroups.forEach((entity) => {
41
+ simplifyMenu(entity, linearMenuItems);
42
+ });
43
+ }
44
+ }
45
+ };