@stackcraftmx/calcita 0.32.2 → 0.51.0
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/README.md +426 -4
- package/cli/calcita.js +15 -0
- package/cli/checkTexts.js +209 -0
- package/cli/packageVersions.js +17 -3
- package/cli/projectFiles.js +7 -2
- package/cli/templates/module/__Name__Page.test.jsx.tpl +24 -0
- package/cli/templates/project/README.md.tpl +35 -2
- package/cli/templates/project/eslint.config.js.tpl +27 -0
- package/cli/templates/project/src/app/appRouter.jsx.tpl +5 -2
- package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
- package/cli/templates/project/src/core/accountConnector.js.tpl +4 -2
- package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
- package/cli/templates/project/src/modules/auth/SignInPage.test.jsx.tpl +20 -0
- package/cli/templates/project/src/modules/home/HomePage.test.jsx.tpl +16 -0
- package/cli/templates/project/src/testing/browserGaps.js.tpl +70 -0
- package/cli/templates/project/src/testing/openScreen.jsx.tpl +58 -0
- package/cli/templates/project/src/testing/setup.js.tpl +25 -0
- package/cli/templates/project/vite.config.js.tpl +20 -1
- package/dist/DataTable/AuditTrailDialog.js +3 -2
- package/dist/DataTable/AuditTrailDialog.js.map +1 -1
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
- package/dist/DataTable/EmptyState.js +3 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +3 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +71 -14
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +97 -28
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +57 -0
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -0
- package/dist/DataTable/StackedCell.js +60 -0
- package/dist/DataTable/StackedCell.js.map +1 -0
- package/dist/DataTable/TableCardsView.js +88 -0
- package/dist/DataTable/TableCardsView.js.map +1 -0
- package/dist/DataTable/columnWidths.js +30 -0
- package/dist/DataTable/columnWidths.js.map +1 -0
- package/dist/DataTable/columns.js +47 -1
- package/dist/DataTable/columns.js.map +1 -1
- package/dist/DataTable/filterContext.js.map +1 -1
- package/dist/DataTable/fixedColumns.js +24 -0
- package/dist/DataTable/fixedColumns.js.map +1 -0
- package/dist/DataTable/goToPageOptions.js +17 -0
- package/dist/DataTable/goToPageOptions.js.map +1 -0
- package/dist/DataTable/useTableView.js +29 -0
- package/dist/DataTable/useTableView.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountProvider.js +1 -1
- package/dist/account/AccountSecuritySection.js +3 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/DemoTotpCodeButton.js +3 -2
- package/dist/account/DemoTotpCodeButton.js.map +1 -1
- package/dist/account/ProfileScreen.js +3 -2
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +3 -2
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/appShell/AccountMenu.js +3 -2
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +3 -2
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +3 -2
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +3 -2
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +11 -4
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +3 -2
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +3 -2
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +3 -2
- package/dist/appShell/BarSearch.js.map +1 -1
- package/dist/appShell/HeaderTools.js +3 -2
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +3 -2
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +3 -2
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +3 -2
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +3 -2
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +3 -2
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TabBarEditor.js +3 -2
- package/dist/appShell/TabBarEditor.js.map +1 -1
- package/dist/appShell/TopMenu.js +3 -2
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +3 -2
- package/dist/appShell/Wordmark.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +3 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +3 -2
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +3 -2
- package/dist/calendar/EventDetails.js.map +1 -1
- package/dist/date-pickers.js +28 -2
- package/dist/date-pickers.js.map +1 -1
- package/dist/datePickers/DateInput.js +41 -54
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +81 -0
- package/dist/datePickers/DateTimeInput.js.map +1 -0
- package/dist/datePickers/FormDateTimeField.js +12 -0
- package/dist/datePickers/FormDateTimeField.js.map +1 -0
- package/dist/datePickers/FormMonthField.js +12 -0
- package/dist/datePickers/FormMonthField.js.map +1 -0
- package/dist/datePickers/FormTimeField.js +12 -0
- package/dist/datePickers/FormTimeField.js.map +1 -0
- package/dist/datePickers/MonthInput.js +64 -0
- package/dist/datePickers/MonthInput.js.map +1 -0
- package/dist/datePickers/PickerFrame.js +27 -0
- package/dist/datePickers/PickerFrame.js.map +1 -0
- package/dist/datePickers/TimeInput.js +61 -0
- package/dist/datePickers/TimeInput.js.map +1 -0
- package/dist/datePickers/dateTimeRules.js +24 -0
- package/dist/datePickers/dateTimeRules.js.map +1 -0
- package/dist/datePickers/pickerValues.js +106 -0
- package/dist/datePickers/pickerValues.js.map +1 -0
- package/dist/datePickers/useCurrentMinute.js +28 -0
- package/dist/datePickers/useCurrentMinute.js.map +1 -0
- package/dist/datePickers/usePickerField.js +23 -0
- package/dist/datePickers/usePickerField.js.map +1 -0
- package/dist/dictionaries/dictionaries.js +3 -1
- package/dist/dictionaries/dictionaries.js.map +1 -1
- package/dist/downloads/DownloadManager.js +3 -2
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +3 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +3 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +3 -2
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/forms/FormAttachmentsField.js +12 -0
- package/dist/forms/FormAttachmentsField.js.map +1 -0
- package/dist/forms/FormCheckboxGroupField.js +12 -0
- package/dist/forms/FormCheckboxGroupField.js.map +1 -0
- package/dist/forms/FormFileField.js +12 -0
- package/dist/forms/FormFileField.js.map +1 -0
- package/dist/forms/FormFileUploadField.js +12 -0
- package/dist/forms/FormFileUploadField.js.map +1 -0
- package/dist/forms/FormImageField.js +12 -0
- package/dist/forms/FormImageField.js.map +1 -0
- package/dist/forms/FormMaskedField.js +12 -0
- package/dist/forms/FormMaskedField.js.map +1 -0
- package/dist/forms/FormProblemAlert.js +3 -2
- package/dist/forms/FormProblemAlert.js.map +1 -1
- package/dist/forms/FormRepeatingFields.js +54 -0
- package/dist/forms/FormRepeatingFields.js.map +1 -0
- package/dist/forms/FormTransferListField.js +12 -0
- package/dist/forms/FormTransferListField.js.map +1 -0
- package/dist/forms/SubmitButton.js +7 -2
- package/dist/forms/SubmitButton.js.map +1 -1
- package/dist/forms/applyServerProblem.js +10 -3
- package/dist/forms/applyServerProblem.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +3 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/fieldRules.js +30 -0
- package/dist/forms/fieldRules.js.map +1 -0
- package/dist/forms/listRules.js +111 -0
- package/dist/forms/listRules.js.map +1 -0
- package/dist/forms/security/SecondStepForm.js +3 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +3 -2
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/forms/serverProblemFields.js +17 -0
- package/dist/forms/serverProblemFields.js.map +1 -1
- package/dist/forms/useValidationText.js +18 -0
- package/dist/forms/useValidationText.js.map +1 -0
- package/dist/forms/validation.js +35 -31
- package/dist/forms/validation.js.map +1 -1
- package/dist/forms/validationMessages.js +64 -7
- package/dist/forms/validationMessages.js.map +1 -1
- package/dist/forms.js +54 -29
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormAttachmentsField.js +12 -0
- package/dist/hookForm/FormAttachmentsField.js.map +1 -0
- package/dist/hookForm/FormCheckboxGroupField.js +12 -0
- package/dist/hookForm/FormCheckboxGroupField.js.map +1 -0
- package/dist/hookForm/FormFileField.js +12 -0
- package/dist/hookForm/FormFileField.js.map +1 -0
- package/dist/hookForm/FormFileUploadField.js +12 -0
- package/dist/hookForm/FormFileUploadField.js.map +1 -0
- package/dist/hookForm/FormImageField.js +12 -0
- package/dist/hookForm/FormImageField.js.map +1 -0
- package/dist/hookForm/FormMaskedField.js +12 -0
- package/dist/hookForm/FormMaskedField.js.map +1 -0
- package/dist/hookForm/FormProblemAlert.js +3 -2
- package/dist/hookForm/FormProblemAlert.js.map +1 -1
- package/dist/hookForm/FormRepeatingFields.js +44 -0
- package/dist/hookForm/FormRepeatingFields.js.map +1 -0
- package/dist/hookForm/FormTransferListField.js +12 -0
- package/dist/hookForm/FormTransferListField.js.map +1 -0
- package/dist/hookForm/SubmitButton.js +7 -2
- package/dist/hookForm/SubmitButton.js.map +1 -1
- package/dist/hookForm/applyServerProblem.js +8 -3
- package/dist/hookForm/applyServerProblem.js.map +1 -1
- package/dist/inbox/InboxLayout.js +3 -2
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +3 -2
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +3 -2
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/index.js +235 -124
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +103 -29
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +103 -0
- package/dist/inputs/CheckboxGroupInput.js.map +1 -0
- package/dist/inputs/ColorInput.js +7 -4
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FieldFrame.js +10 -4
- package/dist/inputs/FieldFrame.js.map +1 -1
- package/dist/inputs/FormSection.js +1 -1
- package/dist/inputs/MaskedInput.js +87 -0
- package/dist/inputs/MaskedInput.js.map +1 -0
- package/dist/inputs/NumberInput.js +41 -25
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +180 -0
- package/dist/inputs/RepeatingFields.js.map +1 -0
- package/dist/inputs/SelectInput.js +50 -9
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +5 -3
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +218 -0
- package/dist/inputs/TransferListInput.js.map +1 -0
- package/dist/inputs/WizardLayout.js +3 -2
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/inputs/inputMasks.js +84 -0
- package/dist/inputs/inputMasks.js.map +1 -0
- package/dist/inputs/selectAllState.js +20 -0
- package/dist/inputs/selectAllState.js.map +1 -0
- package/dist/inputs/useRowKeys.js +26 -0
- package/dist/inputs/useRowKeys.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +3 -2
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/legal/CookieNotice.js +80 -0
- package/dist/legal/CookieNotice.js.map +1 -0
- package/dist/legal/TermsAcceptanceDialog.js +130 -0
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -0
- package/dist/legal/consentStorage.js +18 -0
- package/dist/legal/consentStorage.js.map +1 -0
- package/dist/legal/useCookieConsent.js +17 -0
- package/dist/legal/useCookieConsent.js.map +1 -0
- package/dist/locales/en.js +257 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +257 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +3 -2
- package/dist/maps/MapView.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +82 -0
- package/dist/mexico/CfdiSummary.js.map +1 -0
- package/dist/mexico/EfirmaFields.js +168 -0
- package/dist/mexico/EfirmaFields.js.map +1 -0
- package/dist/mexico/certificateReader.js +79 -0
- package/dist/mexico/certificateReader.js.map +1 -0
- package/dist/mexico/cfdiReader.js +81 -0
- package/dist/mexico/cfdiReader.js.map +1 -0
- package/dist/mexico/derReader.js +81 -0
- package/dist/mexico/derReader.js.map +1 -0
- package/dist/mexico/efirmaKey.js +51 -0
- package/dist/mexico/efirmaKey.js.map +1 -0
- package/dist/mexico/mexicoIdentifiers.js +91 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -0
- package/dist/mexico/mexicoRules.js +21 -0
- package/dist/mexico/mexicoRules.js.map +1 -0
- package/dist/mexico.js +34 -0
- package/dist/mexico.js.map +1 -0
- package/dist/pwa/InstallAppSetting.js +3 -2
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +3 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +3 -2
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/react-hook-form.js +43 -17
- package/dist/react-hook-form.js.map +1 -1
- package/dist/rich-text.js +19 -0
- package/dist/rich-text.js.map +1 -0
- package/dist/richText/FormRichTextField.js +12 -0
- package/dist/richText/FormRichTextField.js.map +1 -0
- package/dist/richText/RichTextEditor.js +111 -0
- package/dist/richText/RichTextEditor.js.map +1 -0
- package/dist/richText/RichTextLinkPopover.js +57 -0
- package/dist/richText/RichTextLinkPopover.js.map +1 -0
- package/dist/richText/RichTextToolbar.js +189 -0
- package/dist/richText/RichTextToolbar.js.map +1 -0
- package/dist/richText/RichTextView.js +27 -0
- package/dist/richText/RichTextView.js.map +1 -0
- package/dist/richText/richTextContentSx.js +42 -0
- package/dist/richText/richTextContentSx.js.map +1 -0
- package/dist/richText/richTextExtensions.js +44 -0
- package/dist/richText/richTextExtensions.js.map +1 -0
- package/dist/richText/richTextRules.js +46 -0
- package/dist/richText/richTextRules.js.map +1 -0
- package/dist/security/ActivityLog.js +3 -2
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +3 -2
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +3 -2
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +23 -2
- package/dist/table.js.map +1 -1
- package/dist/theme/components/glassComponents.js +27 -0
- package/dist/theme/components/glassComponents.js.map +1 -1
- package/dist/theme/components/minimalComponents.js +12 -0
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/theme/themePieces.js +70 -4
- package/dist/theme/themePieces.js.map +1 -1
- package/dist/toolkit/ToolkitProvider.js +8 -1
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -4
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/combineTexts.js +32 -0
- package/dist/toolkit/combineTexts.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +17 -3
- package/dist/toolkit/hashNavigation.js.map +1 -1
- package/dist/toolkit/navigationPaths.js +15 -0
- package/dist/toolkit/navigationPaths.js.map +1 -1
- package/dist/toolkit/pathIndex.js +66 -0
- package/dist/toolkit/pathIndex.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +19 -1
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
- package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
- package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
- package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
- package/dist/types/DataTable/EmptyState.d.ts +6 -9
- package/dist/types/DataTable/FilterChips.d.ts +6 -11
- package/dist/types/DataTable/GoToPage.d.ts +6 -9
- package/dist/types/DataTable/ServerTable.d.ts +16 -80
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +14 -0
- package/dist/types/DataTable/StackedCell.d.ts +12 -0
- package/dist/types/DataTable/TableCardsView.d.ts +27 -0
- package/dist/types/DataTable/columnWidths.d.ts +21 -0
- package/dist/types/DataTable/columns.d.ts +111 -0
- package/dist/types/DataTable/filterContext.d.ts +2 -1
- package/dist/types/DataTable/fixedColumns.d.ts +29 -0
- package/dist/types/DataTable/goToPageOptions.d.ts +15 -0
- package/dist/types/DataTable/index.d.ts +7 -1
- package/dist/types/DataTable/useTableView.d.ts +22 -0
- package/dist/types/access/PermissionMatrix.d.ts +6 -10
- package/dist/types/account/AccountSecuritySection.d.ts +6 -7
- package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
- package/dist/types/account/ProfileScreen.d.ts +6 -18
- package/dist/types/account/SignInScreen.d.ts +6 -13
- package/dist/types/appShell/AccountMenu.d.ts +6 -11
- package/dist/types/appShell/AppLayout.d.ts +3 -40
- package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
- package/dist/types/appShell/AuthLayout.d.ts +3 -11
- package/dist/types/appShell/BarActionButton.d.ts +6 -10
- package/dist/types/appShell/BarActions.d.ts +6 -15
- package/dist/types/appShell/BarCapsule.d.ts +6 -9
- package/dist/types/appShell/BarSearch.d.ts +6 -15
- package/dist/types/appShell/HeaderTools.d.ts +6 -16
- package/dist/types/appShell/MobileTabBar.d.ts +6 -13
- package/dist/types/appShell/SettingsMenu.d.ts +6 -10
- package/dist/types/appShell/SideMenu.d.ts +6 -20
- package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
- package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
- package/dist/types/appShell/TabBarEditor.d.ts +6 -9
- package/dist/types/appShell/TopMenu.d.ts +6 -5
- package/dist/types/appShell/Wordmark.d.ts +6 -7
- package/dist/types/auth/PasswordChecklist.d.ts +6 -9
- package/dist/types/calendar/EventCalendar.d.ts +6 -23
- package/dist/types/calendar/EventDetails.d.ts +6 -10
- package/dist/types/date-pickers.d.ts +10 -1
- package/dist/types/datePickers/DateInput.d.ts +10 -23
- package/dist/types/datePickers/DateTimeInput.d.ts +30 -0
- package/dist/types/datePickers/FormDateTimeField.d.ts +7 -0
- package/dist/types/datePickers/FormMonthField.d.ts +7 -0
- package/dist/types/datePickers/FormTimeField.d.ts +7 -0
- package/dist/types/datePickers/MonthInput.d.ts +25 -0
- package/dist/types/datePickers/PickerFrame.d.ts +22 -0
- package/dist/types/datePickers/TimeInput.d.ts +24 -0
- package/dist/types/datePickers/dateTimeRules.d.ts +31 -0
- package/dist/types/datePickers/pickerValues.d.ts +114 -0
- package/dist/types/datePickers/useCurrentMinute.d.ts +5 -0
- package/dist/types/datePickers/usePickerField.d.ts +28 -0
- package/dist/types/dictionaries/dictionaries.d.ts +6 -3
- package/dist/types/downloads/DownloadManager.d.ts +6 -6
- package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
- package/dist/types/feedback/ModalWaiting.d.ts +6 -9
- package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
- package/dist/types/filepond/FileDropzone.d.ts +6 -29
- package/dist/types/forms/FormAttachmentsField.d.ts +7 -0
- package/dist/types/forms/FormCheckboxGroupField.d.ts +7 -0
- package/dist/types/forms/FormFileField.d.ts +7 -0
- package/dist/types/forms/FormFileUploadField.d.ts +7 -0
- package/dist/types/forms/FormImageField.d.ts +7 -0
- package/dist/types/forms/FormMaskedField.d.ts +7 -0
- package/dist/types/forms/FormProblemAlert.d.ts +6 -5
- package/dist/types/forms/FormRepeatingFields.d.ts +25 -0
- package/dist/types/forms/FormTransferListField.d.ts +7 -0
- package/dist/types/forms/SubmitButton.d.ts +6 -6
- package/dist/types/forms/auth/SignInForm.d.ts +6 -11
- package/dist/types/forms/fieldRules.d.ts +44 -0
- package/dist/types/forms/index.d.ts +11 -1
- package/dist/types/forms/listRules.d.ts +48 -0
- package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
- package/dist/types/forms/serverProblemFields.d.ts +13 -0
- package/dist/types/forms/useValidationText.d.ts +12 -0
- package/dist/types/forms/validation.d.ts +117 -174
- package/dist/types/forms/validationMessages.d.ts +54 -6
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormAttachmentsField.d.ts +8 -0
- package/dist/types/hookForm/FormCheckboxGroupField.d.ts +8 -0
- package/dist/types/hookForm/FormFileField.d.ts +8 -0
- package/dist/types/hookForm/FormFileUploadField.d.ts +8 -0
- package/dist/types/hookForm/FormImageField.d.ts +8 -0
- package/dist/types/hookForm/FormMaskedField.d.ts +9 -0
- package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
- package/dist/types/hookForm/FormRepeatingFields.d.ts +27 -0
- package/dist/types/hookForm/FormTransferListField.d.ts +8 -0
- package/dist/types/hookForm/SubmitButton.d.ts +6 -13
- package/dist/types/inbox/InboxLayout.d.ts +6 -17
- package/dist/types/inbox/MessageList.d.ts +6 -18
- package/dist/types/inbox/MessageView.d.ts +6 -14
- package/dist/types/index.d.ts +33 -4
- package/dist/types/inputs/CatalogInput.d.ts +10 -21
- package/dist/types/inputs/CheckboxGroupInput.d.ts +28 -0
- package/dist/types/inputs/ColorInput.d.ts +7 -16
- package/dist/types/inputs/FieldFrame.d.ts +6 -10
- package/dist/types/inputs/MaskedInput.d.ts +38 -0
- package/dist/types/inputs/NumberInput.d.ts +7 -20
- package/dist/types/inputs/RepeatingFields.d.ts +31 -0
- package/dist/types/inputs/SelectInput.d.ts +10 -18
- package/dist/types/inputs/TextInput.d.ts +3 -0
- package/dist/types/inputs/TransferListInput.d.ts +30 -0
- package/dist/types/inputs/WizardLayout.d.ts +6 -19
- package/dist/types/inputs/inputMasks.d.ts +52 -0
- package/dist/types/inputs/selectAllState.d.ts +11 -0
- package/dist/types/inputs/useRowKeys.d.ts +11 -0
- package/dist/types/kanban/KanbanBoard.d.ts +6 -25
- package/dist/types/legal/CookieNotice.d.ts +20 -0
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +20 -0
- package/dist/types/legal/consentStorage.d.ts +16 -0
- package/dist/types/legal/useCookieConsent.d.ts +13 -0
- package/dist/types/locales/en.d.ts +256 -0
- package/dist/types/locales/es.d.ts +256 -0
- package/dist/types/maps/MapView.d.ts +6 -17
- package/dist/types/mexico/CfdiSummary.d.ts +12 -0
- package/dist/types/mexico/EfirmaFields.d.ts +28 -0
- package/dist/types/mexico/certificateReader.d.ts +37 -0
- package/dist/types/mexico/cfdiReader.d.ts +95 -0
- package/dist/types/mexico/derReader.d.ts +54 -0
- package/dist/types/mexico/efirmaKey.d.ts +26 -0
- package/dist/types/mexico/mexicoIdentifiers.d.ts +68 -0
- package/dist/types/mexico/mexicoRules.d.ts +45 -0
- package/dist/types/mexico.d.ts +11 -0
- package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
- package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
- package/dist/types/react-hook-form.d.ts +11 -1
- package/dist/types/rich-text.d.ts +5 -0
- package/dist/types/richText/FormRichTextField.d.ts +7 -0
- package/dist/types/richText/RichTextEditor.d.ts +27 -0
- package/dist/types/richText/RichTextLinkPopover.d.ts +17 -0
- package/dist/types/richText/RichTextToolbar.d.ts +15 -0
- package/dist/types/richText/RichTextView.d.ts +10 -0
- package/dist/types/richText/richTextContentSx.d.ts +7 -0
- package/dist/types/richText/richTextExtensions.d.ts +15 -0
- package/dist/types/richText/richTextRules.d.ts +41 -0
- package/dist/types/security/ActivityLog.d.ts +6 -9
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
- package/dist/types/security/SessionsCard.d.ts +6 -11
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/themePieces.d.ts +55 -3
- package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
- package/dist/types/toolkit/browserNavigation.d.ts +7 -3
- package/dist/types/toolkit/combineTexts.d.ts +6 -0
- package/dist/types/toolkit/hashNavigation.d.ts +4 -2
- package/dist/types/toolkit/navigationPaths.d.ts +35 -0
- package/dist/types/toolkit/pathIndex.d.ts +27 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
- package/dist/types/ui/actions/ActionsMenu.d.ts +24 -11
- package/dist/types/ui/actions/CopyButton.d.ts +8 -11
- package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
- package/dist/types/ui/actions/SecretField.d.ts +6 -11
- package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
- package/dist/types/ui/actions/tableRowText.d.ts +6 -0
- package/dist/types/ui/cards/RecordCard.d.ts +40 -0
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +12 -0
- package/dist/types/ui/countdown/Countdown.d.ts +22 -0
- package/dist/types/ui/countdown/countdownParts.d.ts +39 -0
- package/dist/types/ui/countdown/useCountdown.d.ts +14 -0
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +14 -0
- package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
- package/dist/types/ui/filters/FilterDrawer.d.ts +23 -0
- package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
- package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
- package/dist/types/ui/filters/filterDrawerFields.d.ts +139 -0
- package/dist/types/ui/help/HelpButton.d.ts +11 -0
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +27 -0
- package/dist/types/ui/help/useHelpCarousel.d.ts +21 -0
- package/dist/types/ui/index.d.ts +10 -0
- package/dist/types/ui/labels/IconBadge.d.ts +34 -0
- package/dist/types/ui/labels/MarkedText.d.ts +10 -0
- package/dist/types/ui/labels/Tag.d.ts +6 -12
- package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
- package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
- package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
- package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
- package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
- package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
- package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
- package/dist/types/ui/layout/PageHeader.d.ts +6 -10
- package/dist/types/ui/layout/PageHeading.d.ts +6 -11
- package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
- package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
- package/dist/types/ui/layout/SectionCard.d.ts +6 -8
- package/dist/types/ui/metrics/BarList.d.ts +6 -12
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
- package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
- package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
- package/dist/types/ui/metrics/LineChart.d.ts +6 -20
- package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
- package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
- package/dist/types/ui/metrics/chartParts.d.ts +15 -30
- package/dist/types/ui/navigation/BackButton.d.ts +6 -9
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
- package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
- package/dist/types/ui/navigation/RouteTabs.d.ts +39 -0
- package/dist/types/ui/navigation/routeTabMatching.d.ts +27 -0
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
- package/dist/types/upload/AttachmentsInput.d.ts +72 -0
- package/dist/types/upload/FileChip.d.ts +18 -0
- package/dist/types/upload/FileDropzone.d.ts +7 -19
- package/dist/types/upload/FileInput.d.ts +31 -0
- package/dist/types/upload/FileUploadInput.d.ts +56 -0
- package/dist/types/upload/ImageInput.d.ts +35 -0
- package/dist/types/upload/TemplateDownloadButton.d.ts +27 -0
- package/dist/types/upload/fileAppearance.d.ts +8 -0
- package/dist/types/upload/fileRules.d.ts +38 -0
- package/dist/types/upload/transferProgress.d.ts +18 -0
- package/dist/types/upload/uploadMachine.d.ts +6 -2
- package/dist/types/upload/uploadWithProgress.d.ts +6 -2
- package/dist/types/upload/useFilePicker.d.ts +39 -0
- package/dist/ui/actions/ActionsMenu.js +109 -17
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +37 -12
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +3 -2
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -3
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +3 -2
- package/dist/ui/actions/SelectionActions.js.map +1 -1
- package/dist/ui/actions/tableRowText.js +7 -0
- package/dist/ui/actions/tableRowText.js.map +1 -0
- package/dist/ui/cards/RecordCard.js +146 -0
- package/dist/ui/cards/RecordCard.js.map +1 -0
- package/dist/ui/cards/RecordCardSkeleton.js +38 -0
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -0
- package/dist/ui/countdown/Countdown.js +39 -0
- package/dist/ui/countdown/Countdown.js.map +1 -0
- package/dist/ui/countdown/countdownParts.js +34 -0
- package/dist/ui/countdown/countdownParts.js.map +1 -0
- package/dist/ui/countdown/useCountdown.js +35 -0
- package/dist/ui/countdown/useCountdown.js.map +1 -0
- package/dist/ui/documents/PdfViewerDialog.js +82 -0
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -0
- package/dist/ui/filters/DateRangeFilter.js +3 -2
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/FilterDrawer.js +212 -0
- package/dist/ui/filters/FilterDrawer.js.map +1 -0
- package/dist/ui/filters/PeriodRangeSelector.js +3 -2
- package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +3 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/filters/filterDrawerFields.js +119 -0
- package/dist/ui/filters/filterDrawerFields.js.map +1 -0
- package/dist/ui/help/HelpButton.js +22 -0
- package/dist/ui/help/HelpButton.js.map +1 -0
- package/dist/ui/help/HelpCarouselDialog.js +155 -0
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -0
- package/dist/ui/help/useHelpCarousel.js +25 -0
- package/dist/ui/help/useHelpCarousel.js.map +1 -0
- package/dist/ui/labels/IconBadge.js +82 -0
- package/dist/ui/labels/IconBadge.js.map +1 -0
- package/dist/ui/labels/MarkedText.js +31 -0
- package/dist/ui/labels/MarkedText.js.map +1 -0
- package/dist/ui/labels/Tag.js +3 -2
- package/dist/ui/labels/Tag.js.map +1 -1
- package/dist/ui/labels/markedTextPieces.js +64 -0
- package/dist/ui/labels/markedTextPieces.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +3 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +3 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/DefinitionList.js +3 -2
- package/dist/ui/layout/DefinitionList.js.map +1 -1
- package/dist/ui/layout/DetailHeader.js +3 -2
- package/dist/ui/layout/DetailHeader.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +3 -2
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +3 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/GlassPanel.js +3 -2
- package/dist/ui/layout/GlassPanel.js.map +1 -1
- package/dist/ui/layout/PageHeader.js +3 -2
- package/dist/ui/layout/PageHeader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +3 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/layout/ProfileHeader.js +3 -2
- package/dist/ui/layout/ProfileHeader.js.map +1 -1
- package/dist/ui/layout/ScrollableRow.js +3 -2
- package/dist/ui/layout/ScrollableRow.js.map +1 -1
- package/dist/ui/layout/SectionCard.js +3 -2
- package/dist/ui/layout/SectionCard.js.map +1 -1
- package/dist/ui/metrics/BarList.js +3 -2
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +3 -2
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +3 -2
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +3 -2
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +3 -2
- package/dist/ui/metrics/LineChart.js.map +1 -1
- package/dist/ui/metrics/MetricCard.js +3 -2
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +3 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +7 -4
- package/dist/ui/metrics/chartParts.js.map +1 -1
- package/dist/ui/navigation/BackButton.js +7 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +3 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +3 -2
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/navigation/RouteTabs.js +79 -0
- package/dist/ui/navigation/RouteTabs.js.map +1 -0
- package/dist/ui/navigation/routeTabMatching.js +23 -0
- package/dist/ui/navigation/routeTabMatching.js.map +1 -0
- package/dist/ui/timeline/GroupedTimeline.js +3 -2
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/AttachmentsInput.js +130 -0
- package/dist/upload/AttachmentsInput.js.map +1 -0
- package/dist/upload/FileChip.js +28 -0
- package/dist/upload/FileChip.js.map +1 -0
- package/dist/upload/FileDropzone.js +23 -87
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +115 -0
- package/dist/upload/FileInput.js.map +1 -0
- package/dist/upload/FileUploadInput.js +288 -0
- package/dist/upload/FileUploadInput.js.map +1 -0
- package/dist/upload/ImageInput.js +128 -0
- package/dist/upload/ImageInput.js.map +1 -0
- package/dist/upload/TemplateDownloadButton.js +68 -0
- package/dist/upload/TemplateDownloadButton.js.map +1 -0
- package/dist/upload/fileAppearance.js +21 -0
- package/dist/upload/fileAppearance.js.map +1 -0
- package/dist/upload/fileRules.js +27 -1
- package/dist/upload/fileRules.js.map +1 -1
- package/dist/upload/transferProgress.js +23 -0
- package/dist/upload/transferProgress.js.map +1 -0
- package/dist/upload/uploadMachine.js +8 -8
- package/dist/upload/uploadMachine.js.map +1 -1
- package/dist/upload/uploadWithProgress.js +1 -1
- package/dist/upload/uploadWithProgress.js.map +1 -1
- package/dist/upload/useFilePicker.js +101 -0
- package/dist/upload/useFilePicker.js.map +1 -0
- package/package.json +44 -1
- package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SignInForm.js","sources":["../../../src/forms/auth/SignInForm.jsx"],"sourcesContent":["// El formulario de iniciar sesión (formik): correo, contraseña, «mantener la sesión abierta» y la liga a «¿Olvidó su\n// contraseña?». La pantalla solo dice qué hacer con los datos:\n//\n// <AuthLayout title={t('auth:signIn.title')}>\n// <SignInForm onSubmit={(values) => signIn(values)} forgotPasswordPath=\"/forgot-password\" />\n// </AuthLayout>\n//\n// Si onSubmit truena con el problema del servidor (RFC 9457; un 401 «correo o contraseña incorrectos», un 429 «espere\n// un minuto»), el aviso sale arriba del formulario y lo de cada campo, en su campo (applyServerProblem).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSignInForm con defaultProps y\n// styleOverrides de sus partes: footer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik, useField} from 'formik';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSignInForm)\nconst THEME_NAME = 'CalcitaSignInForm';\nconst SignInFormFooter = piecePart(THEME_NAME, 'Footer', Stack);\n\n/** «Mantener la sesión abierta», conectado a formik. */\nfunction RememberMeField({label}) {\n const [field] = useField({name: 'remember', type: 'checkbox'});\n return <FormControlLabel label={label} control={<Checkbox {...field} />} />;\n}\n\n/**\n * @param {object} props\n * @param {(values: {email: string, password: string, remember: boolean}) => Promise<unknown>} props.onSubmit entra; si\n * el servidor dice que no, truena con su problema\n * @param {string} [props.forgotPasswordPath] sin ella no sale la liga de «¿Olvidó su contraseña?»\n * @param {boolean} [props.showRememberMe] por omisión sí\n * @param {string} [props.initialEmail]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\
|
|
1
|
+
{"version":3,"file":"SignInForm.js","sources":["../../../src/forms/auth/SignInForm.jsx"],"sourcesContent":["// El formulario de iniciar sesión (formik): correo, contraseña, «mantener la sesión abierta» y la liga a «¿Olvidó su\n// contraseña?». La pantalla solo dice qué hacer con los datos:\n//\n// <AuthLayout title={t('auth:signIn.title')}>\n// <SignInForm onSubmit={(values) => signIn(values)} forgotPasswordPath=\"/forgot-password\" />\n// </AuthLayout>\n//\n// Si onSubmit truena con el problema del servidor (RFC 9457; un 401 «correo o contraseña incorrectos», un 429 «espere\n// un minuto»), el aviso sale arriba del formulario y lo de cada campo, en su campo (applyServerProblem).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSignInForm con defaultProps y\n// styleOverrides de sus partes: footer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useMemo} from 'react';\nimport {Form, Formik, useField} from 'formik';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Checkbox from '@mui/material/Checkbox';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSignInForm)\nconst THEME_NAME = 'CalcitaSignInForm';\nconst SignInFormFooter = piecePart(THEME_NAME, 'Footer', Stack);\n\n/** «Mantener la sesión abierta», conectado a formik. */\nfunction RememberMeField({label}) {\n const [field] = useField({name: 'remember', type: 'checkbox'});\n return <FormControlLabel label={label} control={<Checkbox {...field} />} />;\n}\n\n/**\n * @param {object} props\n * @param {(values: {email: string, password: string, remember: boolean}) => Promise<unknown>} props.onSubmit entra; si\n * el servidor dice que no, truena con su problema\n * @param {string} [props.forgotPasswordPath] sin ella no sale la liga de «¿Olvidó su contraseña?»\n * @param {boolean} [props.showRememberMe] por omisión sí\n * @param {string} [props.initialEmail]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nfunction SignInFormContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSignInForm.defaultProps) y encima lo que llega aquí\n const {onSubmit, forgotPasswordPath, showRememberMe = true, initialEmail = '', texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('auth', texts);\n const {Link: RouterLink} = useToolkitNavigation();\n // el esquema se arma al pintar y no al importar: yup toma sus mensajes al armarlo (buildYupLocale ya corrió)\n const schema = useMemo(() => Yup.object({\n email: Yup.string().trim().required().email(),\n password: Yup.string().required(),\n }), []);\n\n const submit = async (values, formikHelpers) => {\n try {\n await onSubmit({...values, email: values.email.trim()});\n } catch (error) {\n applyServerProblem(error, {...formikHelpers, values});\n }\n };\n\n return (\n <Formik initialValues={{email: initialEmail, password: '', remember: false}} validationSchema={schema} onSubmit={submit}>\n <Form noValidate>\n <Stack spacing={2}>\n <FormProblemAlert />\n <FormTextField name=\"email\" label={t('signIn.email')} type=\"email\" autoComplete=\"username\" autoFocus />\n <FormTextField name=\"password\" label={t('signIn.password')} type=\"password\" autoComplete=\"current-password\" />\n <SignInFormFooter direction=\"row\" ownSx={{alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 1}}>\n {showRememberMe ? <RememberMeField label={t('signIn.remember')} /> : <span />}\n {forgotPasswordPath && (\n <Link component={RouterLink} to={forgotPasswordPath} variant=\"body2\">{t('signIn.forgot')}</Link>\n )}\n </SignInFormFooter>\n <SubmitButton fullWidth>{t('signIn.submit')}</SubmitButton>\n </Stack>\n </Form>\n </Formik>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SignInForm = withPieceSlots(SignInFormContent, THEME_NAME);\nexport default SignInForm;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAgCA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,UAAU,KAAK;AAG9D,SAAS,gBAAgB,EAAC,SAAQ;AAC9B,QAAM,CAAC,KAAK,IAAI,SAAS,EAAC,MAAM,YAAY,MAAM,YAAW;AAC7D,SAAO,oBAAC,oBAAiB,OAAc,6BAAU,UAAA,EAAU,GAAG,OAAO,GAAI;AAC7E;AAWA,SAAS,kBAAkB,OAAO;AAE9B,QAAM,EAAC,UAAU,oBAAoB,iBAAiB,MAAM,eAAe,IAAI,OAAO,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AACxI,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAE3B,QAAM,SAAS,QAAQ,MAAM,IAAI,OAAO;AAAA,IACpC,OAAO,IAAI,OAAA,EAAS,OAAO,SAAA,EAAW,MAAA;AAAA,IACtC,UAAU,IAAI,OAAA,EAAS,SAAA;AAAA,EAAS,CACnC,GAAG,CAAA,CAAE;AAEN,QAAM,SAAS,OAAO,QAAQ,kBAAkB;AAC5C,QAAI;AACA,YAAM,SAAS,EAAC,GAAG,QAAQ,OAAO,OAAO,MAAM,KAAA,GAAO;AAAA,IAC1D,SAAS,OAAO;AACZ,yBAAmB,OAAO,EAAC,GAAG,eAAe,QAAO;AAAA,IACxD;AAAA,EACJ;AAEA,SACI,oBAAC,UAAO,eAAe,EAAC,OAAO,cAAc,UAAU,IAAI,UAAU,MAAA,GAAQ,kBAAkB,QAAQ,UAAU,QAC7G,UAAA,oBAAC,MAAA,EAAK,YAAU,MACZ,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,IAAA,oBAAC,kBAAA,EAAiB;AAAA,IAClB,oBAAC,eAAA,EAAc,MAAK,SAAQ,OAAO,EAAE,cAAc,GAAG,MAAK,SAAQ,cAAa,YAAW,WAAS,KAAA,CAAC;AAAA,IACrG,oBAAC,eAAA,EAAc,MAAK,YAAW,OAAO,EAAE,iBAAiB,GAAG,MAAK,YAAW,cAAa,mBAAA,CAAmB;AAAA,IAC5G,qBAAC,kBAAA,EAAiB,WAAU,OAAM,OAAO,EAAC,YAAY,UAAU,gBAAgB,iBAAiB,UAAU,QAAQ,KAAK,KACnH,UAAA;AAAA,MAAA,iBAAiB,oBAAC,mBAAgB,OAAO,EAAE,iBAAiB,GAAG,wBAAM,QAAA,CAAA,CAAK;AAAA,MAC1E,sBACG,oBAAC,MAAA,EAAK,WAAW,YAAY,IAAI,oBAAoB,SAAQ,SAAS,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,IAAA,GAEjG;AAAA,wBACC,cAAA,EAAa,WAAS,MAAE,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,EAAA,EAAA,CAChD,GACJ,GACJ;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ruleMessageOf } from "./validationMessages.js";
|
|
2
|
+
function isEmpty(value) {
|
|
3
|
+
return value === "" || value === null || value === void 0;
|
|
4
|
+
}
|
|
5
|
+
function sameAsFieldRule(otherField, { otherLabel } = {}) {
|
|
6
|
+
const message = otherLabel ? ruleMessageOf("validation:sameAs", "sameAs", { other: otherLabel }) : ruleMessageOf("validation:sameAsUnnamed", "sameAs");
|
|
7
|
+
return {
|
|
8
|
+
name: "sameAs",
|
|
9
|
+
message,
|
|
10
|
+
test: (value, context) => isEmpty(value) || String(value) === String(context?.parent?.[otherField] ?? "")
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function percentRule({ min = 0, max = 100 } = {}) {
|
|
14
|
+
return {
|
|
15
|
+
name: "percent",
|
|
16
|
+
message: ruleMessageOf("validation:percent", "percent", { min, max }),
|
|
17
|
+
test: (value) => {
|
|
18
|
+
if (isEmpty(value)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
const number = Number(String(value).replace(/,/g, ""));
|
|
22
|
+
return Number.isFinite(number) && number >= min && number <= max;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
percentRule,
|
|
28
|
+
sameAsFieldRule
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=fieldRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fieldRules.js","sources":["../../src/forms/fieldRules.js"],"sourcesContent":["// Reglas de yup para un campo, con sus mensajes en el idioma activo:\n//\n// sameAsFieldRule('password', {otherLabel: 'la contraseña'}) igual a otro campo («Confirme la contraseña», la CLABE)\n// percentRule() un porcentaje entre 0 y 100 (o entre min y max)\n//\n// Yup.object({\n// password: Yup.string().required(),\n// password_confirmation: Yup.string().required().test(sameAsFieldRule('password', {otherLabel: 'la contraseña'})),\n// commission: Yup.string().test(percentRule({max: 30})),\n// })\n//\n// Un campo vacío pasa: que sea obligatorio lo dice required(). Son objetos de .test() y no un Yup propio: el proyecto usa su\n// copia de yup y aquí no se importa. Funcionan con formik y con react-hook-form (yupResolver). Cada mensaje lleva su regla:\n// la pantalla (messages del campo) o los textos del proyecto pueden poner el suyo (validationMessages.js).\n//\n// Genérico del paquete: function (regla de la casa). Pruebas en fieldRules.test.js.\nimport {ruleMessageOf} from '@/forms/validationMessages.js';\n\n/**\n * @param {unknown} value\n * @returns {boolean} si viene vacío (lo dice required(), no esta regla)\n */\nfunction isEmpty(value) {\n return value === '' || value === null || value === undefined;\n}\n\n/**\n * Igual a otro campo del mismo formulario (o del mismo renglón de una lista): la confirmación de una contraseña, de una CLABE\n * o de un correo.\n *\n * @param {string} otherField el campo con el que se compara, en el mismo objeto ('password')\n * @param {object} [options]\n * @param {string} [options.otherLabel] cómo se llama ese campo en el mensaje («Debe ser igual a la contraseña»); sin él,\n * «No coincide»\n * @returns {{name: string, message: {key: string, values: object, rule: string}, test: (value: unknown, context: {parent?: object}) => boolean}}\n */\nexport function sameAsFieldRule(otherField, {otherLabel} = {}) {\n const message = otherLabel\n ? ruleMessageOf('validation:sameAs', 'sameAs', {other: otherLabel})\n : ruleMessageOf('validation:sameAsUnnamed', 'sameAs');\n return {\n name: 'sameAs',\n message,\n test: (value, context) => isEmpty(value) || String(value) === String(context?.parent?.[otherField] ?? ''),\n };\n}\n\n/**\n * Un porcentaje: un número entre min y max (por omisión 0 y 100), con decimales. Acepta el texto que deja NumberInput\n * ('12.5') o un número.\n *\n * @param {object} [options]\n * @param {number} [options.min] por omisión 0\n * @param {number} [options.max] por omisión 100\n * @returns {{name: string, message: {key: string, values: object, rule: string}, test: (value: unknown) => boolean}}\n */\nexport function percentRule({min = 0, max = 100} = {}) {\n return {\n name: 'percent',\n message: ruleMessageOf('validation:percent', 'percent', {min, max}),\n test: (value) => {\n if (isEmpty(value)) {\n return true;\n }\n const number = Number(String(value).replace(/,/g, ''));\n return Number.isFinite(number) && number >= min && number <= max;\n },\n };\n}\n"],"names":[],"mappings":";AAsBA,SAAS,QAAQ,OAAO;AACpB,SAAO,UAAU,MAAM,UAAU,QAAQ,UAAU;AACvD;AAYO,SAAS,gBAAgB,YAAY,EAAC,WAAU,IAAI,CAAA,GAAI;AAC3D,QAAM,UAAU,aACV,cAAc,qBAAqB,UAAU,EAAC,OAAO,WAAU,CAAC,IAChE,cAAc,4BAA4B,QAAQ;AACxD,SAAO;AAAA,IACH,MAAM;AAAA,IACN;AAAA,IACA,MAAM,CAAC,OAAO,YAAY,QAAQ,KAAK,KAAK,OAAO,KAAK,MAAM,OAAO,SAAS,SAAS,UAAU,KAAK,EAAE;AAAA,EAChH;AACA;AAWO,SAAS,YAAY,EAAC,MAAM,GAAG,MAAM,IAAG,IAAI,IAAI;AACnD,SAAO;AAAA,IACH,MAAM;AAAA,IACN,SAAS,cAAc,sBAAsB,WAAW,EAAC,KAAK,IAAG,CAAC;AAAA,IAClE,MAAM,CAAC,UAAU;AACb,UAAI,QAAQ,KAAK,GAAG;AAChB,eAAO;AAAA,MACX;AACA,YAAM,SAAS,OAAO,OAAO,KAAK,EAAE,QAAQ,MAAM,EAAE,CAAC;AACrD,aAAO,OAAO,SAAS,MAAM,KAAK,UAAU,OAAO,UAAU;AAAA,IACjE;AAAA,EACR;AACA;"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Money } from "../money/money.js";
|
|
2
|
+
function rowsOf(context, listName) {
|
|
3
|
+
return context.from?.[1]?.value?.[listName] ?? [];
|
|
4
|
+
}
|
|
5
|
+
function numberOf(value) {
|
|
6
|
+
if (value === "" || value === null || value === void 0) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const number = Number(value);
|
|
10
|
+
return Number.isFinite(number) ? number : null;
|
|
11
|
+
}
|
|
12
|
+
function uniqueInListRule(listName, field) {
|
|
13
|
+
return {
|
|
14
|
+
name: "uniqueInList",
|
|
15
|
+
message: { key: "validation:listDuplicate" },
|
|
16
|
+
test: (value, context) => {
|
|
17
|
+
if (value === "" || value === null || value === void 0) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
const firstIndex = rowsOf(context, listName).findIndex((row) => String(row?.[field] ?? "") === String(value));
|
|
21
|
+
if (firstIndex === -1 || firstIndex >= context.options.index) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return context.createError({ message: { key: "validation:listDuplicate", values: { row: firstIndex + 1 } } });
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function orderedInListRule(listName, field, { ascending = true, allowEqual = false, displayOf = (text) => text } = {}) {
|
|
29
|
+
const isInOrder = (lower, higher) => allowEqual ? lower <= higher : lower < higher;
|
|
30
|
+
return {
|
|
31
|
+
name: "orderedInList",
|
|
32
|
+
message: { key: "validation:listOrderBetween" },
|
|
33
|
+
test: (value, context) => {
|
|
34
|
+
const current = numberOf(value);
|
|
35
|
+
if (current === null) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const rows = rowsOf(context, listName);
|
|
39
|
+
const index = context.options.index;
|
|
40
|
+
const hasValue = (rowIndex) => numberOf(rows[rowIndex]?.[field]) !== null;
|
|
41
|
+
let previousIndex = index - 1;
|
|
42
|
+
while (previousIndex >= 0 && !hasValue(previousIndex)) {
|
|
43
|
+
previousIndex -= 1;
|
|
44
|
+
}
|
|
45
|
+
let nextIndex = index + 1;
|
|
46
|
+
while (nextIndex < rows.length && !hasValue(nextIndex)) {
|
|
47
|
+
nextIndex += 1;
|
|
48
|
+
}
|
|
49
|
+
const previous = previousIndex >= 0 ? numberOf(rows[previousIndex][field]) : null;
|
|
50
|
+
const next = nextIndex < rows.length ? numberOf(rows[nextIndex][field]) : null;
|
|
51
|
+
const fitsPrevious = previous === null || (ascending ? isInOrder(previous, current) : isInOrder(current, previous));
|
|
52
|
+
const fitsNext = next === null || (ascending ? isInOrder(current, next) : isInOrder(next, current));
|
|
53
|
+
if (fitsPrevious && fitsNext) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const values = {
|
|
57
|
+
previous: previous === null ? "" : displayOf(String(rows[previousIndex][field])),
|
|
58
|
+
previousRow: previousIndex + 1,
|
|
59
|
+
next: next === null ? "" : displayOf(String(rows[nextIndex][field])),
|
|
60
|
+
nextRow: nextIndex + 1
|
|
61
|
+
};
|
|
62
|
+
let key = "validation:listOrderBetween";
|
|
63
|
+
if (fitsNext) {
|
|
64
|
+
key = ascending ? "validation:listOrderAbovePrevious" : "validation:listOrderBelowPrevious";
|
|
65
|
+
} else if (fitsPrevious) {
|
|
66
|
+
key = ascending ? "validation:listOrderBelowNext" : "validation:listOrderAboveNext";
|
|
67
|
+
}
|
|
68
|
+
return context.createError({ message: { key, values } });
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function listTotalOf(rows, field, { currency } = {}) {
|
|
73
|
+
return (rows ?? []).reduce((total, row) => {
|
|
74
|
+
try {
|
|
75
|
+
const text = String(row?.[field] ?? "").trim();
|
|
76
|
+
return text ? total.plus(Money.fromString(text, { currency })) : total;
|
|
77
|
+
} catch {
|
|
78
|
+
return total;
|
|
79
|
+
}
|
|
80
|
+
}, Money.zero({ currency }));
|
|
81
|
+
}
|
|
82
|
+
function listTotalAtMostRule(field, max, { currency } = {}) {
|
|
83
|
+
return {
|
|
84
|
+
name: "listTotalAtMost",
|
|
85
|
+
message: { key: "validation:listTotalMax" },
|
|
86
|
+
test: (rows, context) => {
|
|
87
|
+
const maxText = typeof max === "function" ? max(context.parent ?? {}) : max;
|
|
88
|
+
if (maxText === "" || maxText === null || maxText === void 0) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
let limit;
|
|
92
|
+
try {
|
|
93
|
+
limit = Money.fromString(String(maxText), { currency });
|
|
94
|
+
} catch {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const total = listTotalOf(Array.isArray(rows) ? rows : [], field, { currency });
|
|
98
|
+
if (!total.isGreaterThan(limit)) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return context.createError({ message: { key: "validation:listTotalMax", values: { total: total.format(), max: limit.format() } } });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
listTotalAtMostRule,
|
|
107
|
+
listTotalOf,
|
|
108
|
+
orderedInListRule,
|
|
109
|
+
uniqueInListRule
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=listRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listRules.js","sources":["../../src/forms/listRules.js"],"sourcesContent":["// Las reglas de yup para una lista de renglones (FormRepeatingFields), con sus mensajes en el idioma activo:\n//\n// uniqueInListRule('tiers', 'min_quantity') sin repetidos en esa columna («Ya está en el renglón 2»)\n// orderedInListRule('tiers', 'min_quantity') de menor a mayor («Debe ir entre 10 (renglón 1) y 50 (renglón 3)»)\n// listTotalAtMostRule('amount', (values) => values.credit_limit, {currency: 'MXN'})\n// lo que suman entre todos no pasa de un tope (con Money, sin floats)\n//\n// Yup.object({\n// tiers: Yup.array().of(Yup.object({\n// min_quantity: Yup.string().required().test(uniqueInListRule('tiers', 'min_quantity'))\n// .test(orderedInListRule('tiers', 'min_quantity')),\n// })),\n// split: Yup.array().of(…).test(listTotalAtMostRule('amount', (values) => values.credit_limit)),\n// })\n//\n// El repetido y el fuera de orden se marcan en SU renglón (no se borra lo que escribió la persona): el primero que aparece\n// se queda bien y el que repite dice con cuál choca. Un campo vacío pasa: que sea obligatorio lo dice required().\n//\n// Son objetos de .test() y no un Yup propio: el proyecto usa su copia de yup y aquí no se importa. Funcionan con formik y\n// con react-hook-form (yupResolver).\n//\n// Genérico del paquete: function (regla de la casa). Pruebas en listRules.test.js.\nimport {Money} from '@/money/money.js';\n\n/**\n * La lista completa desde la regla de un campo de un renglón: yup da en context.from los objetos de arriba (el renglón y\n * el que tiene la lista).\n *\n * @param {{from?: Array<{value: object}>}} context\n * @param {string} listName\n * @returns {object[]}\n */\nfunction rowsOf(context, listName) {\n return context.from?.[1]?.value?.[listName] ?? [];\n}\n\n/**\n * @param {unknown} value\n * @returns {number|null} el número, o null si viene vacío o no es número\n */\nfunction numberOf(value) {\n if (value === '' || value === null || value === undefined) {\n return null;\n }\n const number = Number(value);\n return Number.isFinite(number) ? number : null;\n}\n\n/**\n * Sin repetidos en una columna: el renglón que repite dice con cuál choca.\n *\n * @param {string} listName la lista en el formulario ('tiers')\n * @param {string} field la columna ('min_quantity')\n * @returns {import('yup').TestConfig<any, any>} para .test() de yup\n */\nexport function uniqueInListRule(listName, field) {\n return {\n name: 'uniqueInList',\n message: {key: 'validation:listDuplicate'},\n test: (value, context) => {\n if (value === '' || value === null || value === undefined) {\n return true;\n }\n const firstIndex = rowsOf(context, listName).findIndex((row) => String(row?.[field] ?? '') === String(value));\n if (firstIndex === -1 || firstIndex >= context.options.index) {\n return true;\n }\n return context.createError({message: {key: 'validation:listDuplicate', values: {row: firstIndex + 1}}});\n },\n };\n}\n\n/**\n * En orden por una columna, comparado con el renglón de arriba y el de abajo que tengan valor.\n *\n * @param {string} listName\n * @param {string} field\n * @param {{ascending?: boolean, allowEqual?: boolean, displayOf?: (text: string) => string}} [options] ascending: de menor\n * a mayor (por omisión); displayOf: cómo se dice el valor del vecino en el mensaje (p. ej. con %)\n * @returns {import('yup').TestConfig<any, any>} para .test() de yup\n */\nexport function orderedInListRule(listName, field, {ascending = true, allowEqual = false, displayOf = (text) => text} = {}) {\n const isInOrder = (lower, higher) => (allowEqual ? lower <= higher : lower < higher);\n return {\n name: 'orderedInList',\n message: {key: 'validation:listOrderBetween'},\n test: (value, context) => {\n const current = numberOf(value);\n if (current === null) {\n return true;\n }\n const rows = rowsOf(context, listName);\n const index = context.options.index;\n const hasValue = (rowIndex) => numberOf(rows[rowIndex]?.[field]) !== null;\n let previousIndex = index - 1;\n while (previousIndex >= 0 && !hasValue(previousIndex)) {\n previousIndex -= 1;\n }\n let nextIndex = index + 1;\n while (nextIndex < rows.length && !hasValue(nextIndex)) {\n nextIndex += 1;\n }\n const previous = previousIndex >= 0 ? numberOf(rows[previousIndex][field]) : null;\n const next = nextIndex < rows.length ? numberOf(rows[nextIndex][field]) : null;\n const fitsPrevious = previous === null || (ascending ? isInOrder(previous, current) : isInOrder(current, previous));\n const fitsNext = next === null || (ascending ? isInOrder(current, next) : isInOrder(next, current));\n if (fitsPrevious && fitsNext) {\n return true;\n }\n const values = {\n previous: previous === null ? '' : displayOf(String(rows[previousIndex][field])), previousRow: previousIndex + 1,\n next: next === null ? '' : displayOf(String(rows[nextIndex][field])), nextRow: nextIndex + 1,\n };\n // entre los dos si choca con ambos; si no, solo con el que choca (de menor a mayor: mayor que el de arriba)\n let key = 'validation:listOrderBetween';\n if (fitsNext) {\n key = ascending ? 'validation:listOrderAbovePrevious' : 'validation:listOrderBelowPrevious';\n } else if (fitsPrevious) {\n key = ascending ? 'validation:listOrderBelowNext' : 'validation:listOrderAboveNext';\n }\n return context.createError({message: {key, values}});\n },\n };\n}\n\n/**\n * Lo que suma una columna, con Money (sin errores de decimales). Los vacíos cuentan como cero y los que no son número no\n * cuentan (su propio campo ya lo dice).\n *\n * @param {object[]} rows\n * @param {string} field\n * @param {{currency?: string}} [options]\n * @returns {Money}\n */\nexport function listTotalOf(rows, field, {currency} = {}) {\n return (rows ?? []).reduce((total, row) => {\n try {\n const text = String(row?.[field] ?? '').trim();\n return text ? total.plus(Money.fromString(text, {currency})) : total;\n } catch {\n return total;\n }\n }, Money.zero({currency}));\n}\n\n/**\n * Lo que suman entre todos no pasa de un tope (el reparto de un presupuesto, de un límite de crédito). Va en la lista:\n * Yup.array().test(listTotalAtMostRule(…)); el mensaje sale abajo de la lista.\n *\n * @param {string} field la columna que se suma\n * @param {number|string|((parent: object) => number|string|null)} max el tope, o cómo sacarlo de los demás valores del\n * formulario (el límite que se escribió arriba)\n * @param {{currency?: string}} [options]\n * @returns {import('yup').TestConfig<any, any>} para .test() de yup\n */\nexport function listTotalAtMostRule(field, max, {currency} = {}) {\n return {\n name: 'listTotalAtMost',\n message: {key: 'validation:listTotalMax'},\n test: (rows, context) => {\n const maxText = typeof max === 'function' ? max(context.parent ?? {}) : max;\n if (maxText === '' || maxText === null || maxText === undefined) {\n return true;\n }\n let limit;\n try {\n limit = Money.fromString(String(maxText), {currency});\n } catch {\n return true;\n }\n const total = listTotalOf(Array.isArray(rows) ? rows : [], field, {currency});\n if (!total.isGreaterThan(limit)) {\n return true;\n }\n return context.createError({message: {key: 'validation:listTotalMax', values: {total: total.format(), max: limit.format()}}});\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ import { applyServerProblem } from "../applyServerProblem.js";
|
|
|
16
16
|
import * as Yup from "yup";
|
|
17
17
|
import { TOTP_CODE_MIN_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_PATTERN, RECOVERY_CODE_MIN_LENGTH, RECOVERY_CODE_MAX_LENGTH, RECOVERY_CODE_PATTERN, normalizeTotpCode, normalizeRecoveryCode } from "./securityRules.js";
|
|
18
18
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
19
|
-
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
19
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
20
20
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
21
21
|
const THEME_NAME = "CalcitaSecondStepForm";
|
|
22
22
|
const SecondStepActions = piecePart(THEME_NAME, "Actions", Stack);
|
|
@@ -26,7 +26,7 @@ const ACCOUNT_LOCKED_CODE = "account_locked";
|
|
|
26
26
|
function isOutOfAttempts(error) {
|
|
27
27
|
return error?.code === ACCOUNT_LOCKED_CODE || error?.status === UNAUTHENTICATED_STATUS && !Number.isInteger(error?.problem?.remaining_attempts);
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function SecondStepFormContent(props) {
|
|
30
30
|
const { methods, canUsePasskeys = false, onUsePasskey, onSubmitCode, onCancel, footer, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
31
31
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
32
32
|
const { t } = useToolkitTranslation("security", texts);
|
|
@@ -121,6 +121,7 @@ function SecondStepForm(props) {
|
|
|
121
121
|
] })
|
|
122
122
|
] });
|
|
123
123
|
}
|
|
124
|
+
const SecondStepForm = withPieceSlots(SecondStepFormContent, THEME_NAME);
|
|
124
125
|
export {
|
|
125
126
|
SECOND_STEP_METHOD,
|
|
126
127
|
SecondStepForm as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecondStepForm.js","sources":["../../../src/forms/security/SecondStepForm.jsx"],"sourcesContent":["// El segundo paso al entrar: la contraseña fue correcta y la cuenta tiene verificación en dos pasos.\n//\n// Los métodos van en el orden de su fuerza: primero la llave de acceso (no se la roba una página falsa), luego el código de\n// la app, y el de recuperación detrás de un enlace (es para emergencias y se gasta). Cada código equivocado cuenta para el\n// bloqueo de la cuenta: el servidor dice cuántos intentos quedan (problem.remaining_attempts) y se le dice a la persona.\n// Si se acabaron los intentos o se venció el tiempo (un 401 sin intentos, o code account_locked), se regresa a empezar.\n//\n// La pantalla no sabe del servidor: el proyecto le pasa qué hacer.\n//\n// <SecondStepForm methods={['passkey', 'totp', 'recoveryCode']} canUsePasskeys={passkeysSupported()}\n// onUsePasskey={async () => finishWith(await api.post('/auth/second-step/passkey',\n// await signWithPasskey(await api.get('/auth/second-step/passkey-options'))))}\n// onSubmitCode={async ({method, code}) => finishWith(await api.post(`/auth/second-step/${method}`, {code}))}\n// onCancel={(message) => backToSignIn(message)} />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSecondStepForm con defaultProps y\n// styleOverrides de sus partes: actions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n RECOVERY_CODE_MAX_LENGTH, RECOVERY_CODE_MIN_LENGTH, RECOVERY_CODE_PATTERN, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN,\n normalizeRecoveryCode, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSecondStepForm)\nconst THEME_NAME = 'CalcitaSecondStepForm';\nconst SecondStepActions = piecePart(THEME_NAME, 'Actions', Stack);\n\n/** Las formas de confirmar el segundo paso, como las manda el servidor. */\nexport const SECOND_STEP_METHOD = Object.freeze({passkey: 'passkey', totp: 'totp', recoveryCode: 'recoveryCode'});\n\nconst UNAUTHENTICATED_STATUS = 401;\nconst ACCOUNT_LOCKED_CODE = 'account_locked';\n\n/**\n * ¿Se acabó la oportunidad (bloqueo o se venció el tiempo)? Entonces hay que empezar de nuevo desde la contraseña.\n *\n * @param {Error & {status?: number, code?: string, problem?: object}} error\n * @returns {boolean}\n */\nfunction isOutOfAttempts(error) {\n return error?.code === ACCOUNT_LOCKED_CODE\n || (error?.status === UNAUTHENTICATED_STATUS && !Number.isInteger(error?.problem?.remaining_attempts));\n}\n\n/**\n * @param {object} props\n * @param {string[]} props.methods los que tiene la cuenta (SECOND_STEP_METHOD), como los mandó el servidor\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys\n * @param {() => Promise<unknown>} [props.onUsePasskey] firma con la llave y termina de entrar; truena si no se pudo\n * @param {(input: {method: string, code: string}) => Promise<unknown>} props.onSubmitCode manda el código ya limpio y\n * termina de entrar; truena con el ApiError si no\n * @param {(message: string|null) => void} props.onCancel regresa a escribir la contraseña (con el motivo, si lo hay)\n * @param {import('react').ReactNode} [props.footer] abajo del botón, dentro del formulario: puede llenar el código con\n * useFormikContext (p. ej. el atajo de los ejemplos)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function SecondStepForm(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSecondStepForm.defaultProps) y encima lo que llega aquí\n const {methods, canUsePasskeys = false, onUsePasskey, onSubmitCode, onCancel, footer, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('security', texts);\n const hasApp = methods.includes(SECOND_STEP_METHOD.totp);\n const hasRecovery = methods.includes(SECOND_STEP_METHOD.recoveryCode);\n const hasPasskey = methods.includes(SECOND_STEP_METHOD.passkey) && canUsePasskeys && Boolean(onUsePasskey);\n const [method, setMethod] = useState(hasApp ? SECOND_STEP_METHOD.totp : SECOND_STEP_METHOD.recoveryCode);\n const [passkeyError, setPasskeyError] = useState(null);\n const [isUsingPasskey, setIsUsingPasskey] = useState(false);\n const isApp = method === SECOND_STEP_METHOD.totp;\n\n // el esquema se arma al pintar: yup toma sus mensajes al armarlo\n const schema = isApp\n ? Yup.object({code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat'))})\n : Yup.object({code: Yup.string().trim().required(t('secondStep.recoveryCodeRequired')).min(RECOVERY_CODE_MIN_LENGTH)\n .max(RECOVERY_CODE_MAX_LENGTH).matches(RECOVERY_CODE_PATTERN, t('secondStep.recoveryCodeFormat'))});\n\n const usePasskey = async () => {\n setPasskeyError(null);\n setIsUsingPasskey(true);\n try {\n await onUsePasskey();\n } catch (error) {\n // cancelar el aviso del sistema no es un error que enseñar\n if (!error?.cancelled) {\n setPasskeyError(error?.message);\n }\n } finally {\n setIsUsingPasskey(false);\n }\n };\n\n const submit = async (values, formikHelpers) => {\n formikHelpers.setStatus(null);\n try {\n const code = isApp ? normalizeTotpCode(values.code) : normalizeRecoveryCode(values.code);\n await onSubmitCode({method, code});\n } catch (error) {\n formikHelpers.setFieldValue('code', '', false);\n if (isOutOfAttempts(error)) {\n onCancel(error?.message ?? null);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n // cuántos intentos quedan, junto al mensaje del servidor\n const remainingAttempts = error?.problem?.remaining_attempts;\n if (Number.isInteger(remainingAttempts)) {\n formikHelpers.setStatus(`${error.message} ${t('secondStep.remainingAttempts', {count: remainingAttempts})}`);\n }\n }\n };\n\n return (\n <Stack spacing={2.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('secondStep.intro')}</Typography>\n\n {hasPasskey && (\n <>\n <Button variant=\"contained\" size=\"large\" startIcon={<FingerprintOutlinedIcon />} onClick={usePasskey} disabled={isUsingPasskey}>\n {t('secondStep.usePasskey')}\n </Button>\n {passkeyError && <Alert severity=\"error\" role=\"alert\">{passkeyError}</Alert>}\n {(hasApp || hasRecovery) && <Divider>{t('secondStep.or')}</Divider>}\n </>\n )}\n\n {(hasApp || hasRecovery) && (\n // key: al cambiar de método, el formulario empieza limpio\n <Formik key={method} initialValues={{code: ''}} validationSchema={schema} onSubmit={submit}>\n <Stack component={Form} noValidate spacing={2}>\n <FormProblemAlert />\n {isApp ? (\n <FormTextField name=\"code\" label={t('secondStep.appCodeLabel')} autoFocus autoComplete=\"one-time-code\" clearable={false}\n helperText={t('secondStep.appCodeHelp')}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n ) : (\n <FormTextField name=\"code\" label={t('secondStep.recoveryCodeLabel')} autoFocus autoComplete=\"off\" clearable={false}\n helperText={t('secondStep.recoveryCodeHelp')} slotProps={{htmlInput: {maxLength: RECOVERY_CODE_MAX_LENGTH}}} />\n )}\n <SubmitButton fullWidth>{t('secondStep.confirm')}</SubmitButton>\n {footer}\n </Stack>\n </Formik>\n )}\n {!hasApp && !hasRecovery && footer}\n\n <SecondStepActions spacing={0.5} ownSx={{alignItems: 'center'}}>\n {hasApp && hasRecovery && (\n <Link component=\"button\" type=\"button\" variant=\"body2\"\n onClick={() => setMethod(isApp ? SECOND_STEP_METHOD.recoveryCode : SECOND_STEP_METHOD.totp)}>\n {isApp ? t('secondStep.useRecovery') : t('secondStep.useApp')}\n </Link>\n )}\n <Link component=\"button\" type=\"button\" variant=\"body2\" onClick={() => onCancel(null)}>{t('secondStep.otherAccount')}</Link>\n </SecondStepActions>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,MAAM,aAAa;AACnB,MAAM,oBAAoB,UAAU,YAAY,WAAW,KAAK;AAGzD,MAAM,qBAAqB,OAAO,OAAO,EAAC,SAAS,WAAW,MAAM,QAAQ,cAAc,eAAA,CAAe;AAEhH,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;AAQ5B,SAAS,gBAAgB,OAAO;AAC5B,SAAO,OAAO,SAAS,uBACf,OAAO,WAAW,0BAA0B,CAAC,OAAO,UAAU,OAAO,SAAS,kBAAkB;AAC5G;AAcA,SAAwB,eAAe,OAAO;AAE1C,QAAM,EAAC,SAAS,iBAAiB,OAAO,cAAc,cAAc,UAAU,QAAQ,OAAO,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC/I,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,YAAY,KAAK;AACnD,QAAM,SAAS,QAAQ,SAAS,mBAAmB,IAAI;AACvD,QAAM,cAAc,QAAQ,SAAS,mBAAmB,YAAY;AACpE,QAAM,aAAa,QAAQ,SAAS,mBAAmB,OAAO,KAAK,kBAAkB,QAAQ,YAAY;AACzG,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,SAAS,mBAAmB,OAAO,mBAAmB,YAAY;AACvG,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,IAAI;AACrD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAC1D,QAAM,QAAQ,WAAW,mBAAmB;AAG5C,QAAM,SAAS,QACT,IAAI,OAAO,EAAC,MAAM,IAAI,OAAA,EAAS,KAAA,EAAO,SAAS,EAAE,sBAAsB,CAAC,EAAE,IAAI,oBAAoB,EAAE,IAAI,oBAAoB,EACzH,QAAQ,mBAAmB,EAAE,oBAAoB,CAAC,EAAA,CAAE,IACvD,IAAI,OAAO,EAAC,MAAM,IAAI,OAAA,EAAS,KAAA,EAAO,SAAS,EAAE,iCAAiC,CAAC,EAAE,IAAI,wBAAwB,EAC9G,IAAI,wBAAwB,EAAE,QAAQ,uBAAuB,EAAE,+BAA+B,CAAC,EAAA,CAAE;AAE1G,QAAM,aAAa,YAAY;AAC3B,oBAAgB,IAAI;AACpB,sBAAkB,IAAI;AACtB,QAAI;AACA,YAAM,aAAA;AAAA,IACV,SAAS,OAAO;AAEZ,UAAI,CAAC,OAAO,WAAW;AACnB,wBAAgB,OAAO,OAAO;AAAA,MAClC;AAAA,IACJ,UAAA;AACI,wBAAkB,KAAK;AAAA,IAC3B;AAAA,EACJ;AAEA,QAAM,SAAS,OAAO,QAAQ,kBAAkB;AAC5C,kBAAc,UAAU,IAAI;AAC5B,QAAI;AACA,YAAM,OAAO,QAAQ,kBAAkB,OAAO,IAAI,IAAI,sBAAsB,OAAO,IAAI;AACvF,YAAM,aAAa,EAAC,QAAQ,MAAK;AAAA,IACrC,SAAS,OAAO;AACZ,oBAAc,cAAc,QAAQ,IAAI,KAAK;AAC7C,UAAI,gBAAgB,KAAK,GAAG;AACxB,iBAAS,OAAO,WAAW,IAAI;AAC/B;AAAA,MACJ;AACA,yBAAmB,OAAO,EAAC,GAAG,eAAe,QAAO;AAEpD,YAAM,oBAAoB,OAAO,SAAS;AAC1C,UAAI,OAAO,UAAU,iBAAiB,GAAG;AACrC,sBAAc,UAAU,GAAG,MAAM,OAAO,IAAI,EAAE,gCAAgC,EAAC,OAAO,kBAAA,CAAkB,CAAC,EAAE;AAAA,MAC/G;AAAA,IACJ;AAAA,EACJ;AAEA,SACI,qBAAC,OAAA,EAAM,SAAS,KACZ,UAAA;AAAA,IAAA,oBAAC,cAAW,SAAQ,SAAQ,OAAM,kBAAkB,UAAA,EAAE,kBAAkB,GAAE;AAAA,IAEzE,cACG,qBAAA,UAAA,EACI,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,MAAK,SAAQ,WAAW,oBAAC,yBAAA,CAAA,CAAwB,GAAI,SAAS,YAAY,UAAU,gBAC3G,UAAA,EAAE,uBAAuB,GAC9B;AAAA,MACC,gBAAgB,oBAAC,OAAA,EAAM,UAAS,SAAQ,MAAK,SAAS,UAAA,cAAa;AAAA,OAClE,UAAU,gBAAgB,oBAAC,SAAA,EAAS,UAAA,EAAE,eAAe,EAAA,CAAE;AAAA,IAAA,GAC7D;AAAA,KAGF,UAAU;AAAA,wBAEP,QAAA,EAAoB,eAAe,EAAC,MAAM,MAAK,kBAAkB,QAAQ,UAAU,QAChF,+BAAC,OAAA,EAAM,WAAW,MAAM,YAAU,MAAC,SAAS,GACxC,UAAA;AAAA,MAAA,oBAAC,kBAAA,EAAiB;AAAA,MACjB,QACG;AAAA,QAAC;AAAA,QAAA;AAAA,UAAc,MAAK;AAAA,UAAO,OAAO,EAAE,yBAAyB;AAAA,UAAG,WAAS;AAAA,UAAC,cAAa;AAAA,UAAgB,WAAW;AAAA,UACnG,YAAY,EAAE,wBAAwB;AAAA,UACtC,WAAW,EAAC,WAAW,EAAC,WAAW,WAAW,WAAW,uBAAoB;AAAA,QAAC;AAAA,MAAA,IAE7F;AAAA,QAAC;AAAA,QAAA;AAAA,UAAc,MAAK;AAAA,UAAO,OAAO,EAAE,8BAA8B;AAAA,UAAG,WAAS;AAAA,UAAC,cAAa;AAAA,UAAM,WAAW;AAAA,UAC9F,YAAY,EAAE,6BAA6B;AAAA,UAAG,WAAW,EAAC,WAAW,EAAC,WAAW,2BAAwB;AAAA,QAAC;AAAA,MAAA;AAAA,0BAE5H,cAAA,EAAa,WAAS,MAAE,UAAA,EAAE,oBAAoB,GAAE;AAAA,MAChD;AAAA,IAAA,EAAA,CACL,KAbS,MAcb;AAAA,IAEH,CAAC,UAAU,CAAC,eAAe;AAAA,IAE5B,qBAAC,qBAAkB,SAAS,KAAK,OAAO,EAAC,YAAY,YAChD,UAAA;AAAA,MAAA,UAAU,eACP;AAAA,QAAC;AAAA,QAAA;AAAA,UAAK,WAAU;AAAA,UAAS,MAAK;AAAA,UAAS,SAAQ;AAAA,UACzC,SAAS,MAAM,UAAU,QAAQ,mBAAmB,eAAe,mBAAmB,IAAI;AAAA,UAC3F,UAAA,QAAQ,EAAE,wBAAwB,IAAI,EAAE,mBAAmB;AAAA,QAAA;AAAA,MAAA;AAAA,MAGpE,oBAAC,MAAA,EAAK,WAAU,UAAS,MAAK,UAAS,SAAQ,SAAQ,SAAS,MAAM,SAAS,IAAI,GAAI,UAAA,EAAE,yBAAyB,EAAA,CAAE;AAAA,IAAA,EAAA,CACxH;AAAA,EAAA,GACJ;AAER;"}
|
|
1
|
+
{"version":3,"file":"SecondStepForm.js","sources":["../../../src/forms/security/SecondStepForm.jsx"],"sourcesContent":["// El segundo paso al entrar: la contraseña fue correcta y la cuenta tiene verificación en dos pasos.\n//\n// Los métodos van en el orden de su fuerza: primero la llave de acceso (no se la roba una página falsa), luego el código de\n// la app, y el de recuperación detrás de un enlace (es para emergencias y se gasta). Cada código equivocado cuenta para el\n// bloqueo de la cuenta: el servidor dice cuántos intentos quedan (problem.remaining_attempts) y se le dice a la persona.\n// Si se acabaron los intentos o se venció el tiempo (un 401 sin intentos, o code account_locked), se regresa a empezar.\n//\n// La pantalla no sabe del servidor: el proyecto le pasa qué hacer.\n//\n// <SecondStepForm methods={['passkey', 'totp', 'recoveryCode']} canUsePasskeys={passkeysSupported()}\n// onUsePasskey={async () => finishWith(await api.post('/auth/second-step/passkey',\n// await signWithPasskey(await api.get('/auth/second-step/passkey-options'))))}\n// onSubmitCode={async ({method, code}) => finishWith(await api.post(`/auth/second-step/${method}`, {code}))}\n// onCancel={(message) => backToSignIn(message)} />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSecondStepForm con defaultProps y\n// styleOverrides de sus partes: actions.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\nimport Link from '@mui/material/Link';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\n\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n RECOVERY_CODE_MAX_LENGTH, RECOVERY_CODE_MIN_LENGTH, RECOVERY_CODE_PATTERN, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN,\n normalizeRecoveryCode, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSecondStepForm)\nconst THEME_NAME = 'CalcitaSecondStepForm';\nconst SecondStepActions = piecePart(THEME_NAME, 'Actions', Stack);\n\n/** Las formas de confirmar el segundo paso, como las manda el servidor. */\nexport const SECOND_STEP_METHOD = Object.freeze({passkey: 'passkey', totp: 'totp', recoveryCode: 'recoveryCode'});\n\nconst UNAUTHENTICATED_STATUS = 401;\nconst ACCOUNT_LOCKED_CODE = 'account_locked';\n\n/**\n * ¿Se acabó la oportunidad (bloqueo o se venció el tiempo)? Entonces hay que empezar de nuevo desde la contraseña.\n *\n * @param {Error & {status?: number, code?: string, problem?: object}} error\n * @returns {boolean}\n */\nfunction isOutOfAttempts(error) {\n return error?.code === ACCOUNT_LOCKED_CODE\n || (error?.status === UNAUTHENTICATED_STATUS && !Number.isInteger(error?.problem?.remaining_attempts));\n}\n\n/**\n * @param {object} props\n * @param {string[]} props.methods los que tiene la cuenta (SECOND_STEP_METHOD), como los mandó el servidor\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys\n * @param {() => Promise<unknown>} [props.onUsePasskey] firma con la llave y termina de entrar; truena si no se pudo\n * @param {(input: {method: string, code: string}) => Promise<unknown>} props.onSubmitCode manda el código ya limpio y\n * termina de entrar; truena con el ApiError si no\n * @param {(message: string|null) => void} props.onCancel regresa a escribir la contraseña (con el motivo, si lo hay)\n * @param {import('react').ReactNode} [props.footer] abajo del botón, dentro del formulario: puede llenar el código con\n * useFormikContext (p. ej. el atajo de los ejemplos)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nfunction SecondStepFormContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSecondStepForm.defaultProps) y encima lo que llega aquí\n const {methods, canUsePasskeys = false, onUsePasskey, onSubmitCode, onCancel, footer, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('security', texts);\n const hasApp = methods.includes(SECOND_STEP_METHOD.totp);\n const hasRecovery = methods.includes(SECOND_STEP_METHOD.recoveryCode);\n const hasPasskey = methods.includes(SECOND_STEP_METHOD.passkey) && canUsePasskeys && Boolean(onUsePasskey);\n const [method, setMethod] = useState(hasApp ? SECOND_STEP_METHOD.totp : SECOND_STEP_METHOD.recoveryCode);\n const [passkeyError, setPasskeyError] = useState(null);\n const [isUsingPasskey, setIsUsingPasskey] = useState(false);\n const isApp = method === SECOND_STEP_METHOD.totp;\n\n // el esquema se arma al pintar: yup toma sus mensajes al armarlo\n const schema = isApp\n ? Yup.object({code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat'))})\n : Yup.object({code: Yup.string().trim().required(t('secondStep.recoveryCodeRequired')).min(RECOVERY_CODE_MIN_LENGTH)\n .max(RECOVERY_CODE_MAX_LENGTH).matches(RECOVERY_CODE_PATTERN, t('secondStep.recoveryCodeFormat'))});\n\n const usePasskey = async () => {\n setPasskeyError(null);\n setIsUsingPasskey(true);\n try {\n await onUsePasskey();\n } catch (error) {\n // cancelar el aviso del sistema no es un error que enseñar\n if (!error?.cancelled) {\n setPasskeyError(error?.message);\n }\n } finally {\n setIsUsingPasskey(false);\n }\n };\n\n const submit = async (values, formikHelpers) => {\n formikHelpers.setStatus(null);\n try {\n const code = isApp ? normalizeTotpCode(values.code) : normalizeRecoveryCode(values.code);\n await onSubmitCode({method, code});\n } catch (error) {\n formikHelpers.setFieldValue('code', '', false);\n if (isOutOfAttempts(error)) {\n onCancel(error?.message ?? null);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n // cuántos intentos quedan, junto al mensaje del servidor\n const remainingAttempts = error?.problem?.remaining_attempts;\n if (Number.isInteger(remainingAttempts)) {\n formikHelpers.setStatus(`${error.message} ${t('secondStep.remainingAttempts', {count: remainingAttempts})}`);\n }\n }\n };\n\n return (\n <Stack spacing={2.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('secondStep.intro')}</Typography>\n\n {hasPasskey && (\n <>\n <Button variant=\"contained\" size=\"large\" startIcon={<FingerprintOutlinedIcon />} onClick={usePasskey} disabled={isUsingPasskey}>\n {t('secondStep.usePasskey')}\n </Button>\n {passkeyError && <Alert severity=\"error\" role=\"alert\">{passkeyError}</Alert>}\n {(hasApp || hasRecovery) && <Divider>{t('secondStep.or')}</Divider>}\n </>\n )}\n\n {(hasApp || hasRecovery) && (\n // key: al cambiar de método, el formulario empieza limpio\n <Formik key={method} initialValues={{code: ''}} validationSchema={schema} onSubmit={submit}>\n <Stack component={Form} noValidate spacing={2}>\n <FormProblemAlert />\n {isApp ? (\n <FormTextField name=\"code\" label={t('secondStep.appCodeLabel')} autoFocus autoComplete=\"one-time-code\" clearable={false}\n helperText={t('secondStep.appCodeHelp')}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n ) : (\n <FormTextField name=\"code\" label={t('secondStep.recoveryCodeLabel')} autoFocus autoComplete=\"off\" clearable={false}\n helperText={t('secondStep.recoveryCodeHelp')} slotProps={{htmlInput: {maxLength: RECOVERY_CODE_MAX_LENGTH}}} />\n )}\n <SubmitButton fullWidth>{t('secondStep.confirm')}</SubmitButton>\n {footer}\n </Stack>\n </Formik>\n )}\n {!hasApp && !hasRecovery && footer}\n\n <SecondStepActions spacing={0.5} ownSx={{alignItems: 'center'}}>\n {hasApp && hasRecovery && (\n <Link component=\"button\" type=\"button\" variant=\"body2\"\n onClick={() => setMethod(isApp ? SECOND_STEP_METHOD.recoveryCode : SECOND_STEP_METHOD.totp)}>\n {isApp ? t('secondStep.useRecovery') : t('secondStep.useApp')}\n </Link>\n )}\n <Link component=\"button\" type=\"button\" variant=\"body2\" onClick={() => onCancel(null)}>{t('secondStep.otherAccount')}</Link>\n </SecondStepActions>\n </Stack>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SecondStepForm = withPieceSlots(SecondStepFormContent, THEME_NAME);\nexport default SecondStepForm;\n"],"names":[],"mappings":"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"}
|
|
@@ -37,7 +37,7 @@ import CopyButton from "../../ui/actions/CopyButton.js";
|
|
|
37
37
|
import Tag from "../../ui/labels/Tag.js";
|
|
38
38
|
import SectionCard from "../../ui/layout/SectionCard.js";
|
|
39
39
|
import ComponentTexts from "../../toolkit/ComponentTexts.js";
|
|
40
|
-
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
40
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
41
41
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
42
42
|
const THEME_NAME = "CalcitaTwoStepVerification";
|
|
43
43
|
const TwoStepQr = piecePart(THEME_NAME, "Qr", Box);
|
|
@@ -200,7 +200,7 @@ function SettingRow({ Icon, title, caption, captionColor = "text.secondary", act
|
|
|
200
200
|
action
|
|
201
201
|
] });
|
|
202
202
|
}
|
|
203
|
-
function
|
|
203
|
+
function TwoStepVerificationCardContent(props) {
|
|
204
204
|
const { security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
205
205
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
206
206
|
const { t } = useToolkitTranslation("security", texts);
|
|
@@ -344,6 +344,7 @@ function TwoStepVerificationCard(props) {
|
|
|
344
344
|
/* @__PURE__ */ jsx(RecoveryCodesDialog, { codes: recoveryCodes, account, onClose: () => setRecoveryCodes(null) })
|
|
345
345
|
] }) });
|
|
346
346
|
}
|
|
347
|
+
const TwoStepVerificationCard = withPieceSlots(TwoStepVerificationCardContent, THEME_NAME);
|
|
347
348
|
export {
|
|
348
349
|
TwoStepVerificationCard as default
|
|
349
350
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwoStepVerificationCard.js","sources":["../../../src/forms/security/TwoStepVerificationCard.jsx"],"sourcesContent":["// «Verificación en dos pasos» de «Mi perfil»: las llaves de acceso, la app autenticadora y los códigos de recuperación.\n//\n// Lo que QUITA protección (desactivar la app, quitar una llave, generar códigos nuevos) pide la contraseña: una sesión que\n// alguien dejó abierta no debe bastar para desarmar la cuenta. Activar no la pide.\n//\n// La tarjeta no sabe del servidor: el proyecto le pasa el estado (security) y qué hacer en cada caso (actions); cada\n// acción regresa {security, recoveryCodes?} y la tarjeta avisa el estado nuevo con onSecurityChange. Las llaves de acceso\n// las crea el proyecto con createPasskey (@stackcraftmx/calcita/passkeys) dentro de actions.addPasskey: así esta puerta no\n// carga la librería de llaves.\n//\n// <TwoStepVerificationCard security={security} onSecurityChange={setSecurity} account={session.email}\n// canUsePasskeys={passkeysSupported()} actions={securityActions} />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTwoStepVerification con defaultProps y\n// styleOverrides de sus partes: qr, secret, secretCode, header, headerTitle, headerText, menuIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport VpnKeyOutlinedIcon from '@mui/icons-material/VpnKeyOutlined';\n\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n CURRENT_PASSWORD_MAX_LENGTH, PASSKEY_NAME_MAX_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport RecoveryCodesDialog from '@/security/RecoveryCodesDialog.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaTwoStepVerification)\nconst THEME_NAME = 'CalcitaTwoStepVerification';\nconst TwoStepQr = piecePart(THEME_NAME, 'Qr', Box);\nconst TwoStepSecret = piecePart(THEME_NAME, 'Secret', Stack);\nconst TwoStepSecretCode = piecePart(THEME_NAME, 'SecretCode', Box);\nconst TwoStepHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst TwoStepHeaderTitle = piecePart(THEME_NAME, 'HeaderTitle', Stack);\nconst TwoStepHeaderText = piecePart(THEME_NAME, 'HeaderText', Box);\nconst TwoStepMenuIcon = piecePart(THEME_NAME, 'MenuIcon', ListItemIcon);\n\n// los códigos que quedan desde donde se avisa en ámbar que ya son pocos\nconst FEW_CODES_LEFT = 2;\nconst QR_SIZE_PIXELS = 200;\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * Lo que hace un formulario de la tarjeta con un error: un ApiError va a su campo o arriba (applyServerProblem); un\n * error del navegador (la llave de acceso) va arriba; si la persona solo canceló, nada.\n *\n * @param {Error & {cancelled?: boolean, status?: number}} error\n * @param {object} formikHelpers\n * @param {object} values\n */\nfunction showFormError(error, formikHelpers, values) {\n if (error?.cancelled) {\n return;\n }\n if (error?.status === undefined) {\n formikHelpers.setStatus(error?.message);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n}\n\n/** Confirmar con la contraseña antes de quitar protección. */\nfunction PasswordDialog({open, title, description, confirmLabel, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const passwordSchema = Yup.object({password: Yup.string().max(CURRENT_PASSWORD_MAX_LENGTH).required(t('twoStep.passwordRequired'))});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{password: ''}} validationSchema={passwordSchema} enableReinitialize\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(values.password);\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{description}</DialogContentText>\n <FormProblemAlert />\n <FormTextField name=\"password\" label={t('twoStep.passwordLabel')} type=\"password\" autoComplete=\"current-password\" autoFocus />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" color=\"error\">{confirmLabel}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Activar la app: escanear el QR (o teclear la clave) y confirmar con un código. */\nfunction TotpDialog({setup, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const codeSchema = Yup.object({\n code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat')),\n });\n\n return (\n <Dialog open={Boolean(setup)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{code: ''}} validationSchema={codeSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(normalizeTotpCode(values.code));\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.totpDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{t('twoStep.totpStep1')}</Typography>\n {setup?.qrImageUrl && (\n <TwoStepQr component=\"img\" src={setup.qrImageUrl} alt={t('twoStep.totpQrAlt')}\n ownSx={{width: QR_SIZE_PIXELS, height: QR_SIZE_PIXELS, alignSelf: 'center', borderRadius: 1, bgcolor: 'common.white'}} />\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">{t('twoStep.totpCantScan')}</Typography>\n <TwoStepSecret direction=\"row\" spacing={1} ownSx={{alignItems: 'center'}}>\n <TwoStepSecretCode component=\"code\" ownSx={{fontFamily: CODE_FONT, fontSize: 13, wordBreak: 'break-all'}}>{setup?.secret}</TwoStepSecretCode>\n <CopyButton value={setup?.secret ?? ''} />\n </TwoStepSecret>\n <Typography variant=\"body2\">{t('twoStep.totpStep2')}</Typography>\n <FormProblemAlert />\n <FormTextField name=\"code\" label={t('twoStep.codeLabel')} autoComplete=\"one-time-code\" clearable={false}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n {setup?.extra}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\">{t('twoStep.activate')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Nombrar y crear una llave nueva (el navegador pide la huella, el rostro o el PIN); notice va arriba del nombre. */\nfunction PasskeyDialog({open, onAdd, onClose, notice}) {\n const {t} = useToolkitTranslation('security');\n const nameSchema = Yup.object({name: Yup.string().trim().max(PASSKEY_NAME_MAX_LENGTH)});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{name: ''}} validationSchema={nameSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onAdd(values.name.trim());\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.passkeyDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{t('twoStep.passkeyDialogText')}</DialogContentText>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('twoStep.passkeyName')} placeholder={t('twoStep.passkeyNamePlaceholder')} autoFocus\n slotProps={{htmlInput: {maxLength: PASSKEY_NAME_MAX_LENGTH}}} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" startIcon={<FingerprintOutlinedIcon />}>{t('twoStep.createPasskey')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/**\n * Un renglón de la tarjeta: ícono, nombre, explicación chica y su botón a la derecha.\n *\n * @param {{Icon: import('react').ElementType, title: string, caption?: import('react').ReactNode, captionColor?: string, action?: import('react').ReactNode}} props\n */\nfunction SettingRow({Icon, title, caption, captionColor = 'text.secondary', action}) {\n return (\n <TwoStepHeader direction=\"row\" spacing={1} ownSx={{alignItems: 'center', justifyContent: 'space-between'}}>\n <TwoStepHeaderTitle direction=\"row\" spacing={1.5} ownSx={{alignItems: 'center', minWidth: 0}}>\n <Icon color=\"action\" />\n <TwoStepHeaderText ownSx={{minWidth: 0}}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n {caption && <Typography variant=\"caption\" color={captionColor} component=\"p\">{caption}</Typography>}\n </TwoStepHeaderText>\n </TwoStepHeaderTitle>\n {action}\n </TwoStepHeader>\n );\n}\n\n/**\n * @param {object} props\n * @param {{isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}} props.security\n * @param {(security: {isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}) => void} props.onSecurityChange\n * @param {object} props.actions lo que hace el proyecto en cada caso; cada una regresa {security, recoveryCodes?}\n * @param {(name: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.addPasskey crea la llave\n * (createPasskey) y la guarda en el servidor\n * @param {(passkey: {id: string, name: string}, password: string) => Promise<{security: object}>} props.actions.removePasskey\n * @param {() => Promise<{secret: string, qrImageUrl: string, extra?: import('react').ReactNode}>} props.actions.startTotp\n * la clave y su QR; extra va abajo del campo (p. ej. un atajo de los ejemplos)\n * @param {(code: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.confirmTotp\n * @param {(password: string) => Promise<{security: object}>} props.actions.disableTotp\n * @param {(password: string) => Promise<{security: object, recoveryCodes: string[]}>} props.actions.regenerateRecoveryCodes\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys; sin él no se ofrece agregar llaves\n * @param {string} [props.account] la cuenta (el correo), para el archivo de los códigos\n * @param {import('react').ReactNode} [props.passkeyNotice] un aviso en el diálogo de crear la llave, antes de que el\n * navegador la guarde (p. ej. el de la demostración: la llave queda de verdad en el llavero de la persona)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function TwoStepVerificationCard(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTwoStepVerification.defaultProps) y encima lo que llega aquí\n const {security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n // qué diálogo está abierto: uno a la vez\n const [dialog, setDialog] = useState(null);\n const [totpSetup, setTotpSetup] = useState(null);\n const [recoveryCodes, setRecoveryCodes] = useState(null);\n const [error, setError] = useState(null);\n\n const close = () => setDialog(null);\n // lo que regresa cada acción: el estado nuevo, los códigos si hubo, el aviso y cerrar el diálogo\n const apply = (result, message) => {\n onSecurityChange(result.security);\n if (result.recoveryCodes?.length) {\n setRecoveryCodes(result.recoveryCodes);\n }\n if (message) {\n toastSuccess(message);\n }\n close();\n };\n\n const startTotp = async () => {\n setError(null);\n try {\n setTotpSetup(await actions.startTotp());\n } catch (caught) {\n setError(caught.message);\n }\n };\n\n const passkeyBeingRemoved = dialog?.passkey;\n const codesLeft = security.recoveryCodesRemaining;\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('twoStep.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('twoStep.description')}</Typography>\n <Box>\n {security.isEnabled\n ? <Tag label={t('twoStep.active')} color=\"success\" tooltip={t('twoStep.activeSince', {date: formatDateTime(security.enabledAt)})} />\n : <Tag label={t('twoStep.inactive')} color=\"warning\" tooltip={t('twoStep.inactiveTooltip')} />}\n </Box>\n {error && <Alert severity=\"error\" onClose={() => setError(null)}>{error}</Alert>}\n\n <Divider />\n <SettingRow Icon={FingerprintOutlinedIcon} title={t('twoStep.passkeysTitle')}\n caption={security.passkeys.length === 0 ? (canUsePasskeys ? t('twoStep.passkeysEmpty') : t('passkeys.unsupported')) : null}\n action={canUsePasskeys && <Button size=\"small\" onClick={() => setDialog({kind: 'passkey'})}>{t('twoStep.add')}</Button>} />\n {security.passkeys.length > 0 && (\n <List dense disablePadding>\n {security.passkeys.map((passkey) => (\n <ListItem key={passkey.id} disableGutters secondaryAction={(\n <Tooltip title={t('twoStep.removePasskeyTooltip')}>\n <IconButton edge=\"end\" aria-label={t('twoStep.removePasskey', {name: passkey.name})}\n onClick={() => setDialog({kind: 'removePasskey', passkey})}>\n <DeleteOutlineOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}>\n <TwoStepMenuIcon ownSx={{minWidth: 32}}><VpnKeyOutlinedIcon fontSize=\"small\" /></TwoStepMenuIcon>\n <ListItemText primary={passkey.name}\n secondary={passkey.lastUsedAt\n ? t('twoStep.passkeyUsed', {date: formatDateTime(passkey.lastUsedAt)})\n : t('twoStep.passkeyCreated', {date: formatDateTime(passkey.createdAt)})} />\n </ListItem>\n ))}\n </List>\n )}\n\n <Divider />\n <SettingRow Icon={PhonelinkLockOutlinedIcon} title={t('twoStep.appTitle')}\n caption={security.totp.isEnabled ? t('twoStep.appActiveSince', {date: formatDateTime(security.totp.enabledAt)}) : t('twoStep.appDescription')}\n action={security.totp.isEnabled\n ? <Button size=\"small\" color=\"error\" onClick={() => setDialog({kind: 'disableTotp'})}>{t('twoStep.deactivate')}</Button>\n : <Button size=\"small\" onClick={startTotp}>{t('twoStep.activate')}</Button>} />\n\n {/* los códigos de recuperación solo existen con la verificación activa */}\n {security.isEnabled && (\n <>\n <Divider />\n <SettingRow Icon={VpnKeyOutlinedIcon} title={t('twoStep.codesTitle')}\n caption={codesLeft ? t('twoStep.codesRemaining', {count: codesLeft}) : t('twoStep.codesNone')}\n captionColor={codesLeft <= FEW_CODES_LEFT ? 'warning.main' : 'text.secondary'}\n action={<Button size=\"small\" onClick={() => setDialog({kind: 'regenerateCodes'})}>{t('twoStep.regenerate')}</Button>} />\n </>\n )}\n </Stack>\n\n <PasskeyDialog open={dialog?.kind === 'passkey'} onClose={close} notice={passkeyNotice}\n onAdd={async (name) => apply(await actions.addPasskey(name), t('twoStep.passkeyAdded'))} />\n <TotpDialog setup={totpSetup} onClose={() => setTotpSetup(null)}\n onConfirm={async (code) => {\n const result = await actions.confirmTotp(code);\n setTotpSetup(null);\n apply(result, t('twoStep.appActivated'));\n }} />\n <PasswordDialog open={dialog?.kind === 'disableTotp'} onClose={close} title={t('twoStep.disableTitle')}\n description={t('twoStep.disableText')} confirmLabel={t('twoStep.deactivate')}\n onConfirm={async (password) => apply(await actions.disableTotp(password), t('twoStep.appDeactivated'))} />\n <PasswordDialog open={dialog?.kind === 'removePasskey'} onClose={close}\n title={t('twoStep.removeTitle', {name: passkeyBeingRemoved?.name ?? ''})} description={t('twoStep.removeText')}\n confirmLabel={t('twoStep.remove')}\n onConfirm={async (password) => apply(await actions.removePasskey(passkeyBeingRemoved, password), t('twoStep.passkeyRemoved'))} />\n <PasswordDialog open={dialog?.kind === 'regenerateCodes'} onClose={close} title={t('twoStep.regenerateTitle')}\n description={t('twoStep.regenerateText')} confirmLabel={t('twoStep.generate')}\n onConfirm={async (password) => apply(await actions.regenerateRecoveryCodes(password))} />\n <RecoveryCodesDialog codes={recoveryCodes} account={account} onClose={() => setRecoveryCodes(null)} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TwoStepVerificationCard.js","sources":["../../../src/forms/security/TwoStepVerificationCard.jsx"],"sourcesContent":["// «Verificación en dos pasos» de «Mi perfil»: las llaves de acceso, la app autenticadora y los códigos de recuperación.\n//\n// Lo que QUITA protección (desactivar la app, quitar una llave, generar códigos nuevos) pide la contraseña: una sesión que\n// alguien dejó abierta no debe bastar para desarmar la cuenta. Activar no la pide.\n//\n// La tarjeta no sabe del servidor: el proyecto le pasa el estado (security) y qué hacer en cada caso (actions); cada\n// acción regresa {security, recoveryCodes?} y la tarjeta avisa el estado nuevo con onSecurityChange. Las llaves de acceso\n// las crea el proyecto con createPasskey (@stackcraftmx/calcita/passkeys) dentro de actions.addPasskey: así esta puerta no\n// carga la librería de llaves.\n//\n// <TwoStepVerificationCard security={security} onSecurityChange={setSecurity} account={session.email}\n// canUsePasskeys={passkeysSupported()} actions={securityActions} />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTwoStepVerification con defaultProps y\n// styleOverrides de sus partes: qr, secret, secretCode, header, headerTitle, headerText, menuIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport VpnKeyOutlinedIcon from '@mui/icons-material/VpnKeyOutlined';\n\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n CURRENT_PASSWORD_MAX_LENGTH, PASSKEY_NAME_MAX_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport RecoveryCodesDialog from '@/security/RecoveryCodesDialog.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaTwoStepVerification)\nconst THEME_NAME = 'CalcitaTwoStepVerification';\nconst TwoStepQr = piecePart(THEME_NAME, 'Qr', Box);\nconst TwoStepSecret = piecePart(THEME_NAME, 'Secret', Stack);\nconst TwoStepSecretCode = piecePart(THEME_NAME, 'SecretCode', Box);\nconst TwoStepHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst TwoStepHeaderTitle = piecePart(THEME_NAME, 'HeaderTitle', Stack);\nconst TwoStepHeaderText = piecePart(THEME_NAME, 'HeaderText', Box);\nconst TwoStepMenuIcon = piecePart(THEME_NAME, 'MenuIcon', ListItemIcon);\n\n// los códigos que quedan desde donde se avisa en ámbar que ya son pocos\nconst FEW_CODES_LEFT = 2;\nconst QR_SIZE_PIXELS = 200;\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * Lo que hace un formulario de la tarjeta con un error: un ApiError va a su campo o arriba (applyServerProblem); un\n * error del navegador (la llave de acceso) va arriba; si la persona solo canceló, nada.\n *\n * @param {Error & {cancelled?: boolean, status?: number}} error\n * @param {object} formikHelpers\n * @param {object} values\n */\nfunction showFormError(error, formikHelpers, values) {\n if (error?.cancelled) {\n return;\n }\n if (error?.status === undefined) {\n formikHelpers.setStatus(error?.message);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n}\n\n/** Confirmar con la contraseña antes de quitar protección. */\nfunction PasswordDialog({open, title, description, confirmLabel, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const passwordSchema = Yup.object({password: Yup.string().max(CURRENT_PASSWORD_MAX_LENGTH).required(t('twoStep.passwordRequired'))});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{password: ''}} validationSchema={passwordSchema} enableReinitialize\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(values.password);\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{description}</DialogContentText>\n <FormProblemAlert />\n <FormTextField name=\"password\" label={t('twoStep.passwordLabel')} type=\"password\" autoComplete=\"current-password\" autoFocus />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" color=\"error\">{confirmLabel}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Activar la app: escanear el QR (o teclear la clave) y confirmar con un código. */\nfunction TotpDialog({setup, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const codeSchema = Yup.object({\n code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat')),\n });\n\n return (\n <Dialog open={Boolean(setup)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{code: ''}} validationSchema={codeSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(normalizeTotpCode(values.code));\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.totpDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{t('twoStep.totpStep1')}</Typography>\n {setup?.qrImageUrl && (\n <TwoStepQr component=\"img\" src={setup.qrImageUrl} alt={t('twoStep.totpQrAlt')}\n ownSx={{width: QR_SIZE_PIXELS, height: QR_SIZE_PIXELS, alignSelf: 'center', borderRadius: 1, bgcolor: 'common.white'}} />\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">{t('twoStep.totpCantScan')}</Typography>\n <TwoStepSecret direction=\"row\" spacing={1} ownSx={{alignItems: 'center'}}>\n <TwoStepSecretCode component=\"code\" ownSx={{fontFamily: CODE_FONT, fontSize: 13, wordBreak: 'break-all'}}>{setup?.secret}</TwoStepSecretCode>\n <CopyButton value={setup?.secret ?? ''} />\n </TwoStepSecret>\n <Typography variant=\"body2\">{t('twoStep.totpStep2')}</Typography>\n <FormProblemAlert />\n <FormTextField name=\"code\" label={t('twoStep.codeLabel')} autoComplete=\"one-time-code\" clearable={false}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n {setup?.extra}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\">{t('twoStep.activate')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Nombrar y crear una llave nueva (el navegador pide la huella, el rostro o el PIN); notice va arriba del nombre. */\nfunction PasskeyDialog({open, onAdd, onClose, notice}) {\n const {t} = useToolkitTranslation('security');\n const nameSchema = Yup.object({name: Yup.string().trim().max(PASSKEY_NAME_MAX_LENGTH)});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{name: ''}} validationSchema={nameSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onAdd(values.name.trim());\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.passkeyDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{t('twoStep.passkeyDialogText')}</DialogContentText>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('twoStep.passkeyName')} placeholder={t('twoStep.passkeyNamePlaceholder')} autoFocus\n slotProps={{htmlInput: {maxLength: PASSKEY_NAME_MAX_LENGTH}}} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" startIcon={<FingerprintOutlinedIcon />}>{t('twoStep.createPasskey')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/**\n * Un renglón de la tarjeta: ícono, nombre, explicación chica y su botón a la derecha.\n *\n * @param {{Icon: import('react').ElementType, title: string, caption?: import('react').ReactNode, captionColor?: string, action?: import('react').ReactNode}} props\n */\nfunction SettingRow({Icon, title, caption, captionColor = 'text.secondary', action}) {\n return (\n <TwoStepHeader direction=\"row\" spacing={1} ownSx={{alignItems: 'center', justifyContent: 'space-between'}}>\n <TwoStepHeaderTitle direction=\"row\" spacing={1.5} ownSx={{alignItems: 'center', minWidth: 0}}>\n <Icon color=\"action\" />\n <TwoStepHeaderText ownSx={{minWidth: 0}}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n {caption && <Typography variant=\"caption\" color={captionColor} component=\"p\">{caption}</Typography>}\n </TwoStepHeaderText>\n </TwoStepHeaderTitle>\n {action}\n </TwoStepHeader>\n );\n}\n\n/**\n * @param {object} props\n * @param {{isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}} props.security\n * @param {(security: {isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}) => void} props.onSecurityChange\n * @param {object} props.actions lo que hace el proyecto en cada caso; cada una regresa {security, recoveryCodes?}\n * @param {(name: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.addPasskey crea la llave\n * (createPasskey) y la guarda en el servidor\n * @param {(passkey: {id: string, name: string}, password: string) => Promise<{security: object}>} props.actions.removePasskey\n * @param {() => Promise<{secret: string, qrImageUrl: string, extra?: import('react').ReactNode}>} props.actions.startTotp\n * la clave y su QR; extra va abajo del campo (p. ej. un atajo de los ejemplos)\n * @param {(code: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.confirmTotp\n * @param {(password: string) => Promise<{security: object}>} props.actions.disableTotp\n * @param {(password: string) => Promise<{security: object, recoveryCodes: string[]}>} props.actions.regenerateRecoveryCodes\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys; sin él no se ofrece agregar llaves\n * @param {string} [props.account] la cuenta (el correo), para el archivo de los códigos\n * @param {import('react').ReactNode} [props.passkeyNotice] un aviso en el diálogo de crear la llave, antes de que el\n * navegador la guarde (p. ej. el de la demostración: la llave queda de verdad en el llavero de la persona)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nfunction TwoStepVerificationCardContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTwoStepVerification.defaultProps) y encima lo que llega aquí\n const {security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n // qué diálogo está abierto: uno a la vez\n const [dialog, setDialog] = useState(null);\n const [totpSetup, setTotpSetup] = useState(null);\n const [recoveryCodes, setRecoveryCodes] = useState(null);\n const [error, setError] = useState(null);\n\n const close = () => setDialog(null);\n // lo que regresa cada acción: el estado nuevo, los códigos si hubo, el aviso y cerrar el diálogo\n const apply = (result, message) => {\n onSecurityChange(result.security);\n if (result.recoveryCodes?.length) {\n setRecoveryCodes(result.recoveryCodes);\n }\n if (message) {\n toastSuccess(message);\n }\n close();\n };\n\n const startTotp = async () => {\n setError(null);\n try {\n setTotpSetup(await actions.startTotp());\n } catch (caught) {\n setError(caught.message);\n }\n };\n\n const passkeyBeingRemoved = dialog?.passkey;\n const codesLeft = security.recoveryCodesRemaining;\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('twoStep.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('twoStep.description')}</Typography>\n <Box>\n {security.isEnabled\n ? <Tag label={t('twoStep.active')} color=\"success\" tooltip={t('twoStep.activeSince', {date: formatDateTime(security.enabledAt)})} />\n : <Tag label={t('twoStep.inactive')} color=\"warning\" tooltip={t('twoStep.inactiveTooltip')} />}\n </Box>\n {error && <Alert severity=\"error\" onClose={() => setError(null)}>{error}</Alert>}\n\n <Divider />\n <SettingRow Icon={FingerprintOutlinedIcon} title={t('twoStep.passkeysTitle')}\n caption={security.passkeys.length === 0 ? (canUsePasskeys ? t('twoStep.passkeysEmpty') : t('passkeys.unsupported')) : null}\n action={canUsePasskeys && <Button size=\"small\" onClick={() => setDialog({kind: 'passkey'})}>{t('twoStep.add')}</Button>} />\n {security.passkeys.length > 0 && (\n <List dense disablePadding>\n {security.passkeys.map((passkey) => (\n <ListItem key={passkey.id} disableGutters secondaryAction={(\n <Tooltip title={t('twoStep.removePasskeyTooltip')}>\n <IconButton edge=\"end\" aria-label={t('twoStep.removePasskey', {name: passkey.name})}\n onClick={() => setDialog({kind: 'removePasskey', passkey})}>\n <DeleteOutlineOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}>\n <TwoStepMenuIcon ownSx={{minWidth: 32}}><VpnKeyOutlinedIcon fontSize=\"small\" /></TwoStepMenuIcon>\n <ListItemText primary={passkey.name}\n secondary={passkey.lastUsedAt\n ? t('twoStep.passkeyUsed', {date: formatDateTime(passkey.lastUsedAt)})\n : t('twoStep.passkeyCreated', {date: formatDateTime(passkey.createdAt)})} />\n </ListItem>\n ))}\n </List>\n )}\n\n <Divider />\n <SettingRow Icon={PhonelinkLockOutlinedIcon} title={t('twoStep.appTitle')}\n caption={security.totp.isEnabled ? t('twoStep.appActiveSince', {date: formatDateTime(security.totp.enabledAt)}) : t('twoStep.appDescription')}\n action={security.totp.isEnabled\n ? <Button size=\"small\" color=\"error\" onClick={() => setDialog({kind: 'disableTotp'})}>{t('twoStep.deactivate')}</Button>\n : <Button size=\"small\" onClick={startTotp}>{t('twoStep.activate')}</Button>} />\n\n {/* los códigos de recuperación solo existen con la verificación activa */}\n {security.isEnabled && (\n <>\n <Divider />\n <SettingRow Icon={VpnKeyOutlinedIcon} title={t('twoStep.codesTitle')}\n caption={codesLeft ? t('twoStep.codesRemaining', {count: codesLeft}) : t('twoStep.codesNone')}\n captionColor={codesLeft <= FEW_CODES_LEFT ? 'warning.main' : 'text.secondary'}\n action={<Button size=\"small\" onClick={() => setDialog({kind: 'regenerateCodes'})}>{t('twoStep.regenerate')}</Button>} />\n </>\n )}\n </Stack>\n\n <PasskeyDialog open={dialog?.kind === 'passkey'} onClose={close} notice={passkeyNotice}\n onAdd={async (name) => apply(await actions.addPasskey(name), t('twoStep.passkeyAdded'))} />\n <TotpDialog setup={totpSetup} onClose={() => setTotpSetup(null)}\n onConfirm={async (code) => {\n const result = await actions.confirmTotp(code);\n setTotpSetup(null);\n apply(result, t('twoStep.appActivated'));\n }} />\n <PasswordDialog open={dialog?.kind === 'disableTotp'} onClose={close} title={t('twoStep.disableTitle')}\n description={t('twoStep.disableText')} confirmLabel={t('twoStep.deactivate')}\n onConfirm={async (password) => apply(await actions.disableTotp(password), t('twoStep.appDeactivated'))} />\n <PasswordDialog open={dialog?.kind === 'removePasskey'} onClose={close}\n title={t('twoStep.removeTitle', {name: passkeyBeingRemoved?.name ?? ''})} description={t('twoStep.removeText')}\n confirmLabel={t('twoStep.remove')}\n onConfirm={async (password) => apply(await actions.removePasskey(passkeyBeingRemoved, password), t('twoStep.passkeyRemoved'))} />\n <PasswordDialog open={dialog?.kind === 'regenerateCodes'} onClose={close} title={t('twoStep.regenerateTitle')}\n description={t('twoStep.regenerateText')} confirmLabel={t('twoStep.generate')}\n onConfirm={async (password) => apply(await actions.regenerateRecoveryCodes(password))} />\n <RecoveryCodesDialog codes={recoveryCodes} account={account} onClose={() => setRecoveryCodes(null)} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst TwoStepVerificationCard = withPieceSlots(TwoStepVerificationCardContent, THEME_NAME);\nexport default TwoStepVerificationCard;\n"],"names":[],"mappings":"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"}
|
|
@@ -18,8 +18,25 @@ function formLevelMessageOf(errorsByField, formFieldNames, generalMessage) {
|
|
|
18
18
|
const hasFieldErrors = Object.keys(errorsByField ?? {}).length > 0;
|
|
19
19
|
return hasFieldErrors ? null : generalMessage || null;
|
|
20
20
|
}
|
|
21
|
+
const ROW_SEGMENT = /(^|\.)\d+(\.|$)|\[\d+\]/;
|
|
22
|
+
function splitRowFieldErrors(rawErrors, existsInForm) {
|
|
23
|
+
const rowFieldErrors = {};
|
|
24
|
+
const otherErrors = {};
|
|
25
|
+
if (!rawErrors || typeof rawErrors !== "object" || Array.isArray(rawErrors)) {
|
|
26
|
+
return { rowFieldErrors, otherErrors };
|
|
27
|
+
}
|
|
28
|
+
for (const [path, messages] of Object.entries(rawErrors)) {
|
|
29
|
+
if (ROW_SEGMENT.test(path) && existsInForm(path)) {
|
|
30
|
+
rowFieldErrors[path] = (Array.isArray(messages) ? messages : [messages]).map(String).join(" ");
|
|
31
|
+
} else {
|
|
32
|
+
otherErrors[path] = messages;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { rowFieldErrors, otherErrors };
|
|
36
|
+
}
|
|
21
37
|
export {
|
|
22
38
|
formLevelMessageOf,
|
|
39
|
+
splitRowFieldErrors,
|
|
23
40
|
splitServerFieldErrors
|
|
24
41
|
};
|
|
25
42
|
//# sourceMappingURL=serverProblemFields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serverProblemFields.js","sources":["../../src/forms/serverProblemFields.js"],"sourcesContent":["// Reparte los errores por campo de un 422 entre los campos que EXISTEN en el formulario y los que no.\n//\n// Lo que no tiene campo no se pierde: va al aviso de arriba del formulario. Un error invisible es peor que uno mal\n// colocado.\n//\n// Función pura sin imports (se prueba con node --test en serverProblemFields.test.js); la usan applyServerProblem y\n// las pantallas que pintan el aviso desde su máquina de estados.\n\n/**\n * @param {Record<string, string[]>} errorsByField lo que regresa fieldErrorsOf(problem)\n * @param {string[]} formFieldNames campos del formulario (Object.keys de sus valores iniciales)\n * @returns {{fieldErrors: Record<string, string>, unassignedMessages: string[]}}\n */\nexport function splitServerFieldErrors(errorsByField, formFieldNames) {\n const fieldErrors = {};\n const unassignedMessages = [];\n\n for (const [fieldName, messages] of Object.entries(errorsByField ?? {})) {\n if (formFieldNames.includes(fieldName)) {\n fieldErrors[fieldName] = messages.join(' ');\n } else {\n unassignedMessages.push(...messages);\n }\n }\n\n return {fieldErrors, unassignedMessages};\n}\n\n/**\n * Mensaje del aviso de arriba: lo que quedó sin campo, o el mensaje general si el error no trae errores por campo.\n * Si todo cayó en sus campos, no se repite arriba (null).\n *\n * @param {Record<string, string[]>} errorsByField\n * @param {string[]} formFieldNames\n * @param {string|null|undefined} generalMessage el message del ApiError (ya traducido)\n * @returns {string|null}\n */\nexport function formLevelMessageOf(errorsByField, formFieldNames, generalMessage) {\n const {unassignedMessages} = splitServerFieldErrors(errorsByField, formFieldNames);\n if (unassignedMessages.length > 0) {\n return unassignedMessages.join(' ');\n }\n const hasFieldErrors = Object.keys(errorsByField ?? {}).length > 0;\n return hasFieldErrors ? null : (generalMessage || null);\n}\n"],"names":[],"mappings":"AAaO,SAAS,uBAAuB,eAAe,gBAAgB;AAClE,QAAM,cAAc,CAAA;AACpB,QAAM,qBAAqB,CAAA;AAE3B,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,iBAAiB,CAAA,CAAE,GAAG;AACrE,QAAI,eAAe,SAAS,SAAS,GAAG;AACpC,kBAAY,SAAS,IAAI,SAAS,KAAK,GAAG;AAAA,IAC9C,OAAO;AACH,yBAAmB,KAAK,GAAG,QAAQ;AAAA,IACvC;AAAA,EACJ;AAEA,SAAO,EAAC,aAAa,mBAAkB;AAC3C;AAWO,SAAS,mBAAmB,eAAe,gBAAgB,gBAAgB;AAC9E,QAAM,EAAC,mBAAkB,IAAI,uBAAuB,eAAe,cAAc;AACjF,MAAI,mBAAmB,SAAS,GAAG;AAC/B,WAAO,mBAAmB,KAAK,GAAG;AAAA,EACtC;AACA,QAAM,iBAAiB,OAAO,KAAK,iBAAiB,CAAA,CAAE,EAAE,SAAS;AACjE,SAAO,iBAAiB,OAAQ,kBAAkB;AACtD;"}
|
|
1
|
+
{"version":3,"file":"serverProblemFields.js","sources":["../../src/forms/serverProblemFields.js"],"sourcesContent":["// Reparte los errores por campo de un 422 entre los campos que EXISTEN en el formulario y los que no.\n//\n// Lo que no tiene campo no se pierde: va al aviso de arriba del formulario. Un error invisible es peor que uno mal\n// colocado.\n//\n// Función pura sin imports (se prueba con node --test en serverProblemFields.test.js); la usan applyServerProblem y\n// las pantallas que pintan el aviso desde su máquina de estados.\n\n/**\n * @param {Record<string, string[]>} errorsByField lo que regresa fieldErrorsOf(problem)\n * @param {string[]} formFieldNames campos del formulario (Object.keys de sus valores iniciales)\n * @returns {{fieldErrors: Record<string, string>, unassignedMessages: string[]}}\n */\nexport function splitServerFieldErrors(errorsByField, formFieldNames) {\n const fieldErrors = {};\n const unassignedMessages = [];\n\n for (const [fieldName, messages] of Object.entries(errorsByField ?? {})) {\n if (formFieldNames.includes(fieldName)) {\n fieldErrors[fieldName] = messages.join(' ');\n } else {\n unassignedMessages.push(...messages);\n }\n }\n\n return {fieldErrors, unassignedMessages};\n}\n\n/**\n * Mensaje del aviso de arriba: lo que quedó sin campo, o el mensaje general si el error no trae errores por campo.\n * Si todo cayó en sus campos, no se repite arriba (null).\n *\n * @param {Record<string, string[]>} errorsByField\n * @param {string[]} formFieldNames\n * @param {string|null|undefined} generalMessage el message del ApiError (ya traducido)\n * @returns {string|null}\n */\nexport function formLevelMessageOf(errorsByField, formFieldNames, generalMessage) {\n const {unassignedMessages} = splitServerFieldErrors(errorsByField, formFieldNames);\n if (unassignedMessages.length > 0) {\n return unassignedMessages.join(' ');\n }\n const hasFieldErrors = Object.keys(errorsByField ?? {}).length > 0;\n return hasFieldErrors ? null : (generalMessage || null);\n}\n\n// un renglón en la ruta del servidor: 'contacts.2.email' (Laravel) o 'contacts[2].email'\nconst ROW_SEGMENT = /(^|\\.)\\d+(\\.|$)|\\[\\d+\\]/;\n\n/**\n * Separa los errores de un 422 que son de un campo DENTRO de un renglón que el formulario sí tiene ('contacts.2.email'\n * con contacts[2] en los valores): esos van a su renglón con la ruta completa. Los demás siguen el camino de siempre\n * (fieldErrorsOf los reduce a su último nombre: 'rows.3.email' → 'email').\n *\n * @param {object|null|undefined} rawErrors problem.errors tal cual ({ruta: [mensajes]})\n * @param {(path: string) => boolean} existsInForm si esa ruta existe en los valores del formulario\n * @returns {{rowFieldErrors: Record<string, string>, otherErrors: Record<string, unknown>}}\n */\nexport function splitRowFieldErrors(rawErrors, existsInForm) {\n const rowFieldErrors = {};\n const otherErrors = {};\n if (!rawErrors || typeof rawErrors !== 'object' || Array.isArray(rawErrors)) {\n return {rowFieldErrors, otherErrors};\n }\n for (const [path, messages] of Object.entries(rawErrors)) {\n if (ROW_SEGMENT.test(path) && existsInForm(path)) {\n rowFieldErrors[path] = (Array.isArray(messages) ? messages : [messages]).map(String).join(' ');\n } else {\n otherErrors[path] = messages;\n }\n }\n return {rowFieldErrors, otherErrors};\n}\n\n"],"names":[],"mappings":"AAaO,SAAS,uBAAuB,eAAe,gBAAgB;AAClE,QAAM,cAAc,CAAA;AACpB,QAAM,qBAAqB,CAAA;AAE3B,aAAW,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,iBAAiB,CAAA,CAAE,GAAG;AACrE,QAAI,eAAe,SAAS,SAAS,GAAG;AACpC,kBAAY,SAAS,IAAI,SAAS,KAAK,GAAG;AAAA,IAC9C,OAAO;AACH,yBAAmB,KAAK,GAAG,QAAQ;AAAA,IACvC;AAAA,EACJ;AAEA,SAAO,EAAC,aAAa,mBAAkB;AAC3C;AAWO,SAAS,mBAAmB,eAAe,gBAAgB,gBAAgB;AAC9E,QAAM,EAAC,mBAAkB,IAAI,uBAAuB,eAAe,cAAc;AACjF,MAAI,mBAAmB,SAAS,GAAG;AAC/B,WAAO,mBAAmB,KAAK,GAAG;AAAA,EACtC;AACA,QAAM,iBAAiB,OAAO,KAAK,iBAAiB,CAAA,CAAE,EAAE,SAAS;AACjE,SAAO,iBAAiB,OAAQ,kBAAkB;AACtD;AAGA,MAAM,cAAc;AAWb,SAAS,oBAAoB,WAAW,cAAc;AACzD,QAAM,iBAAiB,CAAA;AACvB,QAAM,cAAc,CAAA;AACpB,MAAI,CAAC,aAAa,OAAO,cAAc,YAAY,MAAM,QAAQ,SAAS,GAAG;AACzE,WAAO,EAAC,gBAAgB,YAAW;AAAA,EACvC;AACA,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,SAAS,GAAG;AACtD,QAAI,YAAY,KAAK,IAAI,KAAK,aAAa,IAAI,GAAG;AAC9C,qBAAe,IAAI,KAAK,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,QAAQ,GAAG,IAAI,MAAM,EAAE,KAAK,GAAG;AAAA,IACjG,OAAO;AACH,kBAAY,IAAI,IAAI;AAAA,IACxB;AAAA,EACJ;AACA,SAAO,EAAC,gBAAgB,YAAW;AACvC;"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { translateValidationMessage } from "./validationMessages.js";
|
|
4
|
+
import { useToolkitSettings } from "../toolkit/toolkitContext.js";
|
|
5
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
6
|
+
function useValidationText({ name, label, messages } = {}) {
|
|
7
|
+
const { readValidationMessage } = useToolkitSettings();
|
|
8
|
+
const { t } = useToolkitTranslation(["validation"]);
|
|
9
|
+
const labelText = typeof label === "string" ? label : void 0;
|
|
10
|
+
return useCallback(
|
|
11
|
+
(error) => translateValidationMessage(t, error, { label: labelText, messages, readMessage: readValidationMessage, fieldName: name }),
|
|
12
|
+
[t, name, labelText, messages, readValidationMessage]
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
useValidationText
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=useValidationText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useValidationText.js","sources":["../../src/forms/useValidationText.js"],"sourcesContent":["// El texto del error de un campo, dentro de un componente: con el idioma, los textos y la lectura de mensajes\n// (readValidationMessage) del proveedor más cercano. Lo usan todos los campos de la casa; un campo propio del proyecto\n// también:\n//\n// const validationTextOf = useValidationText({name, label, messages});\n// const errorText = validationTextOf(error);\n//\n// No importa yup (validationMessages.js tampoco): sirve en un proyecto que valida con otra cosa.\n//\n// Hook genérico: function (regla de la casa).\nimport {useCallback} from 'react';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * @param {object} [options]\n * @param {string} [options.name] el name del campo: para su texto propio (validation:fields.<campo>.<regla>)\n * @param {string} [options.label] el nombre del campo, para los textos que lo dicen ({{label}})\n * @param {Record<string, string|object>} [options.messages] los textos de la pantalla para este campo, por regla\n * @returns {(error: unknown) => string|undefined}\n */\nexport function useValidationText({name, label, messages} = {}) {\n const {readValidationMessage} = useToolkitSettings();\n const {t} = useToolkitTranslation(['validation']);\n const labelText = typeof label === 'string' ? label : undefined;\n return useCallback(\n (error) => translateValidationMessage(t, error, {label: labelText, messages, readMessage: readValidationMessage, fieldName: name}),\n [t, name, labelText, messages, readValidationMessage],\n );\n}\n"],"names":[],"mappings":";;;;;AAuBO,SAAS,kBAAkB,EAAC,MAAM,OAAO,SAAQ,IAAI,CAAA,GAAI;AAC5D,QAAM,EAAC,sBAAqB,IAAI,mBAAkB;AAClD,QAAM,EAAC,EAAC,IAAI,sBAAsB,CAAC,YAAY,CAAC;AAChD,QAAM,YAAY,OAAO,UAAU,WAAW,QAAQ;AACtD,SAAO;AAAA,IACH,CAAC,UAAU,2BAA2B,GAAG,OAAO,EAAC,OAAO,WAAW,UAAU,aAAa,uBAAuB,WAAW,KAAI,CAAC;AAAA,IACjI,CAAC,GAAG,MAAM,WAAW,UAAU,qBAAqB;AAAA,EAC5D;AACA;"}
|