@argusoft/medplat-app-shell 1.0.6 → 1.0.8
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/store/slices/dashboardSlice.js +14 -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,1149 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useForm, useFieldArray } from 'react-hook-form';
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
Button,
|
|
6
|
+
Card,
|
|
7
|
+
TextField,
|
|
8
|
+
Typography,
|
|
9
|
+
Switch,
|
|
10
|
+
FormControlLabel,
|
|
11
|
+
Select,
|
|
12
|
+
MenuItem,
|
|
13
|
+
InputLabel,
|
|
14
|
+
FormControl,
|
|
15
|
+
IconButton,
|
|
16
|
+
Divider,
|
|
17
|
+
Tabs,
|
|
18
|
+
Tab,
|
|
19
|
+
Paper,
|
|
20
|
+
Grid,
|
|
21
|
+
Menu
|
|
22
|
+
} from '@mui/material';
|
|
23
|
+
import { api } from '@/common/interceptors/AxiosInterceptor';
|
|
24
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
25
|
+
import {
|
|
26
|
+
faPlus,
|
|
27
|
+
faTrash,
|
|
28
|
+
faSave,
|
|
29
|
+
faUndo,
|
|
30
|
+
faCopy,
|
|
31
|
+
faEye,
|
|
32
|
+
faMobileAlt,
|
|
33
|
+
faDesktop,
|
|
34
|
+
faDownload,
|
|
35
|
+
faPerson,
|
|
36
|
+
faBookMedical,
|
|
37
|
+
faPassport,
|
|
38
|
+
faPalette,
|
|
39
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
40
|
+
import { showToast } from '@/common/toaster/toaster';
|
|
41
|
+
import { imageMap } from './Login';
|
|
42
|
+
import { bahaarJSON, upJSON, phmpJSON, sewaruralJSON, oasisJSON, techoJSON, defaultJSON } from '@/common/constants/app.constant';
|
|
43
|
+
|
|
44
|
+
const PRESETS = {
|
|
45
|
+
default: defaultJSON,
|
|
46
|
+
bahaar: bahaarJSON,
|
|
47
|
+
ekavach: upJSON,
|
|
48
|
+
dnhdd: phmpJSON,
|
|
49
|
+
sewarural: sewaruralJSON,
|
|
50
|
+
oasis: oasisJSON,
|
|
51
|
+
gujarat: techoJSON
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const PRESET_LABELS = {
|
|
55
|
+
custom: 'Custom Config',
|
|
56
|
+
default: 'Default (Medplat)',
|
|
57
|
+
bahaar: 'Bahaar (Sath)',
|
|
58
|
+
ekavach: 'UP (eKavach)',
|
|
59
|
+
dnhdd: 'PHMP (DNH & DD)',
|
|
60
|
+
sewarural: 'Sewa Rural',
|
|
61
|
+
oasis: 'OASIS (PHFI/LSHTM)',
|
|
62
|
+
gujarat: 'TeCHO+ (Gujarat)'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const iconMap = {
|
|
66
|
+
PERSON: faPerson,
|
|
67
|
+
MEDICAL: faBookMedical,
|
|
68
|
+
PASSPORT: faPassport,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default function LoginConfigurator() {
|
|
72
|
+
const [selectedPresetKey, setSelectedPresetKey] = useState('gujarat');
|
|
73
|
+
const [activeTab, setActiveTab] = useState(0); // 0 = Form Editor, 1 = Raw JSON
|
|
74
|
+
const [previewDevice, setPreviewDevice] = useState('desktop'); // desktop or mobile
|
|
75
|
+
const [presetMenuAnchor, setPresetMenuAnchor] = useState(null);
|
|
76
|
+
|
|
77
|
+
const containerRef = React.useRef(null);
|
|
78
|
+
const [scale, setScale] = useState(1);
|
|
79
|
+
const isResettingRef = React.useRef(false);
|
|
80
|
+
|
|
81
|
+
const targetWidth = previewDevice === 'mobile' ? 360 : 800;
|
|
82
|
+
const targetHeight = previewDevice === 'mobile' ? 640 : 500;
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!containerRef.current) return;
|
|
86
|
+
const updateScale = () => {
|
|
87
|
+
const parent = containerRef.current.parentElement;
|
|
88
|
+
if (!parent) return;
|
|
89
|
+
// Get padding-adjusted parent height and width
|
|
90
|
+
const parentWidth = parent.clientWidth - 32;
|
|
91
|
+
const parentHeight = parent.clientHeight - 32;
|
|
92
|
+
|
|
93
|
+
const widthScale = parentWidth / targetWidth;
|
|
94
|
+
const heightScale = parentHeight / targetHeight;
|
|
95
|
+
const scaleFactor = Math.max(0.2, Math.min(widthScale, heightScale, 1)); // max scale = 1, min scale = 0.2
|
|
96
|
+
setScale(scaleFactor);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
updateScale();
|
|
100
|
+
window.addEventListener('resize', updateScale);
|
|
101
|
+
|
|
102
|
+
// ResizeObserver for highly responsive updates when layout layout shifts
|
|
103
|
+
const observer = new ResizeObserver(() => {
|
|
104
|
+
updateScale();
|
|
105
|
+
});
|
|
106
|
+
if (containerRef.current.parentElement) {
|
|
107
|
+
observer.observe(containerRef.current.parentElement);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return () => {
|
|
111
|
+
window.removeEventListener('resize', updateScale);
|
|
112
|
+
observer.disconnect();
|
|
113
|
+
};
|
|
114
|
+
}, [previewDevice, targetWidth, targetHeight]);
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
const { register, control, handleSubmit, watch, reset, getValues, setValue } = useForm({
|
|
118
|
+
defaultValues: PRESETS.gujarat
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const { fields: sectionFields, append: appendSection, remove: removeSection } = useFieldArray({
|
|
122
|
+
control,
|
|
123
|
+
name: "footerSections"
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const { fields: bottomLogoFields, append: appendBottomLogo, remove: removeBottomLogo } = useFieldArray({
|
|
127
|
+
control,
|
|
128
|
+
name: "bottomLogos"
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const currentValues = watch();
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (isResettingRef.current) {
|
|
135
|
+
isResettingRef.current = false;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (selectedPresetKey !== 'custom' && PRESETS[selectedPresetKey]) {
|
|
139
|
+
const isDifferent = JSON.stringify(currentValues) !== JSON.stringify(PRESETS[selectedPresetKey]);
|
|
140
|
+
if (isDifferent) {
|
|
141
|
+
setSelectedPresetKey('custom');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, [currentValues, selectedPresetKey]);
|
|
145
|
+
|
|
146
|
+
// Load preset helper
|
|
147
|
+
const handleLoadPreset = (key) => {
|
|
148
|
+
setSelectedPresetKey(key);
|
|
149
|
+
if (key !== 'custom') {
|
|
150
|
+
isResettingRef.current = true;
|
|
151
|
+
reset(PRESETS[key] || {});
|
|
152
|
+
showToast({ message: `Loaded ${PRESET_LABELS[key]} preset`, type: 'info' });
|
|
153
|
+
} else {
|
|
154
|
+
showToast({ message: `Switched to Custom preset mode`, type: 'info' });
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const handleOpenPresetMenu = (event) => {
|
|
159
|
+
setPresetMenuAnchor(event.currentTarget);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const handleClosePresetMenu = () => {
|
|
163
|
+
setPresetMenuAnchor(null);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const handleSelectPresetToLoad = (presetKey) => {
|
|
167
|
+
isResettingRef.current = true;
|
|
168
|
+
reset(PRESETS[presetKey] || {});
|
|
169
|
+
showToast({ message: `Loaded values from ${PRESET_LABELS[presetKey]} into Custom Config`, type: 'success' });
|
|
170
|
+
handleClosePresetMenu();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// Load config from API on mount
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const fetchConfig = async () => {
|
|
176
|
+
try {
|
|
177
|
+
const response = await api.get('login/preference');
|
|
178
|
+
if (response?.data) {
|
|
179
|
+
let data = response.data;
|
|
180
|
+
// Handle cases where response might be wrapped in SystemConfiguration or custom DTO
|
|
181
|
+
if (data && typeof data === 'object') {
|
|
182
|
+
if ('keyValue' in data) {
|
|
183
|
+
data = data.keyValue;
|
|
184
|
+
} else if ('value' in data) {
|
|
185
|
+
data = data.value;
|
|
186
|
+
} else if ('preference' in data) {
|
|
187
|
+
data = data.preference;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (typeof data === 'string') {
|
|
191
|
+
try {
|
|
192
|
+
data = JSON.parse(data);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error('Error parsing config string from API response', err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (data && typeof data === 'object') {
|
|
198
|
+
isResettingRef.current = true;
|
|
199
|
+
reset(data);
|
|
200
|
+
showToast({ message: 'Loaded configuration from server', type: 'success' });
|
|
201
|
+
|
|
202
|
+
// Check if the loaded data matches any of the available presets
|
|
203
|
+
const matchedKey = Object.keys(PRESETS).find(key => {
|
|
204
|
+
return JSON.stringify(PRESETS[key]) === JSON.stringify(data);
|
|
205
|
+
});
|
|
206
|
+
setSelectedPresetKey(matchedKey || 'custom');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw new Error('Invalid or empty config from server');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Failed to fetch login preference from API', error);
|
|
213
|
+
|
|
214
|
+
// Fallback to local storage if API fails or returns empty
|
|
215
|
+
const env = import.meta.env.VITE_APP_IMPLEMENTATION || 'gujarat';
|
|
216
|
+
const localKey = env === 'gujaratStaging' ? 'gujarat' : env;
|
|
217
|
+
const savedCustom = localStorage.getItem('medplat_login_config_custom');
|
|
218
|
+
const savedEnv = localStorage.getItem(`medplat_login_config_${localKey}`);
|
|
219
|
+
const saved = savedCustom || savedEnv;
|
|
220
|
+
if (saved) {
|
|
221
|
+
try {
|
|
222
|
+
const parsed = JSON.parse(saved);
|
|
223
|
+
isResettingRef.current = true;
|
|
224
|
+
reset(parsed);
|
|
225
|
+
|
|
226
|
+
const matchedKey = Object.keys(PRESETS).find(key => {
|
|
227
|
+
return JSON.stringify(PRESETS[key]) === JSON.stringify(parsed);
|
|
228
|
+
});
|
|
229
|
+
setSelectedPresetKey(matchedKey || 'custom');
|
|
230
|
+
showToast({ message: 'Loaded local configuration backup', type: 'info' });
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.error("Error reading initial local storage config", e);
|
|
233
|
+
}
|
|
234
|
+
} else if (PRESETS[localKey]) {
|
|
235
|
+
isResettingRef.current = true;
|
|
236
|
+
reset(PRESETS[localKey]);
|
|
237
|
+
setSelectedPresetKey(localKey);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
fetchConfig();
|
|
243
|
+
}, [reset]);
|
|
244
|
+
|
|
245
|
+
// Save changes helper
|
|
246
|
+
const onSave = async (data) => {
|
|
247
|
+
try {
|
|
248
|
+
await api.post('login/set-preference', JSON.stringify(data));
|
|
249
|
+
localStorage.setItem(`medplat_login_config_${selectedPresetKey}`, JSON.stringify(data));
|
|
250
|
+
showToast({ message: 'Configuration saved successfully to server!', type: 'success' });
|
|
251
|
+
} catch (e) {
|
|
252
|
+
console.error('Failed to save to server', e);
|
|
253
|
+
localStorage.setItem(`medplat_login_config_${selectedPresetKey}`, JSON.stringify(data));
|
|
254
|
+
showToast({ message: 'Failed to save to server. Saved locally as backup.', type: 'warning' });
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Copy to clipboard helper
|
|
259
|
+
const handleCopyJSON = () => {
|
|
260
|
+
navigator.clipboard.writeText(JSON.stringify(currentValues, null, 2));
|
|
261
|
+
showToast({ message: 'JSON copied to clipboard', type: 'success' });
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Reset to static constant default
|
|
265
|
+
const handleResetToDefault = async () => {
|
|
266
|
+
const defaultData = PRESETS[selectedPresetKey] || PRESETS.gujarat;
|
|
267
|
+
isResettingRef.current = true;
|
|
268
|
+
reset(defaultData);
|
|
269
|
+
localStorage.removeItem(`medplat_login_config_${selectedPresetKey}`);
|
|
270
|
+
try {
|
|
271
|
+
await api.post('login/set-preference', JSON.stringify(defaultData));
|
|
272
|
+
showToast({ message: `Reset ${PRESET_LABELS[selectedPresetKey] || 'Custom'} to initial defaults on server`, type: 'info' });
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.error(e);
|
|
275
|
+
showToast({ message: `Reset locally. Failed to update server defaults.`, type: 'warning' });
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Helper to add sub-items
|
|
280
|
+
const addSubItem = (sectionIndex, fieldType) => {
|
|
281
|
+
const sections = getValues('footerSections');
|
|
282
|
+
const targetSection = sections[sectionIndex];
|
|
283
|
+
if (fieldType === 'buttons') {
|
|
284
|
+
const current = targetSection.buttons || [];
|
|
285
|
+
setValue(`footerSections.${sectionIndex}.buttons`, [...current, { icon: null, text: 'New Button', href: '#' }]);
|
|
286
|
+
} else if (fieldType === 'logos') {
|
|
287
|
+
const current = targetSection.logos || [];
|
|
288
|
+
setValue(`footerSections.${sectionIndex}.logos`, [...current, { image: 'medplat-highres.png', href: '#', alt: 'Logo' }]);
|
|
289
|
+
} else if (fieldType === 'logosWithText') {
|
|
290
|
+
const current = targetSection.items || [];
|
|
291
|
+
setValue(`footerSections.${sectionIndex}.items`, [...current, { image: 'medplat-highres.png', href: '#', alt: 'Logo', text: 'Supported text' }]);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// Helper to remove sub-items
|
|
296
|
+
const removeSubItem = (sectionIndex, fieldType, itemIndex) => {
|
|
297
|
+
const sections = getValues('footerSections');
|
|
298
|
+
const targetSection = sections[sectionIndex];
|
|
299
|
+
if (fieldType === 'buttons') {
|
|
300
|
+
const current = targetSection.buttons || [];
|
|
301
|
+
setValue(`footerSections.${sectionIndex}.buttons`, current.filter((_, i) => i !== itemIndex));
|
|
302
|
+
} else if (fieldType === 'logos') {
|
|
303
|
+
const current = targetSection.logos || [];
|
|
304
|
+
setValue(`footerSections.${sectionIndex}.logos`, current.filter((_, i) => i !== itemIndex));
|
|
305
|
+
} else if (fieldType === 'logosWithText') {
|
|
306
|
+
const current = targetSection.items || [];
|
|
307
|
+
setValue(`footerSections.${sectionIndex}.items`, current.filter((_, i) => i !== itemIndex));
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<Box className="min-h-screen bg-slate-50 p-6 font-sans">
|
|
313
|
+
{/* Header section */}
|
|
314
|
+
<Paper elevation={0} className="mb-6 p-6 rounded-2xl bg-gradient-to-r from-slate-900 to-indigo-950 text-white flex flex-col md:flex-row md:items-center justify-between gap-4 shadow-sm border border-slate-800">
|
|
315
|
+
<Box>
|
|
316
|
+
<Typography variant="h4" className="font-extrabold tracking-tight flex items-center gap-3">
|
|
317
|
+
<FontAwesomeIcon icon={faPalette} className="text-indigo-400" />
|
|
318
|
+
Login Configurator
|
|
319
|
+
</Typography>
|
|
320
|
+
<Typography variant="body2" className="text-slate-300 mt-1">
|
|
321
|
+
Customize the look, buttons, logos, and footer links of your portal login screen.
|
|
322
|
+
</Typography>
|
|
323
|
+
</Box>
|
|
324
|
+
<Box className="flex flex-wrap items-center gap-3">
|
|
325
|
+
<FormControl size="small" className="min-w-[180px] rounded-lg">
|
|
326
|
+
<InputLabel id="preset-select-label" style={{ color: 'white' }}>Preset Template</InputLabel>
|
|
327
|
+
<Select
|
|
328
|
+
labelId="preset-select-label"
|
|
329
|
+
value={selectedPresetKey}
|
|
330
|
+
label="Preset Template"
|
|
331
|
+
onChange={(e) => handleLoadPreset(e.target.value)}
|
|
332
|
+
style={{ color: 'white' }}
|
|
333
|
+
>
|
|
334
|
+
{Object.entries(PRESET_LABELS).map(([key, label]) => (
|
|
335
|
+
<MenuItem key={key} value={key}>{label}</MenuItem>
|
|
336
|
+
))}
|
|
337
|
+
</Select>
|
|
338
|
+
</FormControl>
|
|
339
|
+
{selectedPresetKey === 'custom' && (
|
|
340
|
+
<>
|
|
341
|
+
<Button
|
|
342
|
+
variant="outlined"
|
|
343
|
+
onClick={handleOpenPresetMenu}
|
|
344
|
+
className="border-indigo-400 hover:border-indigo-300 text-indigo-200 normal-case rounded-xl font-medium"
|
|
345
|
+
startIcon={<FontAwesomeIcon icon={faDownload} />}
|
|
346
|
+
>
|
|
347
|
+
Load Preset
|
|
348
|
+
</Button>
|
|
349
|
+
<Menu
|
|
350
|
+
anchorEl={presetMenuAnchor}
|
|
351
|
+
open={Boolean(presetMenuAnchor)}
|
|
352
|
+
onClose={handleClosePresetMenu}
|
|
353
|
+
>
|
|
354
|
+
{Object.entries(PRESET_LABELS)
|
|
355
|
+
.filter(([key]) => key !== 'custom')
|
|
356
|
+
.map(([key, label]) => (
|
|
357
|
+
<MenuItem key={key} onClick={() => handleSelectPresetToLoad(key)}>
|
|
358
|
+
{label}
|
|
359
|
+
</MenuItem>
|
|
360
|
+
))}
|
|
361
|
+
</Menu>
|
|
362
|
+
</>
|
|
363
|
+
)}
|
|
364
|
+
<Button
|
|
365
|
+
variant="outlined"
|
|
366
|
+
onClick={handleResetToDefault}
|
|
367
|
+
className="border-slate-500 hover:border-slate-300 text-slate-200 normal-case rounded-xl font-medium"
|
|
368
|
+
startIcon={<FontAwesomeIcon icon={faUndo} />}
|
|
369
|
+
>
|
|
370
|
+
Reset Default
|
|
371
|
+
</Button>
|
|
372
|
+
<Button
|
|
373
|
+
variant="contained"
|
|
374
|
+
onClick={handleSubmit(onSave)}
|
|
375
|
+
className="bg-indigo-600 hover:bg-indigo-500 text-white normal-case rounded-xl font-semibold shadow-md"
|
|
376
|
+
startIcon={<FontAwesomeIcon icon={faSave} />}
|
|
377
|
+
>
|
|
378
|
+
Save Changes
|
|
379
|
+
</Button>
|
|
380
|
+
</Box>
|
|
381
|
+
</Paper>
|
|
382
|
+
|
|
383
|
+
{/* Main Grid split editor & preview */}
|
|
384
|
+
<Grid container spacing={4}>
|
|
385
|
+
{/* Left column editor */}
|
|
386
|
+
<Grid item xs={12} lg={7}>
|
|
387
|
+
<Card className="rounded-2xl shadow-sm border border-slate-200 h-[calc(100vh-190px)] flex flex-col overflow-hidden">
|
|
388
|
+
<Box className="border-b border-slate-200 bg-white flex justify-between items-center px-4">
|
|
389
|
+
<Tabs value={activeTab} onChange={(e, val) => setActiveTab(val)}>
|
|
390
|
+
<Tab label="Form Editor" className="font-semibold text-slate-700 normal-case" />
|
|
391
|
+
<Tab label="Raw JSON View" className="font-semibold text-slate-700 normal-case" />
|
|
392
|
+
</Tabs>
|
|
393
|
+
{activeTab === 1 && (
|
|
394
|
+
<IconButton onClick={handleCopyJSON} color="primary" size="small" title="Copy JSON">
|
|
395
|
+
<FontAwesomeIcon icon={faCopy} />
|
|
396
|
+
</IconButton>
|
|
397
|
+
)}
|
|
398
|
+
</Box>
|
|
399
|
+
|
|
400
|
+
<Box className="flex-1 overflow-y-auto p-6 bg-slate-50/50">
|
|
401
|
+
{activeTab === 0 ? (
|
|
402
|
+
<form className="space-y-6">
|
|
403
|
+
{/* General Config Section */}
|
|
404
|
+
<Paper className="p-5 rounded-xl border border-slate-200/80 shadow-none space-y-4">
|
|
405
|
+
<Typography className="text-slate-800 font-bold text-lg border-b pb-2 flex items-center gap-2">
|
|
406
|
+
General Settings
|
|
407
|
+
</Typography>
|
|
408
|
+
<Grid container spacing={2}>
|
|
409
|
+
<Grid item xs={12} sm={6}>
|
|
410
|
+
<FormControlLabel
|
|
411
|
+
control={
|
|
412
|
+
<Switch
|
|
413
|
+
checked={!!currentValues.centerLogin}
|
|
414
|
+
onChange={(e) => setValue('centerLogin', e.target.checked)}
|
|
415
|
+
color="primary"
|
|
416
|
+
/>
|
|
417
|
+
}
|
|
418
|
+
label="Center Login Card"
|
|
419
|
+
/>
|
|
420
|
+
<Typography variant="caption" className="block text-slate-400 -mt-1">
|
|
421
|
+
If enabled, centers the login box. Otherwise, uses a split layout.
|
|
422
|
+
</Typography>
|
|
423
|
+
</Grid>
|
|
424
|
+
<Grid item xs={12} sm={6}>
|
|
425
|
+
<FormControlLabel
|
|
426
|
+
control={
|
|
427
|
+
<Switch
|
|
428
|
+
checked={!!currentValues.isDownloadLink}
|
|
429
|
+
onChange={(e) => setValue('isDownloadLink', e.target.checked)}
|
|
430
|
+
color="primary"
|
|
431
|
+
/>
|
|
432
|
+
}
|
|
433
|
+
label="Show APK Download"
|
|
434
|
+
/>
|
|
435
|
+
<Typography variant="caption" className="block text-slate-400 -mt-1">
|
|
436
|
+
Show the link to download the mobile application.
|
|
437
|
+
</Typography>
|
|
438
|
+
</Grid>
|
|
439
|
+
<Grid item xs={12} sm={6}>
|
|
440
|
+
<FormControlLabel
|
|
441
|
+
control={
|
|
442
|
+
<Switch
|
|
443
|
+
checked={!!currentValues.showLogoDivider}
|
|
444
|
+
onChange={(e) => setValue('showLogoDivider', e.target.checked)}
|
|
445
|
+
color="primary"
|
|
446
|
+
/>
|
|
447
|
+
}
|
|
448
|
+
label="Show Logo Vertical Divider"
|
|
449
|
+
/>
|
|
450
|
+
</Grid>
|
|
451
|
+
<Grid item xs={12} sm={6}>
|
|
452
|
+
<TextField
|
|
453
|
+
{...register('logoHeightClass')}
|
|
454
|
+
label="Logo Height Class"
|
|
455
|
+
placeholder="h-12 w-auto"
|
|
456
|
+
fullWidth
|
|
457
|
+
size="small"
|
|
458
|
+
InputLabelProps={{ shrink: true }}
|
|
459
|
+
/>
|
|
460
|
+
</Grid>
|
|
461
|
+
<Grid item xs={12} sm={6}>
|
|
462
|
+
<TextField
|
|
463
|
+
{...register('inputFieldClass')}
|
|
464
|
+
label="Input Focus Tailwind Class"
|
|
465
|
+
placeholder="e.g. [&_.MuiOutlinedInput-root.Mui-focused_fieldset]:border-[#0A204A]"
|
|
466
|
+
fullWidth
|
|
467
|
+
size="small"
|
|
468
|
+
InputLabelProps={{ shrink: true }}
|
|
469
|
+
/>
|
|
470
|
+
</Grid>
|
|
471
|
+
<Grid item xs={12} sm={6}>
|
|
472
|
+
<TextField
|
|
473
|
+
{...register('submitButtonClass')}
|
|
474
|
+
label="Button Bg Tailwind Class"
|
|
475
|
+
placeholder="e.g. bg-[#0A204A]"
|
|
476
|
+
fullWidth
|
|
477
|
+
size="small"
|
|
478
|
+
InputLabelProps={{ shrink: true }}
|
|
479
|
+
/>
|
|
480
|
+
</Grid>
|
|
481
|
+
</Grid>
|
|
482
|
+
</Paper>
|
|
483
|
+
|
|
484
|
+
{/* Brand & Background Images */}
|
|
485
|
+
<Paper className="p-5 rounded-xl border border-slate-200/80 shadow-none space-y-4">
|
|
486
|
+
<Typography className="text-slate-800 font-bold text-lg border-b pb-2">
|
|
487
|
+
Images Configuration
|
|
488
|
+
</Typography>
|
|
489
|
+
<Grid container spacing={3}>
|
|
490
|
+
<Grid item xs={12} sm={6}>
|
|
491
|
+
<FormControl fullWidth size="small">
|
|
492
|
+
<InputLabel id="brand-logo-label">Brand Logo Image</InputLabel>
|
|
493
|
+
<Select
|
|
494
|
+
labelId="brand-logo-label"
|
|
495
|
+
label="Brand Logo Image"
|
|
496
|
+
value={currentValues.brandLogo || ''}
|
|
497
|
+
onChange={(e) => setValue('brandLogo', e.target.value)}
|
|
498
|
+
renderValue={(value) => (
|
|
499
|
+
<Box className="flex items-center gap-2">
|
|
500
|
+
<img src={imageMap[value]} alt="" className="h-6 w-auto object-contain bg-slate-100 p-0.5 rounded" />
|
|
501
|
+
<span>{value}</span>
|
|
502
|
+
</Box>
|
|
503
|
+
)}
|
|
504
|
+
>
|
|
505
|
+
{Object.keys(imageMap).map((key) => (
|
|
506
|
+
<MenuItem key={key} value={key} className="flex items-center gap-3">
|
|
507
|
+
<img src={imageMap[key]} alt="" className="h-6 w-12 object-contain bg-slate-100 p-0.5 rounded" />
|
|
508
|
+
<span>{key}</span>
|
|
509
|
+
</MenuItem>
|
|
510
|
+
))}
|
|
511
|
+
</Select>
|
|
512
|
+
</FormControl>
|
|
513
|
+
</Grid>
|
|
514
|
+
|
|
515
|
+
<Grid item xs={12} sm={6}>
|
|
516
|
+
<FormControl fullWidth size="small">
|
|
517
|
+
<InputLabel id="bg-img-label">Split Background Image</InputLabel>
|
|
518
|
+
<Select
|
|
519
|
+
labelId="bg-img-label"
|
|
520
|
+
label="Split Background Image"
|
|
521
|
+
value={currentValues.implementationImage || ''}
|
|
522
|
+
onChange={(e) => setValue('implementationImage', e.target.value)}
|
|
523
|
+
renderValue={(value) => (
|
|
524
|
+
<Box className="flex items-center gap-2">
|
|
525
|
+
<img src={imageMap[value]} alt="" className="h-6 w-auto object-contain bg-slate-100 p-0.5 rounded" />
|
|
526
|
+
<span>{value}</span>
|
|
527
|
+
</Box>
|
|
528
|
+
)}
|
|
529
|
+
>
|
|
530
|
+
{Object.keys(imageMap).map((key) => (
|
|
531
|
+
<MenuItem key={key} value={key} className="flex items-center gap-3">
|
|
532
|
+
<img src={imageMap[key]} alt="" className="h-6 w-12 object-contain bg-slate-100 p-0.5 rounded" />
|
|
533
|
+
<span>{key}</span>
|
|
534
|
+
</MenuItem>
|
|
535
|
+
))}
|
|
536
|
+
</Select>
|
|
537
|
+
</FormControl>
|
|
538
|
+
</Grid>
|
|
539
|
+
</Grid>
|
|
540
|
+
</Paper>
|
|
541
|
+
|
|
542
|
+
{/* Footer Sections */}
|
|
543
|
+
<Paper className="p-5 rounded-xl border border-slate-200/80 shadow-none space-y-4">
|
|
544
|
+
<Box className="flex justify-between items-center border-b pb-2">
|
|
545
|
+
<Typography className="text-slate-800 font-bold text-lg">
|
|
546
|
+
Footer Sections
|
|
547
|
+
</Typography>
|
|
548
|
+
<Button
|
|
549
|
+
size="small"
|
|
550
|
+
variant="outlined"
|
|
551
|
+
onClick={() => appendSection({ type: 'logos', logos: [] })}
|
|
552
|
+
startIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
553
|
+
className="normal-case rounded-lg"
|
|
554
|
+
>
|
|
555
|
+
Add Section
|
|
556
|
+
</Button>
|
|
557
|
+
</Box>
|
|
558
|
+
|
|
559
|
+
{sectionFields.length === 0 ? (
|
|
560
|
+
<Box className="text-center py-6 text-slate-400">
|
|
561
|
+
No footer sections configured. Click "Add Section" to create one.
|
|
562
|
+
</Box>
|
|
563
|
+
) : (
|
|
564
|
+
<div className="space-y-4">
|
|
565
|
+
{sectionFields.map((field, idx) => {
|
|
566
|
+
const sectionType = watch(`footerSections.${idx}.type`);
|
|
567
|
+
return (
|
|
568
|
+
<Card key={field.id} className="p-4 border border-slate-200 shadow-none bg-white rounded-xl space-y-4 relative">
|
|
569
|
+
<Box className="flex justify-between items-center">
|
|
570
|
+
<FormControl size="small" className="w-[180px]">
|
|
571
|
+
<InputLabel>Section Type</InputLabel>
|
|
572
|
+
<Select
|
|
573
|
+
label="Section Type"
|
|
574
|
+
value={sectionType || 'logos'}
|
|
575
|
+
onChange={(e) => {
|
|
576
|
+
const type = e.target.value;
|
|
577
|
+
setValue(`footerSections.${idx}.type`, type);
|
|
578
|
+
if (type === 'buttons') {
|
|
579
|
+
setValue(`footerSections.${idx}.buttons`, []);
|
|
580
|
+
} else if (type === 'logos') {
|
|
581
|
+
setValue(`footerSections.${idx}.logos`, []);
|
|
582
|
+
} else if (type === 'textWithLogo') {
|
|
583
|
+
setValue(`footerSections.${idx}.text`, 'Funded By');
|
|
584
|
+
setValue(`footerSections.${idx}.image', 'medplat-highres.png`);
|
|
585
|
+
setValue(`footerSections.${idx}.href`, '#');
|
|
586
|
+
setValue(`footerSections.${idx}.alt`, '');
|
|
587
|
+
} else if (type === 'logosWithText') {
|
|
588
|
+
setValue(`footerSections.${idx}.items`, []);
|
|
589
|
+
}
|
|
590
|
+
}}
|
|
591
|
+
>
|
|
592
|
+
<MenuItem value="buttons">Quick Link Buttons</MenuItem>
|
|
593
|
+
<MenuItem value="logos">Partners Logos</MenuItem>
|
|
594
|
+
<MenuItem value="textWithLogo">Single Text & Logo</MenuItem>
|
|
595
|
+
<MenuItem value="logosWithText">Logos side-by-side with Text</MenuItem>
|
|
596
|
+
</Select>
|
|
597
|
+
</FormControl>
|
|
598
|
+
<IconButton
|
|
599
|
+
size="small"
|
|
600
|
+
color="error"
|
|
601
|
+
onClick={() => removeSection(idx)}
|
|
602
|
+
>
|
|
603
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
604
|
+
</IconButton>
|
|
605
|
+
</Box>
|
|
606
|
+
|
|
607
|
+
<Divider />
|
|
608
|
+
|
|
609
|
+
{/* Rendering section items depending on type */}
|
|
610
|
+
{sectionType === 'buttons' && (
|
|
611
|
+
<div className="space-y-3">
|
|
612
|
+
<Box className="flex justify-between items-center">
|
|
613
|
+
<Typography variant="subtitle2" className="text-slate-600 font-bold">Buttons list</Typography>
|
|
614
|
+
<Button
|
|
615
|
+
size="small"
|
|
616
|
+
variant="outlined"
|
|
617
|
+
color="primary"
|
|
618
|
+
onClick={() => addSubItem(idx, 'buttons')}
|
|
619
|
+
startIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
620
|
+
className="normal-case rounded-lg"
|
|
621
|
+
>
|
|
622
|
+
Add Button
|
|
623
|
+
</Button>
|
|
624
|
+
</Box>
|
|
625
|
+
{(watch(`footerSections.${idx}.buttons`) || []).map((btn, btnIdx) => (
|
|
626
|
+
<Box key={btnIdx} className="flex flex-wrap items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
627
|
+
<FormControl size="small" className="w-[120px]">
|
|
628
|
+
<InputLabel>Icon</InputLabel>
|
|
629
|
+
<Select
|
|
630
|
+
label="Icon"
|
|
631
|
+
value={btn.icon || ''}
|
|
632
|
+
onChange={(e) => setValue(`footerSections.${idx}.buttons.${btnIdx}.icon`, e.target.value || null)}
|
|
633
|
+
>
|
|
634
|
+
<MenuItem value="">None</MenuItem>
|
|
635
|
+
<MenuItem value="PERSON">Person</MenuItem>
|
|
636
|
+
<MenuItem value="MEDICAL">Medical</MenuItem>
|
|
637
|
+
<MenuItem value="PASSPORT">Passport</MenuItem>
|
|
638
|
+
</Select>
|
|
639
|
+
</FormControl>
|
|
640
|
+
<TextField
|
|
641
|
+
{...register(`footerSections.${idx}.buttons.${btnIdx}.text`)}
|
|
642
|
+
label="Button Text"
|
|
643
|
+
size="small"
|
|
644
|
+
className="flex-1 min-w-[140px]"
|
|
645
|
+
InputLabelProps={{ shrink: true }}
|
|
646
|
+
/>
|
|
647
|
+
<TextField
|
|
648
|
+
{...register(`footerSections.${idx}.buttons.${btnIdx}.href`)}
|
|
649
|
+
label="Link (Href)"
|
|
650
|
+
size="small"
|
|
651
|
+
className="flex-1 min-w-[180px]"
|
|
652
|
+
InputLabelProps={{ shrink: true }}
|
|
653
|
+
/>
|
|
654
|
+
<IconButton size="small" color="error" onClick={() => removeSubItem(idx, 'buttons', btnIdx)}>
|
|
655
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
656
|
+
</IconButton>
|
|
657
|
+
</Box>
|
|
658
|
+
))}
|
|
659
|
+
</div>
|
|
660
|
+
)}
|
|
661
|
+
|
|
662
|
+
{sectionType === 'logos' && (
|
|
663
|
+
<div className="space-y-3">
|
|
664
|
+
<Box className="flex justify-between items-center">
|
|
665
|
+
<Typography variant="subtitle2" className="text-slate-600 font-bold">Logos list</Typography>
|
|
666
|
+
<Button
|
|
667
|
+
size="small"
|
|
668
|
+
variant="outlined"
|
|
669
|
+
color="primary"
|
|
670
|
+
onClick={() => addSubItem(idx, 'logos')}
|
|
671
|
+
startIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
672
|
+
className="normal-case rounded-lg"
|
|
673
|
+
>
|
|
674
|
+
Add Logo
|
|
675
|
+
</Button>
|
|
676
|
+
</Box>
|
|
677
|
+
{(watch(`footerSections.${idx}.logos`) || []).map((logo, logoIdx) => (
|
|
678
|
+
<Box key={logoIdx} className="flex flex-wrap items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
679
|
+
<FormControl size="small" className="w-[180px]">
|
|
680
|
+
<InputLabel>Logo Image</InputLabel>
|
|
681
|
+
<Select
|
|
682
|
+
label="Logo Image"
|
|
683
|
+
value={logo.image || ''}
|
|
684
|
+
onChange={(e) => setValue(`footerSections.${idx}.logos.${logoIdx}.image`, e.target.value)}
|
|
685
|
+
>
|
|
686
|
+
{Object.keys(imageMap).map((key) => (
|
|
687
|
+
<MenuItem key={key} value={key}>{key}</MenuItem>
|
|
688
|
+
))}
|
|
689
|
+
</Select>
|
|
690
|
+
</FormControl>
|
|
691
|
+
<TextField
|
|
692
|
+
{...register(`footerSections.${idx}.logos.${logoIdx}.href`)}
|
|
693
|
+
label="Link (Href)"
|
|
694
|
+
size="small"
|
|
695
|
+
className="flex-1 min-w-[140px]"
|
|
696
|
+
InputLabelProps={{ shrink: true }}
|
|
697
|
+
/>
|
|
698
|
+
<TextField
|
|
699
|
+
{...register(`footerSections.${idx}.logos.${logoIdx}.alt`)}
|
|
700
|
+
label="Alt Tag"
|
|
701
|
+
size="small"
|
|
702
|
+
className="flex-1 min-w-[120px]"
|
|
703
|
+
InputLabelProps={{ shrink: true }}
|
|
704
|
+
/>
|
|
705
|
+
<IconButton size="small" color="error" onClick={() => removeSubItem(idx, 'logos', logoIdx)}>
|
|
706
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
707
|
+
</IconButton>
|
|
708
|
+
</Box>
|
|
709
|
+
))}
|
|
710
|
+
</div>
|
|
711
|
+
)}
|
|
712
|
+
|
|
713
|
+
{sectionType === 'textWithLogo' && (
|
|
714
|
+
<Grid container spacing={2}>
|
|
715
|
+
<Grid item xs={12} sm={6}>
|
|
716
|
+
<TextField
|
|
717
|
+
{...register(`footerSections.${idx}.text`)}
|
|
718
|
+
label="Leading Text"
|
|
719
|
+
size="small"
|
|
720
|
+
fullWidth
|
|
721
|
+
InputLabelProps={{ shrink: true }}
|
|
722
|
+
/>
|
|
723
|
+
</Grid>
|
|
724
|
+
<Grid item xs={12} sm={6}>
|
|
725
|
+
<FormControl fullWidth size="small">
|
|
726
|
+
<InputLabel>Logo Image</InputLabel>
|
|
727
|
+
<Select
|
|
728
|
+
label="Logo Image"
|
|
729
|
+
value={watch(`footerSections.${idx}.image`) || ''}
|
|
730
|
+
onChange={(e) => setValue(`footerSections.${idx}.image`, e.target.value)}
|
|
731
|
+
>
|
|
732
|
+
{Object.keys(imageMap).map((key) => (
|
|
733
|
+
<MenuItem key={key} value={key}>{key}</MenuItem>
|
|
734
|
+
))}
|
|
735
|
+
</Select>
|
|
736
|
+
</FormControl>
|
|
737
|
+
</Grid>
|
|
738
|
+
<Grid item xs={12} sm={6}>
|
|
739
|
+
<TextField
|
|
740
|
+
{...register(`footerSections.${idx}.href`)}
|
|
741
|
+
label="Link (Href)"
|
|
742
|
+
size="small"
|
|
743
|
+
fullWidth
|
|
744
|
+
InputLabelProps={{ shrink: true }}
|
|
745
|
+
/>
|
|
746
|
+
</Grid>
|
|
747
|
+
<Grid item xs={12} sm={6}>
|
|
748
|
+
<TextField
|
|
749
|
+
label="Alt Tag"
|
|
750
|
+
size="small"
|
|
751
|
+
fullWidth
|
|
752
|
+
value={watch(`footerSections.${idx}.alt`) || ''}
|
|
753
|
+
onChange={(e) => setValue(`footerSections.${idx}.alt`, e.target.value)}
|
|
754
|
+
/>
|
|
755
|
+
</Grid>
|
|
756
|
+
</Grid>
|
|
757
|
+
)}
|
|
758
|
+
|
|
759
|
+
{sectionType === 'logosWithText' && (
|
|
760
|
+
<div className="space-y-3">
|
|
761
|
+
<Box className="flex justify-between items-center">
|
|
762
|
+
<Typography variant="subtitle2" className="text-slate-600 font-bold">Items list</Typography>
|
|
763
|
+
<Button
|
|
764
|
+
size="small"
|
|
765
|
+
variant="outlined"
|
|
766
|
+
color="primary"
|
|
767
|
+
onClick={() => addSubItem(idx, 'logosWithText')}
|
|
768
|
+
startIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
769
|
+
className="normal-case rounded-lg"
|
|
770
|
+
>
|
|
771
|
+
Add Item
|
|
772
|
+
</Button>
|
|
773
|
+
</Box>
|
|
774
|
+
{(watch(`footerSections.${idx}.items`) || []).map((item, itemIdx) => (
|
|
775
|
+
<Box key={itemIdx} className="flex flex-wrap items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
776
|
+
<FormControl size="small" className="w-[180px]">
|
|
777
|
+
<InputLabel>Image</InputLabel>
|
|
778
|
+
<Select
|
|
779
|
+
label="Image"
|
|
780
|
+
value={item.image || ''}
|
|
781
|
+
onChange={(e) => setValue(`footerSections.${idx}.items.${itemIdx}.image`, e.target.value)}
|
|
782
|
+
>
|
|
783
|
+
{Object.keys(imageMap).map((key) => (
|
|
784
|
+
<MenuItem key={key} value={key}>{key}</MenuItem>
|
|
785
|
+
))}
|
|
786
|
+
</Select>
|
|
787
|
+
</FormControl>
|
|
788
|
+
<TextField
|
|
789
|
+
label="Label Text"
|
|
790
|
+
size="small"
|
|
791
|
+
className="flex-1 min-w-[120px]"
|
|
792
|
+
value={item.text || ''}
|
|
793
|
+
onChange={(e) => setValue(`footerSections.${idx}.items.${itemIdx}.text`, e.target.value)}
|
|
794
|
+
/>
|
|
795
|
+
<TextField
|
|
796
|
+
label="Link (Href)"
|
|
797
|
+
size="small"
|
|
798
|
+
className="flex-1 min-w-[120px]"
|
|
799
|
+
value={item.href || ''}
|
|
800
|
+
onChange={(e) => setValue(`footerSections.${idx}.items.${itemIdx}.href`, e.target.value)}
|
|
801
|
+
/>
|
|
802
|
+
<TextField
|
|
803
|
+
label="Alt Tag"
|
|
804
|
+
size="small"
|
|
805
|
+
className="flex-1 min-w-[100px]"
|
|
806
|
+
value={item.alt || ''}
|
|
807
|
+
onChange={(e) => setValue(`footerSections.${idx}.items.${itemIdx}.alt`, e.target.value)}
|
|
808
|
+
/>
|
|
809
|
+
<IconButton size="small" color="error" onClick={() => removeSubItem(idx, 'logosWithText', itemIdx)}>
|
|
810
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
811
|
+
</IconButton>
|
|
812
|
+
</Box>
|
|
813
|
+
))}
|
|
814
|
+
</div>
|
|
815
|
+
)}
|
|
816
|
+
</Card>
|
|
817
|
+
);
|
|
818
|
+
})}
|
|
819
|
+
</div>
|
|
820
|
+
)}
|
|
821
|
+
</Paper>
|
|
822
|
+
|
|
823
|
+
{/* Bottom Logos */}
|
|
824
|
+
<Paper className="p-5 rounded-xl border border-slate-200/80 shadow-none space-y-4">
|
|
825
|
+
<Box className="flex justify-between items-center border-b pb-2">
|
|
826
|
+
<Typography className="text-slate-800 font-bold text-lg">
|
|
827
|
+
Bottom Logos (Below divider)
|
|
828
|
+
</Typography>
|
|
829
|
+
<Button
|
|
830
|
+
size="small"
|
|
831
|
+
variant="outlined"
|
|
832
|
+
onClick={() => appendBottomLogo({ image: 'medplat-highres.png', href: '#', alt: 'Medplat Logo' })}
|
|
833
|
+
startIcon={<FontAwesomeIcon icon={faPlus} />}
|
|
834
|
+
className="normal-case rounded-lg"
|
|
835
|
+
>
|
|
836
|
+
Add Bottom Logo
|
|
837
|
+
</Button>
|
|
838
|
+
</Box>
|
|
839
|
+
|
|
840
|
+
{bottomLogoFields.length === 0 ? (
|
|
841
|
+
<Box className="text-center py-6 text-slate-400">
|
|
842
|
+
No bottom logos. Click "Add Bottom Logo" to create one.
|
|
843
|
+
</Box>
|
|
844
|
+
) : (
|
|
845
|
+
<div className="space-y-3">
|
|
846
|
+
{bottomLogoFields.map((field, idx) => (
|
|
847
|
+
<Box key={field.id} className="flex flex-wrap items-center gap-3 p-3 bg-slate-50 rounded-lg border border-slate-100">
|
|
848
|
+
<FormControl size="small" className="w-[180px]">
|
|
849
|
+
<InputLabel>Image</InputLabel>
|
|
850
|
+
<Select
|
|
851
|
+
label="Image"
|
|
852
|
+
value={watch(`bottomLogos.${idx}.image`) || ''}
|
|
853
|
+
onChange={(e) => setValue(`bottomLogos.${idx}.image`, e.target.value)}
|
|
854
|
+
>
|
|
855
|
+
{Object.keys(imageMap).map((key) => (
|
|
856
|
+
<MenuItem key={key} value={key}>{key}</MenuItem>
|
|
857
|
+
))}
|
|
858
|
+
</Select>
|
|
859
|
+
</FormControl>
|
|
860
|
+
<TextField
|
|
861
|
+
label="Link (Href)"
|
|
862
|
+
size="small"
|
|
863
|
+
className="flex-1 min-w-[140px]"
|
|
864
|
+
value={watch(`bottomLogos.${idx}.href`) || ''}
|
|
865
|
+
onChange={(e) => setValue(`bottomLogos.${idx}.href`, e.target.value)}
|
|
866
|
+
/>
|
|
867
|
+
<TextField
|
|
868
|
+
label="Alt Tag"
|
|
869
|
+
size="small"
|
|
870
|
+
className="flex-1 min-w-[120px]"
|
|
871
|
+
value={watch(`bottomLogos.${idx}.alt`) || ''}
|
|
872
|
+
onChange={(e) => setValue(`bottomLogos.${idx}.alt`, e.target.value)}
|
|
873
|
+
/>
|
|
874
|
+
<IconButton size="small" color="error" onClick={() => removeBottomLogo(idx)}>
|
|
875
|
+
<FontAwesomeIcon icon={faTrash} />
|
|
876
|
+
</IconButton>
|
|
877
|
+
</Box>
|
|
878
|
+
))}
|
|
879
|
+
</div>
|
|
880
|
+
)}
|
|
881
|
+
</Paper>
|
|
882
|
+
</form>
|
|
883
|
+
) : (
|
|
884
|
+
/* Raw JSON View */
|
|
885
|
+
<Box className="h-full flex flex-col">
|
|
886
|
+
<Box className="flex-1 bg-slate-900 text-slate-200 p-4 rounded-xl font-mono text-sm overflow-auto max-h-[500px]">
|
|
887
|
+
<pre>{JSON.stringify(currentValues, null, 2)}</pre>
|
|
888
|
+
</Box>
|
|
889
|
+
<Typography variant="caption" className="text-slate-400 mt-2 block">
|
|
890
|
+
Copy this JSON structure to paste inside `app.constant.js` for hardcoding deployment values.
|
|
891
|
+
</Typography>
|
|
892
|
+
</Box>
|
|
893
|
+
)}
|
|
894
|
+
</Box>
|
|
895
|
+
</Card>
|
|
896
|
+
</Grid>
|
|
897
|
+
|
|
898
|
+
{/* Right column live preview */}
|
|
899
|
+
<Grid item xs={12} lg={5}>
|
|
900
|
+
<Card className="rounded-2xl shadow-sm border border-slate-200 h-[calc(100vh-190px)] flex flex-col overflow-hidden bg-slate-100">
|
|
901
|
+
<Box className="border-b border-slate-200 bg-white flex justify-between items-center px-4 py-2">
|
|
902
|
+
<Typography className="text-slate-700 font-extrabold text-sm tracking-wider flex items-center gap-2">
|
|
903
|
+
<FontAwesomeIcon icon={faEye} className="text-indigo-600" />
|
|
904
|
+
Live Preview
|
|
905
|
+
</Typography>
|
|
906
|
+
<Box className="flex gap-2">
|
|
907
|
+
<Button
|
|
908
|
+
size="small"
|
|
909
|
+
variant={previewDevice === 'desktop' ? 'contained' : 'outlined'}
|
|
910
|
+
onClick={() => setPreviewDevice('desktop')}
|
|
911
|
+
className={`rounded-lg py-1 px-3 normal-case ${previewDevice === 'desktop' ? 'bg-indigo-600 text-white' : 'text-indigo-600 border-indigo-200'}`}
|
|
912
|
+
startIcon={<FontAwesomeIcon icon={faDesktop} />}
|
|
913
|
+
>
|
|
914
|
+
Desktop
|
|
915
|
+
</Button>
|
|
916
|
+
<Button
|
|
917
|
+
size="small"
|
|
918
|
+
variant={previewDevice === 'mobile' ? 'contained' : 'outlined'}
|
|
919
|
+
onClick={() => setPreviewDevice('mobile')}
|
|
920
|
+
className={`rounded-lg py-1 px-3 normal-case ${previewDevice === 'mobile' ? 'bg-indigo-600 text-white' : 'text-indigo-600 border-indigo-200'}`}
|
|
921
|
+
startIcon={<FontAwesomeIcon icon={faMobileAlt} />}
|
|
922
|
+
>
|
|
923
|
+
Mobile
|
|
924
|
+
</Button>
|
|
925
|
+
</Box>
|
|
926
|
+
</Box>
|
|
927
|
+
|
|
928
|
+
{/* Container for simulation mockup */}
|
|
929
|
+
<Box className="flex-1 flex items-center justify-center p-4 overflow-hidden bg-slate-100/50">
|
|
930
|
+
<Box
|
|
931
|
+
ref={containerRef}
|
|
932
|
+
className="transition-all duration-300 bg-slate-200 rounded-2xl overflow-hidden border-4 border-slate-300 shadow-lg"
|
|
933
|
+
style={{
|
|
934
|
+
width: targetWidth * scale,
|
|
935
|
+
height: targetHeight * scale,
|
|
936
|
+
position: 'relative',
|
|
937
|
+
}}
|
|
938
|
+
>
|
|
939
|
+
{/* Simulated Screen */}
|
|
940
|
+
<Box
|
|
941
|
+
className="bg-slate-100 select-none"
|
|
942
|
+
style={{
|
|
943
|
+
width: targetWidth,
|
|
944
|
+
height: targetHeight,
|
|
945
|
+
transform: `scale(${scale})`,
|
|
946
|
+
transformOrigin: 'top left',
|
|
947
|
+
position: 'absolute',
|
|
948
|
+
top: 0,
|
|
949
|
+
left: 0,
|
|
950
|
+
fontSize: '0.8rem',
|
|
951
|
+
}}
|
|
952
|
+
>
|
|
953
|
+
{currentValues.centerLogin || previewDevice === 'mobile' ? (
|
|
954
|
+
/* Centered Card Layout */
|
|
955
|
+
<Box className="w-full h-full flex items-center justify-center bg-slate-50 p-6 overflow-y-auto">
|
|
956
|
+
<Paper className="rounded-xl p-5 w-full max-w-sm shadow-md border border-slate-200">
|
|
957
|
+
{renderPreviewForm(currentValues)}
|
|
958
|
+
</Paper>
|
|
959
|
+
</Box>
|
|
960
|
+
) : (
|
|
961
|
+
/* Split Layout */
|
|
962
|
+
<Box className="w-full h-full flex overflow-hidden">
|
|
963
|
+
{/* Left image */}
|
|
964
|
+
<Box className="flex-1 bg-slate-300 relative overflow-hidden">
|
|
965
|
+
<img
|
|
966
|
+
src={imageMap[currentValues.implementationImage]}
|
|
967
|
+
alt=""
|
|
968
|
+
className="w-full h-full object-cover"
|
|
969
|
+
/>
|
|
970
|
+
</Box>
|
|
971
|
+
{/* Right form panel */}
|
|
972
|
+
<Box className="w-1/2 bg-slate-50 overflow-y-auto p-6 flex flex-col justify-center">
|
|
973
|
+
{renderPreviewForm(currentValues)}
|
|
974
|
+
</Box>
|
|
975
|
+
</Box>
|
|
976
|
+
)}
|
|
977
|
+
</Box>
|
|
978
|
+
</Box>
|
|
979
|
+
</Box>
|
|
980
|
+
</Card>
|
|
981
|
+
</Grid>
|
|
982
|
+
</Grid>
|
|
983
|
+
</Box>
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Function to render mockup preview forms
|
|
988
|
+
function renderPreviewForm(config) {
|
|
989
|
+
return (
|
|
990
|
+
<Box className="flex flex-col items-center w-full">
|
|
991
|
+
{/* Brand logo */}
|
|
992
|
+
{config.brandLogo && (
|
|
993
|
+
<img
|
|
994
|
+
src={imageMap[config.brandLogo]}
|
|
995
|
+
alt="Brand Logo"
|
|
996
|
+
className="h-10 w-auto mb-3 object-contain"
|
|
997
|
+
/>
|
|
998
|
+
)}
|
|
999
|
+
|
|
1000
|
+
{/* Divider */}
|
|
1001
|
+
<Box className="flex items-center w-full my-2">
|
|
1002
|
+
<Divider className="flex-1" />
|
|
1003
|
+
<Typography className="px-2 text-xs font-semibold text-slate-500">
|
|
1004
|
+
LOGIN
|
|
1005
|
+
</Typography>
|
|
1006
|
+
<Divider className="flex-1" />
|
|
1007
|
+
</Box>
|
|
1008
|
+
|
|
1009
|
+
{/* Fields */}
|
|
1010
|
+
<Box className="w-full space-y-2 mt-2">
|
|
1011
|
+
<TextField
|
|
1012
|
+
disabled
|
|
1013
|
+
value="admin"
|
|
1014
|
+
size="small"
|
|
1015
|
+
fullWidth
|
|
1016
|
+
className={config.inputFieldClass}
|
|
1017
|
+
variant="outlined"
|
|
1018
|
+
label="Username"
|
|
1019
|
+
InputProps={{ style: { fontSize: '0.8rem' } }}
|
|
1020
|
+
/>
|
|
1021
|
+
<TextField
|
|
1022
|
+
disabled
|
|
1023
|
+
type="password"
|
|
1024
|
+
value="••••••••"
|
|
1025
|
+
size="small"
|
|
1026
|
+
fullWidth
|
|
1027
|
+
className={config.inputFieldClass}
|
|
1028
|
+
variant="outlined"
|
|
1029
|
+
label="Password"
|
|
1030
|
+
InputProps={{ style: { fontSize: '0.8rem' } }}
|
|
1031
|
+
/>
|
|
1032
|
+
</Box>
|
|
1033
|
+
|
|
1034
|
+
{/* Login Button */}
|
|
1035
|
+
<Button
|
|
1036
|
+
disabled
|
|
1037
|
+
variant="contained"
|
|
1038
|
+
fullWidth
|
|
1039
|
+
className={`mt-4 py-1.5 text-xs font-semibold rounded-full text-white shadow-none ${config.submitButtonClass || 'bg-blue-600'}`}
|
|
1040
|
+
style={{ pointerEvents: 'none' }}
|
|
1041
|
+
>
|
|
1042
|
+
LOGIN
|
|
1043
|
+
</Button>
|
|
1044
|
+
|
|
1045
|
+
{/* Forgot password */}
|
|
1046
|
+
<Typography variant="caption" className="text-blue-500 mt-2 hover:underline cursor-pointer">
|
|
1047
|
+
Forgot Password?
|
|
1048
|
+
</Typography>
|
|
1049
|
+
|
|
1050
|
+
{/* Download app link */}
|
|
1051
|
+
{config.isDownloadLink && (
|
|
1052
|
+
<Typography variant="caption" className="text-blue-500 mt-1 hover:underline cursor-pointer flex items-center gap-1 font-medium">
|
|
1053
|
+
<span className="text-red-500 font-bold">(New)</span>
|
|
1054
|
+
<FontAwesomeIcon icon={faDownload} size="xs" />
|
|
1055
|
+
Download Mobile App
|
|
1056
|
+
</Typography>
|
|
1057
|
+
)}
|
|
1058
|
+
|
|
1059
|
+
{/* Footer sections */}
|
|
1060
|
+
{config.footerSections && config.footerSections.length > 0 && (
|
|
1061
|
+
<Box className="w-full mt-4 space-y-3">
|
|
1062
|
+
{config.footerSections.map((section, idx) => {
|
|
1063
|
+
const isButtons = section.type === 'buttons';
|
|
1064
|
+
const showDivider = idx === 0 || (idx > 0 && config.footerSections[idx - 1].type !== 'buttons');
|
|
1065
|
+
|
|
1066
|
+
return (
|
|
1067
|
+
<Box key={idx} className="w-full flex flex-col items-center">
|
|
1068
|
+
{showDivider && <Divider className="w-full my-1 opacity-60" />}
|
|
1069
|
+
|
|
1070
|
+
{section.type === 'buttons' && (
|
|
1071
|
+
<Box className="flex justify-between gap-2 mt-2 w-full">
|
|
1072
|
+
{(section.buttons || []).map((btn, btnIdx) => {
|
|
1073
|
+
const icon = iconMap[btn.icon];
|
|
1074
|
+
return (
|
|
1075
|
+
<Box
|
|
1076
|
+
key={btnIdx}
|
|
1077
|
+
className="flex-1 flex flex-col items-center justify-center py-2 bg-slate-100 rounded-xl border border-slate-200 cursor-pointer"
|
|
1078
|
+
>
|
|
1079
|
+
{icon && <FontAwesomeIcon icon={icon} className="text-[#00685b] mb-1" />}
|
|
1080
|
+
<Typography style={{ fontSize: '0.65rem' }} className="font-extrabold text-[#00685b] uppercase text-center tracking-tight">
|
|
1081
|
+
{btn.text}
|
|
1082
|
+
</Typography>
|
|
1083
|
+
</Box>
|
|
1084
|
+
);
|
|
1085
|
+
})}
|
|
1086
|
+
</Box>
|
|
1087
|
+
)}
|
|
1088
|
+
|
|
1089
|
+
{section.type === 'logos' && (
|
|
1090
|
+
<Box className="w-full mt-2 flex items-center justify-center gap-3">
|
|
1091
|
+
{(section.logos || []).map((logo, logoIdx) => {
|
|
1092
|
+
const isMedplat = logo.image === 'medplat-highres.png';
|
|
1093
|
+
const showDividerFlag = isMedplat && logoIdx > 0 && section.logos[logoIdx - 1].image !== 'medplat-highres.png' && config.showLogoDivider;
|
|
1094
|
+
return (
|
|
1095
|
+
<React.Fragment key={logoIdx}>
|
|
1096
|
+
{showDividerFlag && <Divider orientation="vertical" flexItem className="h-4 mx-0.5" />}
|
|
1097
|
+
<img
|
|
1098
|
+
src={imageMap[logo.image]}
|
|
1099
|
+
alt={logo.alt}
|
|
1100
|
+
className={config.logoHeightClass || 'h-6 w-auto object-contain'}
|
|
1101
|
+
/>
|
|
1102
|
+
</React.Fragment>
|
|
1103
|
+
);
|
|
1104
|
+
})}
|
|
1105
|
+
</Box>
|
|
1106
|
+
)}
|
|
1107
|
+
|
|
1108
|
+
{section.type === 'textWithLogo' && (
|
|
1109
|
+
<Box className="w-full mt-2 flex items-center justify-center gap-1">
|
|
1110
|
+
<span className="text-[10px] text-slate-500 font-medium">{section.text}</span>
|
|
1111
|
+
<img src={imageMap[section.image]} alt={section.alt} className="h-6 w-auto object-contain" />
|
|
1112
|
+
</Box>
|
|
1113
|
+
)}
|
|
1114
|
+
|
|
1115
|
+
{section.type === 'logosWithText' && (
|
|
1116
|
+
<Box className="w-full mt-2 flex items-center justify-center gap-2">
|
|
1117
|
+
{(section.items || []).map((item, itemIdx) => (
|
|
1118
|
+
<Box key={itemIdx} className="flex items-center gap-1">
|
|
1119
|
+
<img src={imageMap[item.image]} alt={item.alt} className="h-5 w-auto object-contain" />
|
|
1120
|
+
{item.text && <span style={{ fontSize: '7px' }} className="text-slate-500">{item.text}</span>}
|
|
1121
|
+
</Box>
|
|
1122
|
+
))}
|
|
1123
|
+
</Box>
|
|
1124
|
+
)}
|
|
1125
|
+
</Box>
|
|
1126
|
+
);
|
|
1127
|
+
})}
|
|
1128
|
+
</Box>
|
|
1129
|
+
)}
|
|
1130
|
+
|
|
1131
|
+
{/* Bottom Logos */}
|
|
1132
|
+
{config.bottomLogos && config.bottomLogos.length > 0 && (
|
|
1133
|
+
<Box className="w-full mt-3 flex flex-col items-center">
|
|
1134
|
+
<Divider className="w-full my-1" />
|
|
1135
|
+
<Box className="flex items-center gap-2 mt-1">
|
|
1136
|
+
{config.bottomLogos.map((logo, idx) => (
|
|
1137
|
+
<img
|
|
1138
|
+
key={idx}
|
|
1139
|
+
src={imageMap[logo.image]}
|
|
1140
|
+
alt={logo.alt}
|
|
1141
|
+
className="h-6 w-auto object-contain"
|
|
1142
|
+
/>
|
|
1143
|
+
))}
|
|
1144
|
+
</Box>
|
|
1145
|
+
</Box>
|
|
1146
|
+
)}
|
|
1147
|
+
</Box>
|
|
1148
|
+
);
|
|
1149
|
+
}
|