@argusoft/medplat-app-shell 1.0.6 → 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.
- package/package.json +139 -141
- package/src/GlobalErrorBoundary.jsx +31 -0
- package/src/SilentErrorFallback.jsx +68 -0
- package/src/TrackingProviderWrapper.jsx +40 -0
- package/src/_tests_/__mocks__/MockTranslationProvider.jsx +21 -0
- package/src/_tests_/__mocks__/ckeditor.js +45 -0
- package/src/_tests_/__mocks__/fileMock.js +1 -0
- package/src/_tests_/__mocks__/useranalytics.js +5 -0
- package/src/_tests_/views/components/Dashboard/DashboardUI.test.jsx +137 -0
- package/src/_tests_/views/components/Dashboard/DashboardUIMock.js +877 -0
- package/src/_tests_/views/components/ForgotPassword/ForgotPassword.test.jsx +314 -0
- package/src/_tests_/views/components/LocationDirective/LocationDirective.test.jsx.disable +229 -0
- package/src/_tests_/views/components/LocationDirective/mockLocationDirective.js +810 -0
- package/src/_tests_/views/components/LocationType/MockLocationType.js +42259 -0
- package/src/_tests_/views/components/LocationType/addlocationtype.test.jsx.disable +276 -0
- package/src/_tests_/views/components/LocationType/editlocationtype.test.jsx.disable +262 -0
- package/src/_tests_/views/components/LocationType/locationtype.test.jsx.disable +148 -0
- package/src/_tests_/views/components/Profile/UpdateProfileModalData.js +4396 -0
- package/src/_tests_/views/components/Profile/updateprofilemodal.test.jsx +282 -0
- package/src/_tests_/views/components/SideBar/MockSideBar.js +1379 -0
- package/src/_tests_/views/components/SideBar/SideBar.test.jsx +98 -0
- package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/AddManageSystemConfig.test.jsx.disable +164 -0
- package/src/_tests_/views/components/SystemConfig/ManageSystemConfig/UpdateManageSystemConfig.test.jsx.disable +157 -0
- package/src/_tests_/views/components/SystemConfig/MockSystemConfig.js +1280 -0
- package/src/_tests_/views/components/SystemConfig/SystemConfig.test.jsx.disable +165 -0
- package/src/_tests_/views/components/login/Login.test.jsx +276 -0
- package/src/_tests_/views/components/login/MockAuthorise.js +2414 -0
- package/src/_tests_/views/components/login/ServiceResponse.js +595 -0
- package/src/_tests_/views/components/user/MockManageUser.js +7965 -0
- package/src/_tests_/views/components/user/manageuser.test.jsx.disable +989 -0
- package/src/_tests_/views/components/user/mockUsersData.js +582 -0
- package/src/assets/img/OASISLogin.png +0 -0
- package/src/assets/img/bahaarNew.png +0 -0
- package/src/assets/img/dnhdd4K.png +0 -0
- package/src/assets/img/govtofup.png +0 -0
- package/src/assets/img/sewarural4K.png +0 -0
- package/src/assets/img/techo4K.png +0 -0
- package/src/assets/img/up4K.png +0 -0
- package/src/common/HolidayList.jsx +573 -0
- package/src/common/MalaciaousInputUtil.js +23 -0
- package/src/common/SafeHtml.jsx +17 -0
- package/src/common/VersionManager.jsx +109 -0
- package/src/common/constants/PerformanceDashboard.js +514 -0
- package/src/common/constants/app.constant.js +781 -0
- package/src/common/constants/cccVerificationConstants.js +18 -0
- package/src/common/constants/fhsrConstant.js +33 -0
- package/src/common/constants/gvk-verification.constant.js +76 -0
- package/src/common/constants/search.constant.js +23 -0
- package/src/common/constants/teleconsulatationConstant.jsx +1339 -0
- package/src/common/directives/SearchTemplate.jsx +784 -0
- package/src/common/directives/SearchTemplate.scss +14 -0
- package/src/common/dynamicView/DynamicView.jsx +353 -0
- package/src/common/dynamicView/InputFieldComponent.jsx +1501 -0
- package/src/common/dynamicView/InputViewComponent.jsx +298 -0
- package/src/common/dynamicView/InputViewComponent.scss +15 -0
- package/src/common/env.js +5 -0
- package/src/common/filters/locationNameFilter.js +26 -0
- package/src/common/fontAwesomeIcons/FontAwesomeIcons.jsx +27 -0
- package/src/common/fontAwesomeIcons/FontAwesomeIconsNames.js +1968 -0
- package/src/common/fontPreferences/fontSizeProvider.jsx +34 -0
- package/src/common/fontPreferences/fontSizeSelector.jsx +116 -0
- package/src/common/getAssignedFeature/getAssignedFeature.js +32 -0
- package/src/common/interceptors/AxiosInterceptor.js +216 -0
- package/src/common/languageTranslator/TranslationContext.js +5 -0
- package/src/common/languageTranslator/TranslationProvider.jsx +24 -0
- package/src/common/languageTranslator/i18n.js +49 -0
- package/src/common/services/AuthenticateService.js +116 -0
- package/src/common/services/DownloadFile.js +35 -0
- package/src/common/services/ForgotPassword.js +18 -0
- package/src/common/services/FormConfiguratorService.js +195 -0
- package/src/common/services/GlobalApis.js +84 -0
- package/src/common/services/InterceptorNavigationService.js +17 -0
- package/src/common/services/LocationService.js +65 -0
- package/src/common/services/LocationType.js +11 -0
- package/src/common/services/QueryBuilder.js +36 -0
- package/src/common/services/Roles.js +28 -0
- package/src/common/services/SyncWithServer.js +15 -0
- package/src/common/services/SystemConfig.js +15 -0
- package/src/common/services/TranslationService.js +70 -0
- package/src/common/services/TwoFactorService.js +7 -0
- package/src/common/services/Users.js +91 -0
- package/src/common/services/Webtasks.js +27 -0
- package/src/common/services/util/Convert-pad-data-to-API-format.jsx +167 -0
- package/src/common/services/util/Convert-to-UI-format.jsx +82 -0
- package/src/common/services/util/EmptyPrescriptionPadData.jsx +11 -0
- package/src/common/services/util/GeneralUtil.js +456 -0
- package/src/common/services/util/Prescription-pad-util.js +339 -0
- package/src/common/services/util/PrescriptionPadData.js +67 -0
- package/src/common/services/util/PrescriptionpadCommonUtil.js +96 -0
- package/src/common/services/util/ReportFieldUtil.jsx +398 -0
- package/src/common/services/util/WebSocketContext.jsx +261 -0
- package/src/common/syncWithServer/SyncWithServerDialog.jsx +170 -0
- package/src/common/syncWithServer/SyncWithServerDialogSkeleton.jsx +67 -0
- package/src/common/tests/CustomWrapper.jsx +49 -0
- package/src/common/tests/TranslationWrapper.jsx +38 -0
- package/src/common/themeProvider/ColorInputs.jsx +97 -0
- package/src/common/themeProvider/EditableColorInput.jsx +128 -0
- package/src/common/themeProvider/ThemeEditor.jsx +319 -0
- package/src/common/themeProvider/ThemeProvider.jsx +210 -0
- package/src/common/themeProvider/themeConfig.js +558 -0
- package/src/common/toaster/toaster.jsx +30 -0
- package/src/firebaseConfig.js +24 -0
- package/src/global.scss +221 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/useAESEncryption.js +56 -0
- package/src/hooks/useCaching.js +43 -0
- package/src/hooks/useDebounce.js +34 -0
- package/src/hooks/useDebounceFn.js +50 -0
- package/src/hooks/useDownloadPdf.js +358 -0
- package/src/hooks/useDownloadXlsx.js +55 -0
- package/src/hooks/useListValueFieldValues.js +30 -0
- package/src/hooks/useLocationHierarchies.js +63 -0
- package/src/hooks/useLocationHierarchyTranslate.js +16 -0
- package/src/hooks/useOnline.js +27 -0
- package/src/hooks/usePagination.js +63 -0
- package/src/hooks/useRefreshToken.js +87 -0
- package/src/hooks/useScript.js +25 -0
- package/src/hooks/useStopwatch.js +75 -0
- package/src/hooks/useTrackEvent.js +22 -0
- package/src/hooks/useWebAudioRecorder.js +115 -0
- package/src/layout/LoaderComponet.jsx +22 -0
- package/src/layout/LoaderContext.jsx +29 -0
- package/src/layout/mainLayout/AdaptiveZoom.jsx +27 -0
- package/src/layout/mainLayout/Chatbot.jsx +243 -0
- package/src/layout/mainLayout/Layout.jsx +445 -0
- package/src/layout/mainLayout/Profile/UpdateProfileModal.jsx +684 -0
- package/src/layout/mainLayout/header/LogoutModal.jsx +131 -0
- package/src/layout/mainLayout/header/Navbar.jsx +1677 -0
- package/src/layout/mainLayout/header/Navbar.scss +4 -0
- package/src/layout/mainLayout/header/index.js +0 -0
- package/src/layout/mainLayout/sidebar/SideBar.jsx +1402 -0
- package/src/layout/mainLayout/sidebar/Sidebar.css +159 -0
- package/src/layout/mainLayout/sidebar/index.js +0 -0
- package/src/logo.svg +1 -0
- package/src/reportWebVitals.js +13 -0
- package/src/setupFirebaseMessaging.js +28 -0
- package/src/setupTests.js +8 -0
- package/src/store/actions/AuthenticationActions.js +0 -0
- package/src/store/actions/ReportsActions.js +0 -0
- package/src/store/actions/TranslationAction.js +0 -0
- package/src/store/index.js +8 -0
- package/src/store/reducer.js +46 -0
- package/src/store/reducers/AuthenticationReducer.js +50 -0
- package/src/store/reducers/CalendarEventReducer.js +41 -0
- package/src/store/reducers/ConditionClipboardReducer.js +45 -0
- package/src/store/reducers/FeatureReducer.js +27 -0
- package/src/store/reducers/FormConfiguratorReducer.js +38 -0
- package/src/store/reducers/LoadingReducer.js +20 -0
- package/src/store/reducers/MembersAuthenticationReducer.js +28 -0
- package/src/store/reducers/PrescriptionPadReducer.js +329 -0
- package/src/store/reducers/QuestionaireReducer.js +29 -0
- package/src/store/reducers/ReportsReducer.js +24 -0
- package/src/store/reducers/SkeletonReducer.js +20 -0
- package/src/store/reducers/ThemeReducer.js +106 -0
- package/src/store/reducers/TranslationReducer.js +126 -0
- package/src/store/reducers/dashboardEditorSlice.js +77 -0
- package/src/store/reducers/districtHealthDashboardSlice.js +58 -0
- package/src/store/reducers/immunizationSlice.js +234 -0
- package/src/store/slices/dashboardPagesSlice.js +51 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/FormConstants.js +2629 -0
- package/src/utils/GujaratTopoChart.jsx +483 -0
- package/src/utils/UUIDgenerator.js +8 -0
- package/src/utils/appointment-utils/appointment-utils.js +123 -0
- package/src/utils/feature.js +42 -0
- package/src/utils/getThemeColor.js +12 -0
- package/src/utils/localStorageHelper.js +11 -0
- package/src/utils/notifications/enable-push-notifications.js +27 -0
- package/src/utils/resolveAppliedStyle.js +11 -0
- package/src/utils/themeConfigs.js +1483 -0
- package/src/views/custom-components/.gitkeep +0 -0
- package/src/views/custom-components/AgIconButton/RIf.jsx +14 -0
- package/src/views/custom-components/AgIconButton/button.jsx +108 -0
- package/src/views/custom-components/AgIconButton/waterDrop.jsx +95 -0
- package/src/views/custom-components/AgIconButton/waterDrop.scss +37 -0
- package/src/views/custom-components/AlertPlaceholder.jsx +32 -0
- package/src/views/custom-components/AllFaIconsSelector.jsx +56 -0
- package/src/views/custom-components/CkEditor/CkEditor.js +102 -0
- package/src/views/custom-components/CustomAccordion.jsx +72 -0
- package/src/views/custom-components/CustomActionIcons.jsx +118 -0
- package/src/views/custom-components/CustomAutoComplete.jsx +188 -0
- package/src/views/custom-components/CustomCheckBox.jsx +60 -0
- package/src/views/custom-components/CustomConfirmationModal.jsx +118 -0
- package/src/views/custom-components/CustomCountrySelect.jsx +129 -0
- package/src/views/custom-components/CustomDatePicker.jsx +122 -0
- package/src/views/custom-components/CustomDropdown.jsx +191 -0
- package/src/views/custom-components/CustomFileUpload.jsx +387 -0
- package/src/views/custom-components/CustomFullCalendar.jsx +514 -0
- package/src/views/custom-components/CustomInfiniteScroll.jsx +126 -0
- package/src/views/custom-components/CustomRadioComponent.jsx +65 -0
- package/src/views/custom-components/CustomStatsComponent.jsx +114 -0
- package/src/views/custom-components/CustomSvgUpload.jsx +170 -0
- package/src/views/custom-components/CustomSwitch.jsx +37 -0
- package/src/views/custom-components/CustomTabPanel.jsx +19 -0
- package/src/views/custom-components/CustomTextArea.jsx +62 -0
- package/src/views/custom-components/CustomTextArea.scss +27 -0
- package/src/views/custom-components/CustomTextField.jsx +116 -0
- package/src/views/custom-components/CustomToggleSwitch.jsx +138 -0
- package/src/views/custom-components/CustomTooltip.jsx +51 -0
- package/src/views/custom-components/CustomZoomImage.jsx +134 -0
- package/src/views/custom-components/CustomizedTable/CustomizedTableV2.jsx +1407 -0
- package/src/views/custom-components/CustomizedTable/VirtualizeTableBody.jsx +295 -0
- package/src/views/custom-components/CustomizedTable/helper.jsx +159 -0
- package/src/views/custom-components/CustomizedTable.jsx +532 -0
- package/src/views/custom-components/EditInputField.jsx +174 -0
- package/src/views/custom-components/FieldDescription.jsx +22 -0
- package/src/views/custom-components/FileDisplayComponent.jsx +138 -0
- package/src/views/custom-components/FormItem.jsx +53 -0
- package/src/views/custom-components/GenericChart.jsx +80 -0
- package/src/views/custom-components/InfoBadge.jsx +60 -0
- package/src/views/custom-components/PostgresEditor.jsx +801 -0
- package/src/views/custom-components/ResizableEditAutocompleteField.jsx +249 -0
- package/src/views/custom-components/ResizableEditInputField.jsx +215 -0
- package/src/views/custom-components/ResizeableEditSelectField.jsx +197 -0
- package/src/views/custom-components/SideOverlay.jsx +113 -0
- package/src/views/custom-components/SideOverlay.scss +42 -0
- package/src/views/custom-components/calendar.scss +571 -0
- package/src/views/feature-components/.gitkeep +0 -0
- package/src/views/feature-components/Dashboard/DashboardUI.jsx +1043 -0
- package/src/views/feature-components/Dashboard/DhnddModal/AshaDataQualityVerificationModal.jsx +278 -0
- package/src/views/feature-components/Dashboard/PinFeatureModal.jsx +143 -0
- package/src/views/feature-components/Dashboard/QuickLinks.jsx +163 -0
- package/src/views/feature-components/Dashboard/Taskbar.jsx +56 -0
- package/src/views/feature-components/Dashboard/WebtasksFilterForm.jsx +109 -0
- package/src/views/feature-components/Dashboard/WidgetCard.jsx +161 -0
- package/src/views/feature-components/Dashboard/actionModal.jsx +263 -0
- package/src/views/feature-components/Dashboard/ekavachModal/HealthWorkerIncorrectDetailsModal.jsx +332 -0
- package/src/views/feature-components/Dashboard/ekavachModal/MoMaternalDeathVerifcationModal.jsx +275 -0
- package/src/views/feature-components/Dashboard/ekavachModal/MoVerficationForChildScreeningMoadal.jsx +566 -0
- package/src/views/feature-components/FeatureUsageAnalytics/FeatureUsageAnalytics.jsx +989 -0
- package/src/views/feature-components/Features/NewServerManagement.jsx +217 -0
- package/src/views/feature-components/Features/ServerManagement.scss +120 -0
- package/src/views/feature-components/ForgotPassword/ForgotPassword.jsx +226 -0
- package/src/views/feature-components/LocationDirective/LocationDirective.jsx +992 -0
- package/src/views/feature-components/LocationDirective/LocationDirectiveV2.jsx +909 -0
- package/src/views/feature-components/NotFound.jsx +66 -0
- package/src/views/feature-components/Onboarding/Onboarding.jsx +1400 -0
- package/src/views/feature-components/Skeletons.js +115 -0
- package/src/views/feature-components/Unauthorized.jsx +48 -0
- package/src/views/feature-components/VerifyRoute.jsx +88 -0
- package/src/views/feature-components/YearlyRecap/YearlyRecap.jsx +357 -0
- package/src/views/feature-components/YearlyRecap/components/RecapSlide.jsx +183 -0
- package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationContext.js +5 -0
- package/src/views/feature-components/YearlyRecap/languageTranslator/TranslationProvider.jsx +26 -0
- package/src/views/feature-components/YearlyRecap/languageTranslator/i18n.js +46 -0
- package/src/views/feature-components/YearlyRecap/languageTranslator/translations.json +167 -0
- package/src/views/feature-components/YearlyRecap/slides/IntroSlide.jsx +233 -0
- package/src/views/feature-components/YearlyRecap/slides/MaternalHealthSlide.jsx +146 -0
- package/src/views/feature-components/YearlyRecap/slides/MetricSlide.jsx +227 -0
- package/src/views/feature-components/YearlyRecap/slides/OutroSlide.jsx +701 -0
- package/src/views/feature-components/YearlyRecap/slides/ReachSlide.jsx +273 -0
- package/src/views/feature-components/login/Login.jsx +840 -0
- package/src/views/feature-components/login/Login.scss +154 -0
- package/src/views/feature-components/login/LoginConfigurator.jsx +1149 -0
- package/src/views/feature-components/login/TwoFactorSetupModal.jsx +411 -0
- package/src/views/feature-components/login/simplifyMenu.js +45 -0
- package/src/views/feature-components/system-config/ManageSystemConfigs.jsx +284 -0
- package/src/views/feature-components/system-config/SystemConfig.jsx +299 -0
- package/src/views/feature-components/users/ChangePasswordModal.jsx +243 -0
- package/src/views/feature-components/users/PasswordField.jsx +56 -0
- package/dist/index.css +0 -1
- package/dist/index.js +0 -32001
- 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'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'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'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'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
|
+
};
|