@stackcraftmx/calcita 0.23.0 → 0.26.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 +127 -22
- package/cli/projectFiles.js +5 -3
- package/cli/templates/project/README.md.tpl +28 -0
- package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
- package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
- package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
- package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
- package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
- package/cli/templates/project/src/core/index.js.tpl +1 -0
- package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
- package/cli/templates/project/vite.config.js.tpl +27 -1
- package/dist/DataTable/AuditTrailTable.js +5 -3
- package/dist/DataTable/AuditTrailTable.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/EmptyState.js +2 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +2 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +4 -3
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +153 -83
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/rowSelection.js +61 -0
- package/dist/DataTable/rowSelection.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountSecuritySection.js +2 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/ForgotPasswordScreen.js +5 -4
- package/dist/account/ForgotPasswordScreen.js.map +1 -1
- package/dist/account/ProfileScreen.js +39 -13
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/ResetPasswordScreen.js +3 -2
- package/dist/account/ResetPasswordScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +7 -6
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/account/accountConnector.js.map +1 -1
- package/dist/account/restAccountConnector.js +2 -1
- package/dist/account/restAccountConnector.js.map +1 -1
- package/dist/adapters/reactRouterRoutes.js +7 -6
- package/dist/adapters/reactRouterRoutes.js.map +1 -1
- package/dist/api/createApiClient.js +40 -5
- package/dist/api/createApiClient.js.map +1 -1
- package/dist/appShell/AccountMenu.js +15 -23
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +42 -15
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +29 -6
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +2 -0
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +3 -3
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +19 -8
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +19 -4
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +92 -0
- package/dist/appShell/BarSearch.js.map +1 -0
- package/dist/appShell/HeaderTools.js +4 -2
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MenuLinks.js +36 -0
- package/dist/appShell/MenuLinks.js.map +1 -0
- package/dist/appShell/MobileTabBar.js +6 -4
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +10 -5
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +5 -3
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +3 -3
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +3 -4
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TopMenu.js +5 -6
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +11 -0
- package/dist/appShell/Wordmark.js.map +1 -0
- package/dist/appShell/menuItemText.js +4 -0
- package/dist/appShell/menuItemText.js.map +1 -1
- package/dist/appShell/shellSizes.js +2 -0
- package/dist/appShell/shellSizes.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +2 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +16 -5
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +74 -0
- package/dist/calendar/EventDetails.js.map +1 -0
- package/dist/calendar/calendarEvents.js +31 -0
- package/dist/calendar/calendarEvents.js.map +1 -0
- package/dist/calendar.js +8 -1
- package/dist/calendar.js.map +1 -1
- package/dist/charts.js +10 -1
- package/dist/charts.js.map +1 -1
- package/dist/datePickers/DateInput.js +3 -2
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/downloads/DownloadManager.js +14 -20
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/downloads/downloadQueue.js +4 -2
- package/dist/downloads/downloadQueue.js.map +1 -1
- package/dist/downloads/downloadsMachine.js +4 -1
- package/dist/downloads/downloadsMachine.js.map +1 -1
- package/dist/downloads/fileTypes.js +22 -0
- package/dist/downloads/fileTypes.js.map +1 -0
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +2 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +2 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +5 -3
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/formatting.js +171 -143
- package/dist/formatting.js.map +1 -1
- package/dist/forms/FormWizard.js +18 -45
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/auth/ChangePasswordForm.js +5 -4
- package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
- package/dist/forms/auth/ForgotPasswordForm.js +2 -2
- package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +2 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/security/SecondStepForm.js +2 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +8 -6
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/hookForm/FormWizard.js +59 -0
- package/dist/hookForm/FormWizard.js.map +1 -0
- package/dist/inbox/InboxLayout.js +44 -8
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +71 -21
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +80 -29
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/inbox/inboxDates.js +4 -3
- package/dist/inbox/inboxDates.js.map +1 -1
- package/dist/index.js +163 -136
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +8 -4
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +3 -2
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FormSection.js +3 -3
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/NumberInput.js +3 -2
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/SelectInput.js +29 -11
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +6 -2
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +54 -0
- package/dist/inputs/WizardLayout.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +239 -14
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/kanban/kanbanCards.js +19 -0
- package/dist/kanban/kanbanCards.js.map +1 -0
- package/dist/kanban.js +7 -1
- package/dist/kanban.js.map +1 -1
- package/dist/locales/en.js +74 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +74 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +28 -8
- package/dist/maps/MapView.js.map +1 -1
- package/dist/navigation/appMenu.js +13 -1
- package/dist/navigation/appMenu.js.map +1 -1
- package/dist/observability/logger.js +18 -0
- package/dist/observability/logger.js.map +1 -1
- package/dist/observability/usePublishedBuildWatch.js +4 -3
- package/dist/observability/usePublishedBuildWatch.js.map +1 -1
- package/dist/pwa/InstallAppSetting.js +6 -5
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +2 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +15 -9
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/pwa/useInstallPrompt.js +2 -1
- package/dist/pwa/useInstallPrompt.js.map +1 -1
- package/dist/react-hook-form.js +6 -4
- package/dist/react-hook-form.js.map +1 -1
- package/dist/security/ActivityLog.js +4 -3
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +4 -3
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +8 -6
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +2 -0
- package/dist/table.js.map +1 -1
- package/dist/theme/components/MuiAppBar.js +6 -5
- package/dist/theme/components/MuiAppBar.js.map +1 -1
- package/dist/theme/components/MuiDialog.js +13 -0
- package/dist/theme/components/MuiDialog.js.map +1 -0
- package/dist/theme/components/glassComponents.js +32 -31
- package/dist/theme/components/glassComponents.js.map +1 -1
- package/dist/theme/components/minimalComponents.js +8 -4
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/theme/createToolkitTheme.js.map +1 -1
- package/dist/theme/glassSurface.js +36 -38
- package/dist/theme/glassSurface.js.map +1 -1
- package/dist/theme/lightDarkStyles.js +48 -0
- package/dist/theme/lightDarkStyles.js.map +1 -0
- package/dist/theme/toolkitComponents.js +2 -0
- package/dist/theme/toolkitComponents.js.map +1 -1
- package/dist/toolkit/ComponentTexts.js +13 -0
- package/dist/toolkit/ComponentTexts.js.map +1 -0
- package/dist/toolkit/ToolkitProvider.js +36 -28
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -20
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/componentTextsContext.js +15 -0
- package/dist/toolkit/componentTextsContext.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +40 -0
- package/dist/toolkit/hashNavigation.js.map +1 -0
- package/dist/toolkit/navigationPaths.js +49 -0
- package/dist/toolkit/navigationPaths.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +24 -4
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/toolkit/useFormatters.js +12 -0
- package/dist/toolkit/useFormatters.js.map +1 -0
- package/dist/toolkit/useToolkitTranslation.js +11 -7
- package/dist/toolkit/useToolkitTranslation.js.map +1 -1
- package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
- package/dist/types/DataTable/EmptyState.d.ts +7 -2
- package/dist/types/DataTable/FilterChips.d.ts +3 -1
- package/dist/types/DataTable/GoToPage.d.ts +7 -2
- package/dist/types/DataTable/SelectionBar.d.ts +32 -0
- package/dist/types/DataTable/ServerTable.d.ts +64 -3
- package/dist/types/DataTable/index.d.ts +1 -1
- package/dist/types/DataTable/rowSelection.d.ts +113 -0
- package/dist/types/access/PermissionMatrix.d.ts +3 -1
- package/dist/types/account/AccountSecuritySection.d.ts +3 -1
- package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
- package/dist/types/account/ProfileScreen.d.ts +31 -1
- package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
- package/dist/types/account/SignInScreen.d.ts +3 -1
- package/dist/types/account/accountConnector.d.ts +7 -2
- package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
- package/dist/types/api/createApiClient.d.ts +46 -1
- package/dist/types/appShell/AppLayout.d.ts +15 -1
- package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
- package/dist/types/appShell/BarActionButton.d.ts +2 -2
- package/dist/types/appShell/BarActions.d.ts +6 -1
- package/dist/types/appShell/BarCapsule.d.ts +8 -0
- package/dist/types/appShell/BarSearch.d.ts +24 -0
- package/dist/types/appShell/MenuLinks.d.ts +17 -0
- package/dist/types/appShell/MobileTabBar.d.ts +4 -1
- package/dist/types/appShell/SettingsMenu.d.ts +3 -1
- package/dist/types/appShell/SideMenu.d.ts +3 -1
- package/dist/types/appShell/Wordmark.d.ts +6 -0
- package/dist/types/appShell/menuItemText.d.ts +16 -0
- package/dist/types/appShell/shellSizes.d.ts +1 -0
- package/dist/types/auth/PasswordChecklist.d.ts +3 -1
- package/dist/types/calendar/EventCalendar.d.ts +18 -1
- package/dist/types/calendar/EventDetails.d.ts +36 -0
- package/dist/types/calendar/calendarEvents.d.ts +27 -0
- package/dist/types/calendar.d.ts +2 -0
- package/dist/types/charts.d.ts +4 -0
- package/dist/types/datePickers/DateInput.d.ts +3 -1
- package/dist/types/downloads/DownloadManager.d.ts +7 -1
- package/dist/types/downloads/downloadQueue.d.ts +8 -3
- package/dist/types/downloads/fileTypes.d.ts +39 -0
- package/dist/types/downloads/index.d.ts +1 -0
- package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
- package/dist/types/feedback/ModalWaiting.d.ts +7 -2
- package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
- package/dist/types/filepond/FileDropzone.d.ts +3 -1
- package/dist/types/formatting.d.ts +45 -90
- package/dist/types/forms/FormWizard.d.ts +3 -1
- package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/SignInForm.d.ts +3 -1
- package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
- package/dist/types/hookForm/FormWizard.d.ts +22 -0
- package/dist/types/inbox/InboxLayout.d.ts +14 -2
- package/dist/types/inbox/MessageList.d.ts +30 -5
- package/dist/types/inbox/MessageView.d.ts +35 -3
- package/dist/types/inbox/inboxDates.d.ts +6 -1
- package/dist/types/index.d.ts +15 -8
- package/dist/types/inputs/CatalogInput.d.ts +9 -5
- package/dist/types/inputs/ColorInput.d.ts +3 -1
- package/dist/types/inputs/FormSection.d.ts +2 -2
- package/dist/types/inputs/NumberInput.d.ts +3 -1
- package/dist/types/inputs/SelectInput.d.ts +9 -5
- package/dist/types/inputs/TextInput.d.ts +3 -1
- package/dist/types/inputs/WizardLayout.d.ts +34 -0
- package/dist/types/kanban/KanbanBoard.d.ts +61 -6
- package/dist/types/kanban/kanbanCards.d.ts +22 -0
- package/dist/types/kanban.d.ts +1 -0
- package/dist/types/locales/en.d.ts +77 -0
- package/dist/types/locales/es.d.ts +77 -0
- package/dist/types/maps/MapView.d.ts +12 -3
- package/dist/types/navigation/appMenu.d.ts +8 -0
- package/dist/types/observability/logger.d.ts +13 -0
- package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
- package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
- package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
- package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/security/ActivityLog.d.ts +3 -1
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
- package/dist/types/security/SessionsCard.d.ts +3 -1
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/components/MuiAppBar.d.ts +3 -1
- package/dist/types/theme/components/MuiDialog.d.ts +9 -0
- package/dist/types/theme/components/glassComponents.d.ts +5 -0
- package/dist/types/theme/lightDarkStyles.d.ts +8 -0
- package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
- package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
- package/dist/types/toolkit/browserNavigation.d.ts +23 -28
- package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
- package/dist/types/toolkit/hashNavigation.d.ts +12 -0
- package/dist/types/toolkit/navigationPaths.d.ts +43 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
- package/dist/types/toolkit/useFormatters.d.ts +6 -0
- package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
- package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
- package/dist/types/ui/actions/CopyButton.d.ts +3 -1
- package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
- package/dist/types/ui/actions/SecretField.d.ts +5 -1
- package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
- package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
- package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
- package/dist/types/ui/index.d.ts +1 -1
- package/dist/types/ui/labels/tagOf.d.ts +13 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
- package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
- package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
- package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
- package/dist/types/ui/layout/PageHeading.d.ts +3 -1
- package/dist/types/ui/metrics/BarList.d.ts +7 -1
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
- package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
- package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
- package/dist/types/ui/metrics/LineChart.d.ts +41 -0
- package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
- package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
- package/dist/types/ui/metrics/chartParts.d.ts +112 -0
- package/dist/types/ui/navigation/BackButton.d.ts +3 -1
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
- package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
- package/dist/types/upload/FileDropzone.d.ts +3 -1
- package/dist/ui/actions/ActionsMenu.js +51 -0
- package/dist/ui/actions/ActionsMenu.js.map +1 -0
- package/dist/ui/actions/CopyButton.js +2 -2
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +11 -12
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -4
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +142 -0
- package/dist/ui/actions/SelectionActions.js.map +1 -0
- package/dist/ui/filters/DateRangeFilter.js +4 -3
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +2 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/labels/tagOf.js +7 -0
- package/dist/ui/labels/tagOf.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +6 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +2 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +21 -7
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +2 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +2 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/metrics/BarList.js +18 -13
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +11 -6
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +1 -1
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +59 -63
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +117 -0
- package/dist/ui/metrics/LineChart.js.map +1 -0
- package/dist/ui/metrics/MetricCard.js +41 -8
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +4 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +93 -0
- package/dist/ui/metrics/chartParts.js.map +1 -0
- package/dist/ui/navigation/BackButton.js +2 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
- 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 +40 -19
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +10 -8
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -3
- package/dist/upload/FileDropzone.js.map +1 -1
- package/package.json +16 -7
|
@@ -13,6 +13,7 @@ function reloadPage() {
|
|
|
13
13
|
}
|
|
14
14
|
function usePublishedBuildWatch({
|
|
15
15
|
url = null,
|
|
16
|
+
buildId,
|
|
16
17
|
checkEveryMinutes = DEFAULT_CHECK_EVERY_MINUTES,
|
|
17
18
|
reloadAfterHiddenMinutes = DEFAULT_RELOAD_AFTER_HIDDEN_MINUTES,
|
|
18
19
|
onReload = reloadPage
|
|
@@ -32,7 +33,7 @@ function usePublishedBuildWatch({
|
|
|
32
33
|
const response = await fetch(url, { cache: "no-store", headers: { Accept: "application/json" } });
|
|
33
34
|
const publishedBuildId = response.ok ? publishedBuildIdOf(await response.json()) : null;
|
|
34
35
|
const action = buildWatchActionOf({
|
|
35
|
-
runningBuildId: toolkitSettings().buildId,
|
|
36
|
+
runningBuildId: buildId ?? toolkitSettings().buildId,
|
|
36
37
|
publishedBuildId,
|
|
37
38
|
hiddenMilliseconds,
|
|
38
39
|
reloadAfterHiddenMilliseconds: reloadAfterHiddenMinutes === null ? null : reloadAfterHiddenMinutes * MILLISECONDS_PER_MINUTE
|
|
@@ -40,7 +41,7 @@ function usePublishedBuildWatch({
|
|
|
40
41
|
if (action === BUILD_WATCH_ACTION.reload) {
|
|
41
42
|
onReload();
|
|
42
43
|
} else if (action === BUILD_WATCH_ACTION.notice) {
|
|
43
|
-
reportPublishedBuild(publishedBuildId);
|
|
44
|
+
reportPublishedBuild(publishedBuildId, buildId ?? toolkitSettings().buildId);
|
|
44
45
|
}
|
|
45
46
|
} catch (error) {
|
|
46
47
|
log.info("No se pudo revisar la versión publicada", { url, reason: error.message });
|
|
@@ -68,7 +69,7 @@ function usePublishedBuildWatch({
|
|
|
68
69
|
window.clearInterval(timer);
|
|
69
70
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
70
71
|
};
|
|
71
|
-
}, [url, checkEveryMinutes, reloadAfterHiddenMinutes, onReload]);
|
|
72
|
+
}, [url, buildId, checkEveryMinutes, reloadAfterHiddenMinutes, onReload]);
|
|
72
73
|
}
|
|
73
74
|
export {
|
|
74
75
|
usePublishedBuildWatch
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePublishedBuildWatch.js","sources":["../../src/observability/usePublishedBuildWatch.js"],"sourcesContent":["// Que la app se entere sola de que se publicó otra versión del front, sin depender del API. Hace falta sobre todo con la\n// app anclada al inicio del iPhone: al abrirla, el iPhone NO vuelve a pedir la página, retoma la que tenía en memoria (aunque\n// haya pasado un día), así que la persona se quedaba con la versión vieja hasta cerrarla a la fuerza.\n//\n// Cómo: al construir, el proyecto deja un archivo con su versión ({\"buildId\": \"…\"}, por omisión /version.json) y la misma\n// versión en ToolkitProvider (buildId). Esta app vuelve a leer el archivo:\n// - cada vez que regresa del segundo plano (justo cuando el iPhone la despierta), y\n// - cada tanto mientras se usa (checkEveryMinutes).\n// Si ya hay otra (buildWatchActionOf): si estuvo fuera al menos reloadAfterHiddenMinutes, se recarga sola; si no, sale el\n// aviso «Hay una versión nueva · Actualizar» (NewBuildNotice, que el marco ya pinta) para no borrar lo que llevaba escrito.\n//\n// Se monta UNA vez, en la raíz de la app:\n// usePublishedBuildWatch({url: import.meta.env.PROD ? '/version.json' : null});\n// Sin url no hace nada (en desarrollo no hay versión que comparar).\n//\n// OJO: el archivo se pide con cache 'no-store' y el servidor no debe guardarlo (en el nginx de los ejemplos, «location /»\n// ya va con no-cache). Sin red (el teléfono en el metro) no pasa nada: se vuelve a intentar en la siguiente revisión.\n//\n// Gancho genérico del paquete: function (regla de la casa).\nimport {useEffect} from 'react';\n\nimport {BUILD_WATCH_ACTION, buildWatchActionOf, publishedBuildIdOf} from '@/observability/buildWatch.js';\nimport {createLogger} from '@/observability/logger.js';\nimport {reportPublishedBuild} from '@/observability/publishedBuild.js';\nimport {toolkitSettings} from '@/toolkit/toolkitSettings.js';\n\nconst log = createLogger('build-watch');\nconst MILLISECONDS_PER_MINUTE = 60 * 1000;\n// por omisión: revisar cada 5 minutos mientras se usa, y recargar sola si estuvo fuera 15 minutos o más\nconst DEFAULT_CHECK_EVERY_MINUTES = 5;\nconst DEFAULT_RELOAD_AFTER_HIDDEN_MINUTES = 15;\n\n/** Lo que hace si el proyecto no dice otra cosa: recargar la página, que trae la versión nueva. */\nfunction reloadPage() {\n window.location.reload();\n}\n\n/**\n * @param {object} [options]\n * @param {string|null} [options.url] dónde está el archivo de versión ({\"buildId\": \"…\"}); null: no vigila\n * @param {number} [options.checkEveryMinutes] cada cuánto revisa mientras se usa; por omisión 5\n * @param {number|null} [options.reloadAfterHiddenMinutes] desde cuántos minutos fuera se recarga sola al volver; por\n * omisión 15. null: nunca se recarga sola, siempre el aviso\n * @param {() => void} [options.onReload] cómo recargar (p. ej. guardar un borrador antes). OJO: con useCallback; una\n * función nueva en cada pintada volvería a armar la vigilancia\n * @returns {void}\n */\nexport function usePublishedBuildWatch({\n url = null, checkEveryMinutes = DEFAULT_CHECK_EVERY_MINUTES, reloadAfterHiddenMinutes = DEFAULT_RELOAD_AFTER_HIDDEN_MINUTES, onReload = reloadPage,\n} = {}) {\n useEffect(() => {\n if (!url || typeof document === 'undefined') {\n return undefined;\n }\n let hiddenSince = null;\n let isChecking = false;\n\n const check = async (hiddenMilliseconds = 0) => {\n // una revisión a la vez: al volver del segundo plano pueden llegar dos avisos del navegador juntos\n if (isChecking) {\n return;\n }\n isChecking = true;\n try {\n const response = await fetch(url, {cache: 'no-store', headers: {Accept: 'application/json'}});\n const publishedBuildId = response.ok ? publishedBuildIdOf(await response.json()) : null;\n const action = buildWatchActionOf({\n runningBuildId: toolkitSettings().buildId,\n publishedBuildId,\n hiddenMilliseconds,\n reloadAfterHiddenMilliseconds: reloadAfterHiddenMinutes === null ? null : reloadAfterHiddenMinutes * MILLISECONDS_PER_MINUTE,\n });\n if (action === BUILD_WATCH_ACTION.reload) {\n onReload();\n } else if (action === BUILD_WATCH_ACTION.notice) {\n reportPublishedBuild(publishedBuildId);\n }\n } catch (error) {\n // sin red o con un archivo que no es JSON: no es algo que la persona pueda arreglar; queda en el registro\n log.info('No se pudo revisar la versión publicada', {url, reason: error.message});\n } finally {\n isChecking = false;\n }\n };\n\n // al irse a segundo plano se anota la hora; al volver se revisa con cuánto tiempo estuvo fuera\n const handleVisibilityChange = () => {\n if (document.visibilityState === 'hidden') {\n hiddenSince = Date.now();\n return;\n }\n const hiddenMilliseconds = hiddenSince === null ? 0 : Date.now() - hiddenSince;\n hiddenSince = null;\n check(hiddenMilliseconds);\n };\n // mientras se usa (en segundo plano el teléfono detiene los relojes de la página de todos modos)\n const timer = window.setInterval(() => {\n if (document.visibilityState === 'visible') {\n check();\n }\n }, checkEveryMinutes * MILLISECONDS_PER_MINUTE);\n\n check();\n document.addEventListener('visibilitychange', handleVisibilityChange);\n return () => {\n window.clearInterval(timer);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n };\n }, [url, checkEveryMinutes, reloadAfterHiddenMinutes, onReload]);\n}\n"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"usePublishedBuildWatch.js","sources":["../../src/observability/usePublishedBuildWatch.js"],"sourcesContent":["// Que la app se entere sola de que se publicó otra versión del front, sin depender del API. Hace falta sobre todo con la\n// app anclada al inicio del iPhone: al abrirla, el iPhone NO vuelve a pedir la página, retoma la que tenía en memoria (aunque\n// haya pasado un día), así que la persona se quedaba con la versión vieja hasta cerrarla a la fuerza.\n//\n// Cómo: al construir, el proyecto deja un archivo con su versión ({\"buildId\": \"…\"}, por omisión /version.json) y la misma\n// versión en ToolkitProvider (buildId). Esta app vuelve a leer el archivo:\n// - cada vez que regresa del segundo plano (justo cuando el iPhone la despierta), y\n// - cada tanto mientras se usa (checkEveryMinutes).\n// Si ya hay otra (buildWatchActionOf): si estuvo fuera al menos reloadAfterHiddenMinutes, se recarga sola; si no, sale el\n// aviso «Hay una versión nueva · Actualizar» (NewBuildNotice, que el marco ya pinta) para no borrar lo que llevaba escrito.\n//\n// Se monta UNA vez, en la raíz de la app, con la versión de la pestaña (la misma que va a ToolkitProvider buildId):\n// usePublishedBuildWatch({url: import.meta.env.PROD ? '/version.json' : null, buildId: APP_BUILD_ID});\n// Sin url no hace nada (en desarrollo no hay versión que comparar).\n//\n// OJO buildId: sin él se toma el de ToolkitProvider, pero la raíz suele montar este gancho ANTES de que ToolkitProvider se\n// pinte (AccountProvider lo pinta hasta tener la sesión): la primera revisión no tenía con qué comparar y, al abrir la app\n// en la pantalla de entrar, el aviso no salía (medido en el proyecto que arma calcita new).\n//\n// OJO: el archivo se pide con cache 'no-store' y el servidor no debe guardarlo (en el nginx de los ejemplos, «location /»\n// ya va con no-cache). Sin red (el teléfono en el metro) no pasa nada: se vuelve a intentar en la siguiente revisión.\n//\n// Gancho genérico del paquete: function (regla de la casa).\nimport {useEffect} from 'react';\n\nimport {BUILD_WATCH_ACTION, buildWatchActionOf, publishedBuildIdOf} from '@/observability/buildWatch.js';\nimport {createLogger} from '@/observability/logger.js';\nimport {reportPublishedBuild} from '@/observability/publishedBuild.js';\nimport {toolkitSettings} from '@/toolkit/toolkitSettings.js';\n\nconst log = createLogger('build-watch');\nconst MILLISECONDS_PER_MINUTE = 60 * 1000;\n// por omisión: revisar cada 5 minutos mientras se usa, y recargar sola si estuvo fuera 15 minutos o más\nconst DEFAULT_CHECK_EVERY_MINUTES = 5;\nconst DEFAULT_RELOAD_AFTER_HIDDEN_MINUTES = 15;\n\n/** Lo que hace si el proyecto no dice otra cosa: recargar la página, que trae la versión nueva. */\nfunction reloadPage() {\n window.location.reload();\n}\n\n/**\n * @param {object} [options]\n * @param {string|null} [options.url] dónde está el archivo de versión ({\"buildId\": \"…\"}); null: no vigila\n * @param {string} [options.buildId] la versión de esta pestaña; por omisión el buildId de ToolkitProvider (ver el OJO)\n * @param {number} [options.checkEveryMinutes] cada cuánto revisa mientras se usa; por omisión 5\n * @param {number|null} [options.reloadAfterHiddenMinutes] desde cuántos minutos fuera se recarga sola al volver; por\n * omisión 15. null: nunca se recarga sola, siempre el aviso\n * @param {() => void} [options.onReload] cómo recargar (p. ej. guardar un borrador antes). OJO: con useCallback; una\n * función nueva en cada pintada volvería a armar la vigilancia\n * @returns {void}\n */\nexport function usePublishedBuildWatch({\n url = null, buildId, checkEveryMinutes = DEFAULT_CHECK_EVERY_MINUTES, reloadAfterHiddenMinutes = DEFAULT_RELOAD_AFTER_HIDDEN_MINUTES, onReload = reloadPage,\n} = {}) {\n useEffect(() => {\n if (!url || typeof document === 'undefined') {\n return undefined;\n }\n let hiddenSince = null;\n let isChecking = false;\n\n const check = async (hiddenMilliseconds = 0) => {\n // una revisión a la vez: al volver del segundo plano pueden llegar dos avisos del navegador juntos\n if (isChecking) {\n return;\n }\n isChecking = true;\n try {\n const response = await fetch(url, {cache: 'no-store', headers: {Accept: 'application/json'}});\n const publishedBuildId = response.ok ? publishedBuildIdOf(await response.json()) : null;\n const action = buildWatchActionOf({\n runningBuildId: buildId ?? toolkitSettings().buildId,\n publishedBuildId,\n hiddenMilliseconds,\n reloadAfterHiddenMilliseconds: reloadAfterHiddenMinutes === null ? null : reloadAfterHiddenMinutes * MILLISECONDS_PER_MINUTE,\n });\n if (action === BUILD_WATCH_ACTION.reload) {\n onReload();\n } else if (action === BUILD_WATCH_ACTION.notice) {\n reportPublishedBuild(publishedBuildId, buildId ?? toolkitSettings().buildId);\n }\n } catch (error) {\n // sin red o con un archivo que no es JSON: no es algo que la persona pueda arreglar; queda en el registro\n log.info('No se pudo revisar la versión publicada', {url, reason: error.message});\n } finally {\n isChecking = false;\n }\n };\n\n // al irse a segundo plano se anota la hora; al volver se revisa con cuánto tiempo estuvo fuera\n const handleVisibilityChange = () => {\n if (document.visibilityState === 'hidden') {\n hiddenSince = Date.now();\n return;\n }\n const hiddenMilliseconds = hiddenSince === null ? 0 : Date.now() - hiddenSince;\n hiddenSince = null;\n check(hiddenMilliseconds);\n };\n // mientras se usa (en segundo plano el teléfono detiene los relojes de la página de todos modos)\n const timer = window.setInterval(() => {\n if (document.visibilityState === 'visible') {\n check();\n }\n }, checkEveryMinutes * MILLISECONDS_PER_MINUTE);\n\n check();\n document.addEventListener('visibilitychange', handleVisibilityChange);\n return () => {\n window.clearInterval(timer);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n };\n }, [url, buildId, checkEveryMinutes, reloadAfterHiddenMinutes, onReload]);\n}\n"],"names":[],"mappings":";;;;;;AA8BA,MAAM,MAAM,aAAa,aAAa;AACtC,MAAM,0BAA0B,KAAK;AAErC,MAAM,8BAA8B;AACpC,MAAM,sCAAsC;AAG5C,SAAS,aAAa;AAClB,SAAO,SAAS,OAAA;AACpB;AAaO,SAAS,uBAAuB;AAAA,EACnC,MAAM;AAAA,EAAM;AAAA,EAAS,oBAAoB;AAAA,EAA6B,2BAA2B;AAAA,EAAqC,WAAW;AACrJ,IAAI,IAAI;AACJ,YAAU,MAAM;AACZ,QAAI,CAAC,OAAO,OAAO,aAAa,aAAa;AACzC,aAAO;AAAA,IACX;AACA,QAAI,cAAc;AAClB,QAAI,aAAa;AAEjB,UAAM,QAAQ,OAAO,qBAAqB,MAAM;AAE5C,UAAI,YAAY;AACZ;AAAA,MACJ;AACA,mBAAa;AACb,UAAI;AACA,cAAM,WAAW,MAAM,MAAM,KAAK,EAAC,OAAO,YAAY,SAAS,EAAC,QAAQ,mBAAA,EAAkB,CAAE;AAC5F,cAAM,mBAAmB,SAAS,KAAK,mBAAmB,MAAM,SAAS,KAAA,CAAM,IAAI;AACnF,cAAM,SAAS,mBAAmB;AAAA,UAC9B,gBAAgB,WAAW,gBAAA,EAAkB;AAAA,UAC7C;AAAA,UACA;AAAA,UACA,+BAA+B,6BAA6B,OAAO,OAAO,2BAA2B;AAAA,QAAA,CACxG;AACD,YAAI,WAAW,mBAAmB,QAAQ;AACtC,mBAAA;AAAA,QACJ,WAAW,WAAW,mBAAmB,QAAQ;AAC7C,+BAAqB,kBAAkB,WAAW,gBAAA,EAAkB,OAAO;AAAA,QAC/E;AAAA,MACJ,SAAS,OAAO;AAEZ,YAAI,KAAK,2CAA2C,EAAC,KAAK,QAAQ,MAAM,SAAQ;AAAA,MACpF,UAAA;AACI,qBAAa;AAAA,MACjB;AAAA,IACJ;AAGA,UAAM,yBAAyB,MAAM;AACjC,UAAI,SAAS,oBAAoB,UAAU;AACvC,sBAAc,KAAK,IAAA;AACnB;AAAA,MACJ;AACA,YAAM,qBAAqB,gBAAgB,OAAO,IAAI,KAAK,QAAQ;AACnE,oBAAc;AACd,YAAM,kBAAkB;AAAA,IAC5B;AAEA,UAAM,QAAQ,OAAO,YAAY,MAAM;AACnC,UAAI,SAAS,oBAAoB,WAAW;AACxC,cAAA;AAAA,MACJ;AAAA,IACJ,GAAG,oBAAoB,uBAAuB;AAE9C,UAAA;AACA,aAAS,iBAAiB,oBAAoB,sBAAsB;AACpE,WAAO,MAAM;AACT,aAAO,cAAc,KAAK;AAC1B,eAAS,oBAAoB,oBAAoB,sBAAsB;AAAA,IAC3E;AAAA,EACJ,GAAG,CAAC,KAAK,SAAS,mBAAmB,0BAA0B,QAAQ,CAAC;AAC5E;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import Box from "@mui/material/Box";
|
|
5
5
|
import Button from "@mui/material/Button";
|
|
@@ -8,8 +8,9 @@ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
|
8
8
|
import { INSTALL_VARIANT } from "./installInstructions.js";
|
|
9
9
|
import InstallInstructionsDialog from "./InstallInstructionsDialog.js";
|
|
10
10
|
import { useInstallPrompt } from "./useInstallPrompt.js";
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
import ComponentTexts from "../toolkit/ComponentTexts.js";
|
|
12
|
+
function InstallAppSetting({ texts } = {}) {
|
|
13
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
13
14
|
const { variant, promptInstall } = useInstallPrompt();
|
|
14
15
|
const [stepsOpen, setStepsOpen] = useState(false);
|
|
15
16
|
if (!variant) {
|
|
@@ -22,10 +23,10 @@ function InstallAppSetting() {
|
|
|
22
23
|
setStepsOpen(true);
|
|
23
24
|
}
|
|
24
25
|
};
|
|
25
|
-
return /* @__PURE__ */ jsxs(Box, { sx: { px: 1, py: 0.5 }, children: [
|
|
26
|
+
return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "common", texts, children: /* @__PURE__ */ jsxs(Box, { sx: { px: 1, py: 0.5 }, children: [
|
|
26
27
|
/* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "outlined", startIcon: /* @__PURE__ */ jsx(InstallMobileRoundedIcon, {}), onClick: handleClick, children: t("install.menuLabel") }),
|
|
27
28
|
/* @__PURE__ */ jsx(InstallInstructionsDialog, { variant, open: stepsOpen, onClose: () => setStepsOpen(false) })
|
|
28
|
-
] });
|
|
29
|
+
] }) });
|
|
29
30
|
}
|
|
30
31
|
export {
|
|
31
32
|
InstallAppSetting as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstallAppSetting.js","sources":["../../src/pwa/InstallAppSetting.jsx"],"sourcesContent":["// «Instalar la app» dentro del menú de la cuenta (accountSettings de AppLayout): para quien dijo «Ahora no» a la invitación\n// y después sí la quiere. No respeta el «Ahora no» (aquí la persona la busca a propósito). Ya instalada o sin manera de\n// instalar en ese equipo, no pinta nada.\n//\n// El diálogo de los pasos va aquí mismo: el menú de la cuenta se queda abierto mientras se ve, y al cerrarlo se regresa al\n// menú.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\n\nexport default function InstallAppSetting() {\n const {t} = useToolkitTranslation('common');\n const {variant, promptInstall} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n if (!variant) {\n return null;\n }\n\n const handleClick = () => {\n if (variant === INSTALL_VARIANT.native) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n return (\n <Box sx={{px: 1, py: 0.5}}>\n
|
|
1
|
+
{"version":3,"file":"InstallAppSetting.js","sources":["../../src/pwa/InstallAppSetting.jsx"],"sourcesContent":["// «Instalar la app» dentro del menú de la cuenta (accountSettings de AppLayout): para quien dijo «Ahora no» a la invitación\n// y después sí la quiere. No respeta el «Ahora no» (aquí la persona la busca a propósito). Ya instalada o sin manera de\n// instalar en ese equipo, no pinta nada.\n//\n// El diálogo de los pasos va aquí mismo: el menú de la cuenta se queda abierto mientras se ve, y al cerrarlo se regresa al\n// menú.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n/**\n * @param {object} [props]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallAppSetting({texts} = {}) {\n const {t} = useToolkitTranslation('common', texts);\n const {variant, promptInstall} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n if (!variant) {\n return null;\n }\n\n const handleClick = () => {\n if (variant === INSTALL_VARIANT.native) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n return (\n <ComponentTexts namespace=\"common\" texts={texts}>\n <Box sx={{px: 1, py: 0.5}}>\n <Button fullWidth variant=\"outlined\" startIcon={<InstallMobileRoundedIcon />} onClick={handleClick}>\n {t('install.menuLabel')}\n </Button>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={() => setStepsOpen(false)} />\n </Box>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA,SAAwB,kBAAkB,EAAC,MAAA,IAAS,IAAI;AACpD,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAS,cAAA,IAAiB,iBAAA;AACjC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,cAAc,MAAM;AACtB,QAAI,YAAY,gBAAgB,QAAQ;AACpC,oBAAA;AAAA,IACJ,OAAO;AACH,mBAAa,IAAI;AAAA,IACrB;AAAA,EACJ;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,UAAS,OAC/B,UAAA,qBAAC,KAAA,EAAI,IAAI,EAAC,IAAI,GAAG,IAAI,OACjB,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO,WAAS,MAAC,SAAQ,YAAW,WAAW,oBAAC,0BAAA,CAAA,CAAyB,GAAI,SAAS,aAClF,UAAA,EAAE,mBAAmB,GAC1B;AAAA,IACA,oBAAC,6BAA0B,SAAkB,MAAM,WAAW,SAAS,MAAM,aAAa,KAAK,EAAA,CAAG;AAAA,EAAA,EAAA,CACtG,EAAA,CACJ;AAER;"}
|
|
@@ -16,8 +16,8 @@ const STEPS_BY_VARIANT = Object.freeze({
|
|
|
16
16
|
function variantHasInstructions(variant) {
|
|
17
17
|
return Boolean(STEPS_BY_VARIANT[variant]);
|
|
18
18
|
}
|
|
19
|
-
function InstallInstructionsDialog({ variant, open, onClose }) {
|
|
20
|
-
const { t } = useToolkitTranslation("common");
|
|
19
|
+
function InstallInstructionsDialog({ variant, open, onClose, texts }) {
|
|
20
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
21
21
|
const steps = STEPS_BY_VARIANT[variant];
|
|
22
22
|
if (!steps) {
|
|
23
23
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstallInstructionsDialog.js","sources":["../../src/pwa/InstallInstructionsDialog.jsx"],"sourcesContent":["// Los pasos para instalar la app a mano, según el equipo (iOS 26 cambió el camino; Firefox usa su propio menú). Lo usan la\n// invitación (InstallPromptBanner) y la entrada del menú de la cuenta (InstallAppSetting): los dos abren el mismo diálogo.\n// Si la variante no lleva pasos (el navegador tiene su propio diálogo, o no hay nada que ofrecer) no pinta nada.\n//\n// OJO, para quien lo monte: NO dentro de un Menu de MUI que se cierra; el Menu quita a sus hijos al cerrarse y el diálogo\n// nunca aparecería. Va junto al Menu o en un lugar que se quede pintado.\n//\n// Genérico del paquete: function (regla de la casa).\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 DialogTitle from '@mui/material/DialogTitle';\nimport Typography from '@mui/material/Typography';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\n\n// cada variante con pasos a mano → su título y sus pasos en los textos del paquete (common:install.<variante>)\nconst STEPS_BY_VARIANT = Object.freeze({\n [INSTALL_VARIANT.iosModern]: ['step1', 'step2', 'step3', 'step4'],\n [INSTALL_VARIANT.iosClassic]: ['step1', 'step2', 'step3'],\n [INSTALL_VARIANT.androidFirefox]: ['step1', 'step2', 'step3'],\n});\n\n/**\n * ¿Esta variante se instala con pasos a mano (y no con el diálogo del navegador)?\n *\n * @param {string|null} variant\n * @returns {boolean}\n */\nexport function variantHasInstructions(variant) {\n return Boolean(STEPS_BY_VARIANT[variant]);\n}\n\n/**\n * @param {object} props\n * @param {string|null} props.variant una de INSTALL_VARIANT (useInstallPrompt)\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n */\nexport default function InstallInstructionsDialog({variant, open, onClose}) {\n const {t} = useToolkitTranslation('common');\n const steps = STEPS_BY_VARIANT[variant];\n if (!steps) {\n return null;\n }\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{t(`install.${variant}.title`)}</DialogTitle>\n <DialogContent>\n {/* pasos numerados con palabras, sin dibujar los íconos del sistema: cambian entre versiones */}\n <Typography component=\"ol\" sx={{m: 0, pl: 3, display: 'grid', gap: 1.25}}>\n {steps.map((step) => (\n <Typography key={step} component=\"li\" variant=\"body1\">{t(`install.${variant}.${step}`)}</Typography>\n ))}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" onClick={onClose}>{t('install.gotIt')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAmBA,MAAM,mBAAmB,OAAO,OAAO;AAAA,EACnC,CAAC,gBAAgB,SAAS,GAAG,CAAC,SAAS,SAAS,SAAS,OAAO;AAAA,EAChE,CAAC,gBAAgB,UAAU,GAAG,CAAC,SAAS,SAAS,OAAO;AAAA,EACxD,CAAC,gBAAgB,cAAc,GAAG,CAAC,SAAS,SAAS,OAAO;AAChE,CAAC;AAQM,SAAS,uBAAuB,SAAS;AAC5C,SAAO,QAAQ,iBAAiB,OAAO,CAAC;AAC5C;
|
|
1
|
+
{"version":3,"file":"InstallInstructionsDialog.js","sources":["../../src/pwa/InstallInstructionsDialog.jsx"],"sourcesContent":["// Los pasos para instalar la app a mano, según el equipo (iOS 26 cambió el camino; Firefox usa su propio menú). Lo usan la\n// invitación (InstallPromptBanner) y la entrada del menú de la cuenta (InstallAppSetting): los dos abren el mismo diálogo.\n// Si la variante no lleva pasos (el navegador tiene su propio diálogo, o no hay nada que ofrecer) no pinta nada.\n//\n// OJO, para quien lo monte: NO dentro de un Menu de MUI que se cierra; el Menu quita a sus hijos al cerrarse y el diálogo\n// nunca aparecería. Va junto al Menu o en un lugar que se quede pintado.\n//\n// Genérico del paquete: function (regla de la casa).\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 DialogTitle from '@mui/material/DialogTitle';\nimport Typography from '@mui/material/Typography';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\n\n// cada variante con pasos a mano → su título y sus pasos en los textos del paquete (common:install.<variante>)\nconst STEPS_BY_VARIANT = Object.freeze({\n [INSTALL_VARIANT.iosModern]: ['step1', 'step2', 'step3', 'step4'],\n [INSTALL_VARIANT.iosClassic]: ['step1', 'step2', 'step3'],\n [INSTALL_VARIANT.androidFirefox]: ['step1', 'step2', 'step3'],\n});\n\n/**\n * ¿Esta variante se instala con pasos a mano (y no con el diálogo del navegador)?\n *\n * @param {string|null} variant\n * @returns {boolean}\n */\nexport function variantHasInstructions(variant) {\n return Boolean(STEPS_BY_VARIANT[variant]);\n}\n\n/**\n * @param {object} props\n * @param {string|null} props.variant una de INSTALL_VARIANT (useInstallPrompt)\n * @param {boolean} props.open\n * @param {() => void} props.onClose\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallInstructionsDialog({variant, open, onClose, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n const steps = STEPS_BY_VARIANT[variant];\n if (!steps) {\n return null;\n }\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{t(`install.${variant}.title`)}</DialogTitle>\n <DialogContent>\n {/* pasos numerados con palabras, sin dibujar los íconos del sistema: cambian entre versiones */}\n <Typography component=\"ol\" sx={{m: 0, pl: 3, display: 'grid', gap: 1.25}}>\n {steps.map((step) => (\n <Typography key={step} component=\"li\" variant=\"body1\">{t(`install.${variant}.${step}`)}</Typography>\n ))}\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" onClick={onClose}>{t('install.gotIt')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;AAmBA,MAAM,mBAAmB,OAAO,OAAO;AAAA,EACnC,CAAC,gBAAgB,SAAS,GAAG,CAAC,SAAS,SAAS,SAAS,OAAO;AAAA,EAChE,CAAC,gBAAgB,UAAU,GAAG,CAAC,SAAS,SAAS,OAAO;AAAA,EACxD,CAAC,gBAAgB,cAAc,GAAG,CAAC,SAAS,SAAS,OAAO;AAChE,CAAC;AAQM,SAAS,uBAAuB,SAAS;AAC5C,SAAO,QAAQ,iBAAiB,OAAO,CAAC;AAC5C;AASA,SAAwB,0BAA0B,EAAC,SAAS,MAAM,SAAS,SAAQ;AAC/E,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,QAAQ,iBAAiB,OAAO;AACtC,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,8BACK,QAAA,EAAO,MAAY,SAAkB,UAAS,MAAK,WAAS,MACzD,UAAA;AAAA,IAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,WAAW,OAAO,QAAQ,GAAE;AAAA,wBAC3C,eAAA,EAEG,UAAA,oBAAC,YAAA,EAAW,WAAU,MAAK,IAAI,EAAC,GAAG,GAAG,IAAI,GAAG,SAAS,QAAQ,KAAK,QAC9D,UAAA,MAAM,IAAI,CAAC,SACR,oBAAC,YAAA,EAAsB,WAAU,MAAK,SAAQ,SAAS,UAAA,EAAE,WAAW,OAAO,IAAI,IAAI,EAAE,KAApE,IAAsE,CAC1F,GACL,GACJ;AAAA,IACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,SAAS,SAAU,UAAA,EAAE,eAAe,EAAA,CAAE,EAAA,CACtE;AAAA,EAAA,GACJ;AAER;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs, Fragment
|
|
2
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import Alert from "@mui/material/Alert";
|
|
5
5
|
import Button from "@mui/material/Button";
|
|
@@ -13,27 +13,33 @@ import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
|
13
13
|
import { INSTALL_VARIANT } from "./installInstructions.js";
|
|
14
14
|
import InstallInstructionsDialog from "./InstallInstructionsDialog.js";
|
|
15
15
|
import { useInstallPrompt } from "./useInstallPrompt.js";
|
|
16
|
+
import ComponentTexts from "../toolkit/ComponentTexts.js";
|
|
16
17
|
const TAB_BAR_GAP_PIXELS = 8;
|
|
17
|
-
function InstallPromptBanner({ message }) {
|
|
18
|
-
const { t } = useToolkitTranslation("common");
|
|
18
|
+
function InstallPromptBanner({ message, onInstallChoice, onShowSteps, onDismiss, texts }) {
|
|
19
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
19
20
|
const { variant, dismissed, promptInstall, dismiss } = useInstallPrompt();
|
|
20
21
|
const [stepsOpen, setStepsOpen] = useState(false);
|
|
21
22
|
if (!variant || dismissed) {
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
const isNative = variant === INSTALL_VARIANT.native;
|
|
25
|
-
const handleAction = () => {
|
|
26
|
+
const handleAction = async () => {
|
|
26
27
|
if (isNative) {
|
|
27
|
-
promptInstall();
|
|
28
|
+
onInstallChoice?.(await promptInstall());
|
|
28
29
|
} else {
|
|
29
30
|
setStepsOpen(true);
|
|
31
|
+
onShowSteps?.();
|
|
30
32
|
}
|
|
31
33
|
};
|
|
34
|
+
const handleDismiss = () => {
|
|
35
|
+
dismiss();
|
|
36
|
+
onDismiss?.();
|
|
37
|
+
};
|
|
32
38
|
const handleStepsClose = () => {
|
|
33
39
|
setStepsOpen(false);
|
|
34
|
-
|
|
40
|
+
handleDismiss();
|
|
35
41
|
};
|
|
36
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "common", texts, children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
43
|
/* @__PURE__ */ jsx(
|
|
38
44
|
Snackbar,
|
|
39
45
|
{
|
|
@@ -48,7 +54,7 @@ function InstallPromptBanner({ message }) {
|
|
|
48
54
|
sx: { width: "100%", alignItems: "center" },
|
|
49
55
|
action: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
50
56
|
/* @__PURE__ */ jsx(Button, { color: "inherit", size: "small", onClick: handleAction, children: isNative ? t("install.action") : t("install.howTo") }),
|
|
51
|
-
/* @__PURE__ */ jsx(IconButton, { size: "small", color: "inherit", "aria-label": t("install.dismiss"), onClick:
|
|
57
|
+
/* @__PURE__ */ jsx(IconButton, { size: "small", color: "inherit", "aria-label": t("install.dismiss"), onClick: handleDismiss, children: /* @__PURE__ */ jsx(CloseRoundedIcon, { fontSize: "small" }) })
|
|
52
58
|
] }),
|
|
53
59
|
children: message ?? t("install.banner")
|
|
54
60
|
}
|
|
@@ -56,7 +62,7 @@ function InstallPromptBanner({ message }) {
|
|
|
56
62
|
}
|
|
57
63
|
),
|
|
58
64
|
/* @__PURE__ */ jsx(InstallInstructionsDialog, { variant, open: stepsOpen, onClose: handleStepsClose })
|
|
59
|
-
] });
|
|
65
|
+
] }) });
|
|
60
66
|
}
|
|
61
67
|
export {
|
|
62
68
|
InstallPromptBanner as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstallPromptBanner.js","sources":["../../src/pwa/InstallPromptBanner.jsx"],"sourcesContent":["// La invitación a instalar la app, abajo al centro. Según el equipo (useInstallPrompt): con el diálogo del navegador, el\n// botón «Instalar»; en iPhone, iPad y Firefox de Android, «Cómo instalar» abre los pasos (InstallInstructionsDialog).\n// Ya instalada, sin manera de instalar o después de «Ahora no», no pinta nada; quien se arrepienta la encuentra en el menú\n// de la cuenta (InstallAppSetting).\n//\n// Color de información, no de advertencia: instalar es una sugerencia, no un problema. En el teléfono con vidrio va encima\n// de la barra de pestañas; sin ella, respeta la zona del gesto de inicio del iPhone.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\n\n// lo que se separa de la barra de pestañas (px)\nconst TAB_BAR_GAP_PIXELS = 8;\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete con el nombre de la app (appName de ToolkitProvider)\n */\nexport default function InstallPromptBanner({message}) {\n const {t} = useToolkitTranslation('common');\n const {variant, dismissed, promptInstall, dismiss} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n // nada que ofrecer o ya dijo «Ahora no»\n if (!variant || dismissed) {\n return null;\n }\n\n const isNative = variant === INSTALL_VARIANT.native;\n\n const handleAction = () => {\n if (isNative) {\n promptInstall();\n } else {\n setStepsOpen(true);\n }\n };\n\n // si ya vio los pasos, no se le vuelven a ofrecer en cada visita\n const handleStepsClose = () => {\n setStepsOpen(false);\n
|
|
1
|
+
{"version":3,"file":"InstallPromptBanner.js","sources":["../../src/pwa/InstallPromptBanner.jsx"],"sourcesContent":["// La invitación a instalar la app, abajo al centro. Según el equipo (useInstallPrompt): con el diálogo del navegador, el\n// botón «Instalar»; en iPhone, iPad y Firefox de Android, «Cómo instalar» abre los pasos (InstallInstructionsDialog).\n// Ya instalada, sin manera de instalar o después de «Ahora no», no pinta nada; quien se arrepienta la encuentra en el menú\n// de la cuenta (InstallAppSetting).\n//\n// Color de información, no de advertencia: instalar es una sugerencia, no un problema. En el teléfono con vidrio va encima\n// de la barra de pestañas; sin ella, respeta la zona del gesto de inicio del iPhone.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\nimport Snackbar from '@mui/material/Snackbar';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\nimport InstallMobileRoundedIcon from '@mui/icons-material/InstallMobileRounded';\n\nimport {aboveMobileTabBarOf} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {INSTALL_VARIANT} from '@/pwa/installInstructions.js';\nimport InstallInstructionsDialog from '@/pwa/InstallInstructionsDialog.jsx';\nimport {useInstallPrompt} from '@/pwa/useInstallPrompt.js';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n// lo que se separa de la barra de pestañas (px)\nconst TAB_BAR_GAP_PIXELS = 8;\n\n/**\n * @param {object} [props]\n * @param {string} [props.message] ya traducido; por omisión el del paquete con el nombre de la app (appName de ToolkitProvider)\n * @param {(outcome: 'accepted'|'dismissed'|null) => void} [props.onInstallChoice] lo que contestó la persona al diálogo de\n * instalar del navegador (Chrome, Edge, Android)\n * @param {() => void} [props.onShowSteps] abrió los pasos para instalar (iPhone, iPad, Firefox de Android)\n * @param {() => void} [props.onDismiss] dijo «Ahora no» (o cerró los pasos): no se le vuelve a ofrecer en este navegador\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function InstallPromptBanner({message, onInstallChoice, onShowSteps, onDismiss, texts}) {\n const {t} = useToolkitTranslation('common', texts);\n const {variant, dismissed, promptInstall, dismiss} = useInstallPrompt();\n const [stepsOpen, setStepsOpen] = useState(false);\n\n // nada que ofrecer o ya dijo «Ahora no»\n if (!variant || dismissed) {\n return null;\n }\n\n const isNative = variant === INSTALL_VARIANT.native;\n\n const handleAction = async () => {\n if (isNative) {\n onInstallChoice?.(await promptInstall());\n } else {\n setStepsOpen(true);\n onShowSteps?.();\n }\n };\n const handleDismiss = () => {\n dismiss();\n onDismiss?.();\n };\n\n // si ya vio los pasos, no se le vuelven a ofrecer en cada visita\n const handleStepsClose = () => {\n setStepsOpen(false);\n handleDismiss();\n };\n\n return (\n <ComponentTexts namespace=\"common\" texts={texts}>\n <>\n <Snackbar open anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}\n sx={(theme) => (usesMobileTabBar(theme)\n ? aboveMobileTabBarOf(TAB_BAR_GAP_PIXELS)\n : {mb: 'env(safe-area-inset-bottom)'})}>\n <Alert severity=\"info\" icon={<InstallMobileRoundedIcon />} sx={{width: '100%', alignItems: 'center'}}\n action={(\n <>\n <Button color=\"inherit\" size=\"small\" onClick={handleAction}>\n {isNative ? t('install.action') : t('install.howTo')}\n </Button>\n <IconButton size=\"small\" color=\"inherit\" aria-label={t('install.dismiss')} onClick={handleDismiss}>\n <CloseRoundedIcon fontSize=\"small\" />\n </IconButton>\n </>\n )}>\n {message ?? t('install.banner')}\n </Alert>\n </Snackbar>\n <InstallInstructionsDialog variant={variant} open={stepsOpen} onClose={handleStepsClose} />\n </>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA0BA,MAAM,qBAAqB;AAW3B,SAAwB,oBAAoB,EAAC,SAAS,iBAAiB,aAAa,WAAW,SAAQ;AACnG,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,EAAC,SAAS,WAAW,eAAe,QAAA,IAAW,iBAAA;AACrD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAGhD,MAAI,CAAC,WAAW,WAAW;AACvB,WAAO;AAAA,EACX;AAEA,QAAM,WAAW,YAAY,gBAAgB;AAE7C,QAAM,eAAe,YAAY;AAC7B,QAAI,UAAU;AACV,wBAAkB,MAAM,eAAe;AAAA,IAC3C,OAAO;AACH,mBAAa,IAAI;AACjB,oBAAA;AAAA,IACJ;AAAA,EACJ;AACA,QAAM,gBAAgB,MAAM;AACxB,YAAA;AACA,gBAAA;AAAA,EACJ;AAGA,QAAM,mBAAmB,MAAM;AAC3B,iBAAa,KAAK;AAClB,kBAAA;AAAA,EACJ;AAEA,SACI,oBAAC,gBAAA,EAAe,WAAU,UAAS,OAC/B,UAAA,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAS,MAAI;AAAA,QAAC,cAAc,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,QACpD,IAAI,CAAC,UAAW,iBAAiB,KAAK,IAChC,oBAAoB,kBAAkB,IACtC,EAAC,IAAI,8BAAA;AAAA,QACjB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAM,UAAS;AAAA,YAAO,0BAAO,0BAAA,EAAyB;AAAA,YAAI,IAAI,EAAC,OAAO,QAAQ,YAAY,SAAA;AAAA,YACpF,QACI,qBAAA,UAAA,EACI,UAAA;AAAA,cAAA,oBAAC,QAAA,EAAO,OAAM,WAAU,MAAK,SAAQ,SAAS,cACzC,UAAA,WAAW,EAAE,gBAAgB,IAAI,EAAE,eAAe,GACvD;AAAA,kCACC,YAAA,EAAW,MAAK,SAAQ,OAAM,WAAU,cAAY,EAAE,iBAAiB,GAAG,SAAS,eAChF,UAAA,oBAAC,kBAAA,EAAiB,UAAS,SAAQ,EAAA,CACvC;AAAA,YAAA,GACJ;AAAA,YAEN,UAAA,WAAW,EAAE,gBAAgB;AAAA,UAAA;AAAA,QAAA;AAAA,MAClC;AAAA,IAAA;AAAA,wBAEH,2BAAA,EAA0B,SAAkB,MAAM,WAAW,SAAS,iBAAA,CAAkB;AAAA,EAAA,EAAA,CAC7F,EAAA,CACJ;AAER;"}
|
|
@@ -42,12 +42,13 @@ function useInstallPrompt() {
|
|
|
42
42
|
}, []);
|
|
43
43
|
const promptInstall = useCallback(async () => {
|
|
44
44
|
if (!deferredPrompt) {
|
|
45
|
-
return;
|
|
45
|
+
return null;
|
|
46
46
|
}
|
|
47
47
|
deferredPrompt.prompt();
|
|
48
48
|
const { outcome } = await deferredPrompt.userChoice;
|
|
49
49
|
log.info("respuesta al diálogo de instalar", { outcome });
|
|
50
50
|
setDeferredPrompt(null);
|
|
51
|
+
return outcome;
|
|
51
52
|
}, [deferredPrompt]);
|
|
52
53
|
const dismiss = useCallback(() => {
|
|
53
54
|
setDismissed(true);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInstallPrompt.js","sources":["../../src/pwa/useInstallPrompt.js"],"sourcesContent":["// La invitación a instalar la app (agregar a la pantalla de inicio): escucha al navegador, decide qué variante enseñar\n// (installInstructions.js) y recuerda el «Ahora no». Es el de walk-ot, ya probado en iPhone y Android.\n//\n// El navegador NO tiene una sola manera de instalar:\n// - Android con Chromium (Chrome, Samsung Internet, Edge) avisa con beforeinstallprompt. Se guarda el aviso (preventDefault\n// quita su barrita) y se lanza desde NUESTRO botón.\n// - Safari de iOS nunca avisa: se instala a mano y el camino cambió en iOS 26.\n// - Firefox de Android tampoco avisa: a mano.\n//\n// El «Ahora no» se guarda en este navegador. Mientras no se lee (en el servidor y en el primer pintado) se supone que ya\n// lo dijo: así la invitación nunca parpadea al cargar ni cambia entre el servidor y el navegador.\n//\n// Es un gancho: function, como los demás del paquete.\nimport {useCallback, useEffect, useMemo, useState} from 'react';\n\nimport {createLogger} from '@/observability/logger.js';\nimport {detectPlatform, resolveInstallVariant} from '@/pwa/installInstructions.js';\n\nconst log = createLogger('pwa');\n\nconst DISMISS_STORAGE_KEY = 'calcita:installPromptDismissed';\nconst DISMISSED = '1';\n\n/** ¿Ya se abrió como app instalada? En el iPhone lo dice navigator.standalone; en los demás, el display-mode. */\nfunction isRunningInstalled() {\n return window.matchMedia?.('(display-mode: standalone)').matches === true || window.navigator.standalone === true;\n}\n\n/** El «Ahora no» guardado; con el almacenamiento bloqueado se toma como no dicho. */\nfunction readDismissed() {\n try {\n return window.localStorage.getItem(DISMISS_STORAGE_KEY) === DISMISSED;\n } catch {\n return false;\n }\n}\n\n/**\n * @returns {{variant: string|null, isInstalled: boolean, dismissed: boolean, dismiss: () => void, promptInstall: () => Promise<
|
|
1
|
+
{"version":3,"file":"useInstallPrompt.js","sources":["../../src/pwa/useInstallPrompt.js"],"sourcesContent":["// La invitación a instalar la app (agregar a la pantalla de inicio): escucha al navegador, decide qué variante enseñar\n// (installInstructions.js) y recuerda el «Ahora no». Es el de walk-ot, ya probado en iPhone y Android.\n//\n// El navegador NO tiene una sola manera de instalar:\n// - Android con Chromium (Chrome, Samsung Internet, Edge) avisa con beforeinstallprompt. Se guarda el aviso (preventDefault\n// quita su barrita) y se lanza desde NUESTRO botón.\n// - Safari de iOS nunca avisa: se instala a mano y el camino cambió en iOS 26.\n// - Firefox de Android tampoco avisa: a mano.\n//\n// El «Ahora no» se guarda en este navegador. Mientras no se lee (en el servidor y en el primer pintado) se supone que ya\n// lo dijo: así la invitación nunca parpadea al cargar ni cambia entre el servidor y el navegador.\n//\n// Es un gancho: function, como los demás del paquete.\nimport {useCallback, useEffect, useMemo, useState} from 'react';\n\nimport {createLogger} from '@/observability/logger.js';\nimport {detectPlatform, resolveInstallVariant} from '@/pwa/installInstructions.js';\n\nconst log = createLogger('pwa');\n\nconst DISMISS_STORAGE_KEY = 'calcita:installPromptDismissed';\nconst DISMISSED = '1';\n\n/** ¿Ya se abrió como app instalada? En el iPhone lo dice navigator.standalone; en los demás, el display-mode. */\nfunction isRunningInstalled() {\n return window.matchMedia?.('(display-mode: standalone)').matches === true || window.navigator.standalone === true;\n}\n\n/** El «Ahora no» guardado; con el almacenamiento bloqueado se toma como no dicho. */\nfunction readDismissed() {\n try {\n return window.localStorage.getItem(DISMISS_STORAGE_KEY) === DISMISSED;\n } catch {\n return false;\n }\n}\n\n/**\n * @returns {{variant: string|null, isInstalled: boolean, dismissed: boolean, dismiss: () => void, promptInstall: () => Promise<'accepted'|'dismissed'|null>}}\n * promptInstall regresa lo que contestó la persona al diálogo del navegador (null: ya no había diálogo)\n * variant: una de INSTALL_VARIANT o null (nada que ofrecer). NO toma en cuenta el «Ahora no»: la invitación sí lo respeta,\n * pero la entrada del menú de la cuenta no, porque ahí la persona la busca a propósito.\n */\nexport function useInstallPrompt() {\n const [deferredPrompt, setDeferredPrompt] = useState(null);\n const [isInstalled, setIsInstalled] = useState(false);\n const [platform, setPlatform] = useState(null);\n // se supone dicho hasta leer el navegador (ver arriba)\n const [dismissed, setDismissed] = useState(true);\n\n useEffect(() => {\n setPlatform(detectPlatform(window.navigator));\n setIsInstalled(isRunningInstalled());\n setDismissed(readDismissed());\n\n const handleBeforeInstall = (event) => {\n // el momento lo elige la persona con nuestro botón, no la barrita del navegador\n event.preventDefault();\n setDeferredPrompt(event);\n };\n const handleInstalled = () => {\n setIsInstalled(true);\n setDeferredPrompt(null);\n log.info('app instalada');\n };\n window.addEventListener('beforeinstallprompt', handleBeforeInstall);\n window.addEventListener('appinstalled', handleInstalled);\n return () => {\n window.removeEventListener('beforeinstallprompt', handleBeforeInstall);\n window.removeEventListener('appinstalled', handleInstalled);\n };\n }, []);\n\n // El diálogo del navegador sirve UNA vez: con cualquier respuesta se suelta. Cancelarlo no es «Ahora no» (eso solo lo\n // dice el botón de la invitación).\n const promptInstall = useCallback(async () => {\n if (!deferredPrompt) {\n return null;\n }\n deferredPrompt.prompt();\n const {outcome} = await deferredPrompt.userChoice;\n log.info('respuesta al diálogo de instalar', {outcome});\n setDeferredPrompt(null);\n return outcome;\n }, [deferredPrompt]);\n\n const dismiss = useCallback(() => {\n setDismissed(true);\n try {\n window.localStorage.setItem(DISMISS_STORAGE_KEY, DISMISSED);\n } catch {\n // almacenamiento bloqueado: se cierra igual, solo no se recuerda\n }\n }, []);\n\n const variant = useMemo(() => {\n if (!platform) {\n return null;\n }\n return resolveInstallVariant({platform, canPromptNative: deferredPrompt !== null, isInstalled});\n }, [platform, deferredPrompt, isInstalled]);\n\n return {variant, isInstalled, dismissed, dismiss, promptInstall};\n}\n"],"names":[],"mappings":";;;;AAkBA,MAAM,MAAM,aAAa,KAAK;AAE9B,MAAM,sBAAsB;AAC5B,MAAM,YAAY;AAGlB,SAAS,qBAAqB;AAC1B,SAAO,OAAO,aAAa,4BAA4B,EAAE,YAAY,QAAQ,OAAO,UAAU,eAAe;AACjH;AAGA,SAAS,gBAAgB;AACrB,MAAI;AACA,WAAO,OAAO,aAAa,QAAQ,mBAAmB,MAAM;AAAA,EAChE,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAQO,SAAS,mBAAmB;AAC/B,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,IAAI;AACzD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,IAAI;AAE7C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAE/C,YAAU,MAAM;AACZ,gBAAY,eAAe,OAAO,SAAS,CAAC;AAC5C,mBAAe,mBAAkB,CAAE;AACnC,iBAAa,cAAa,CAAE;AAE5B,UAAM,sBAAsB,CAAC,UAAU;AAEnC,YAAM,eAAc;AACpB,wBAAkB,KAAK;AAAA,IAC3B;AACA,UAAM,kBAAkB,MAAM;AAC1B,qBAAe,IAAI;AACnB,wBAAkB,IAAI;AACtB,UAAI,KAAK,eAAe;AAAA,IAC5B;AACA,WAAO,iBAAiB,uBAAuB,mBAAmB;AAClE,WAAO,iBAAiB,gBAAgB,eAAe;AACvD,WAAO,MAAM;AACT,aAAO,oBAAoB,uBAAuB,mBAAmB;AACrE,aAAO,oBAAoB,gBAAgB,eAAe;AAAA,IAC9D;AAAA,EACJ,GAAG,CAAA,CAAE;AAIL,QAAM,gBAAgB,YAAY,YAAY;AAC1C,QAAI,CAAC,gBAAgB;AACjB,aAAO;AAAA,IACX;AACA,mBAAe,OAAM;AACrB,UAAM,EAAC,QAAO,IAAI,MAAM,eAAe;AACvC,QAAI,KAAK,oCAAoC,EAAC,QAAO,CAAC;AACtD,sBAAkB,IAAI;AACtB,WAAO;AAAA,EACX,GAAG,CAAC,cAAc,CAAC;AAEnB,QAAM,UAAU,YAAY,MAAM;AAC9B,iBAAa,IAAI;AACjB,QAAI;AACA,aAAO,aAAa,QAAQ,qBAAqB,SAAS;AAAA,IAC9D,QAAQ;AAAA,IAER;AAAA,EACJ,GAAG,CAAA,CAAE;AAEL,QAAM,UAAU,QAAQ,MAAM;AAC1B,QAAI,CAAC,UAAU;AACX,aAAO;AAAA,IACX;AACA,WAAO,sBAAsB,EAAC,UAAU,iBAAiB,mBAAmB,MAAM,YAAW,CAAC;AAAA,EAClG,GAAG,CAAC,UAAU,gBAAgB,WAAW,CAAC;AAE1C,SAAO,EAAC,SAAS,aAAa,WAAW,SAAS,cAAa;AACnE;"}
|
package/dist/react-hook-form.js
CHANGED
|
@@ -6,8 +6,9 @@ import { default as default3 } from "./hookForm/FormColorField.js";
|
|
|
6
6
|
import { default as default4 } from "./hookForm/FormNumberField.js";
|
|
7
7
|
import { default as default5 } from "./hookForm/FormProblemAlert.js";
|
|
8
8
|
import { default as default6 } from "./hookForm/FormSelectField.js";
|
|
9
|
-
import { default as default7 } from "./hookForm/
|
|
10
|
-
import { default as default8 } from "./hookForm/
|
|
9
|
+
import { default as default7 } from "./hookForm/FormWizard.js";
|
|
10
|
+
import { default as default8 } from "./hookForm/FormTextField.js";
|
|
11
|
+
import { default as default9 } from "./hookForm/SubmitButton.js";
|
|
11
12
|
import { SERVER_PROBLEM_ROOT, applyServerProblem } from "./hookForm/applyServerProblem.js";
|
|
12
13
|
import { useHookFormField } from "./hookForm/useHookFormField.js";
|
|
13
14
|
export {
|
|
@@ -18,9 +19,10 @@ export {
|
|
|
18
19
|
default5 as FormProblemAlert,
|
|
19
20
|
FormProvider,
|
|
20
21
|
default6 as FormSelectField,
|
|
21
|
-
|
|
22
|
+
default8 as FormTextField,
|
|
23
|
+
default7 as FormWizard,
|
|
22
24
|
SERVER_PROBLEM_ROOT,
|
|
23
|
-
|
|
25
|
+
default9 as SubmitButton,
|
|
24
26
|
applyServerProblem,
|
|
25
27
|
useForm,
|
|
26
28
|
useFormContext,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-hook-form.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"react-hook-form.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;"}
|
|
@@ -18,7 +18,7 @@ import PasswordOutlinedIcon from "@mui/icons-material/PasswordOutlined";
|
|
|
18
18
|
import PhonelinkLockOutlinedIcon from "@mui/icons-material/PhonelinkLockOutlined";
|
|
19
19
|
import PhonelinkEraseOutlinedIcon from "@mui/icons-material/PhonelinkEraseOutlined";
|
|
20
20
|
import SyncLockOutlinedIcon from "@mui/icons-material/SyncLockOutlined";
|
|
21
|
-
import {
|
|
21
|
+
import { useFormatters } from "../toolkit/useFormatters.js";
|
|
22
22
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
23
23
|
import SectionCard from "../ui/layout/SectionCard.js";
|
|
24
24
|
const ACCOUNT_EVENT_KIND = Object.freeze({
|
|
@@ -49,8 +49,9 @@ const EVENT_LOOK = Object.freeze({
|
|
|
49
49
|
});
|
|
50
50
|
const DEFAULT_LOOK = Object.freeze({ Icon: EventNoteOutlinedIcon });
|
|
51
51
|
const LOADING_HEIGHT_PIXELS = 160;
|
|
52
|
-
function ActivityLog({ events, error = null }) {
|
|
53
|
-
const { t } = useToolkitTranslation("security");
|
|
52
|
+
function ActivityLog({ events, error = null, texts }) {
|
|
53
|
+
const { t } = useToolkitTranslation("security", texts);
|
|
54
|
+
const { formatDateTime } = useFormatters();
|
|
54
55
|
return /* @__PURE__ */ jsx(SectionCard, { title: t("activity.title"), children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
|
|
55
56
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("activity.description") }),
|
|
56
57
|
error && /* @__PURE__ */ jsx(Alert, { severity: "error", children: error }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityLog.js","sources":["../../src/security/ActivityLog.jsx"],"sourcesContent":["// La bitácora de la cuenta: lo último que pasó (entró, contraseña equivocada, cambió su contraseña, activó la verificación,\n// agregó una llave…), con la hora, el equipo y la red a medias. Para que la persona reconozca lo que no fue ella.\n//\n// Cada evento llega con su tipo (kind) y el texto sale de los textos del paquete (security:activity.kinds.<kind>); un tipo\n// propio del proyecto trae su texto en description.\n//\n// <ActivityLog events={events} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\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 Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';\nimport EventNoteOutlinedIcon from '@mui/icons-material/EventNoteOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport KeyOutlinedIcon from '@mui/icons-material/KeyOutlined';\nimport LoginOutlinedIcon from '@mui/icons-material/LoginOutlined';\nimport LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';\nimport PasswordOutlinedIcon from '@mui/icons-material/PasswordOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport PhonelinkEraseOutlinedIcon from '@mui/icons-material/PhonelinkEraseOutlined';\nimport SyncLockOutlinedIcon from '@mui/icons-material/SyncLockOutlined';\n\nimport {
|
|
1
|
+
{"version":3,"file":"ActivityLog.js","sources":["../../src/security/ActivityLog.jsx"],"sourcesContent":["// La bitácora de la cuenta: lo último que pasó (entró, contraseña equivocada, cambió su contraseña, activó la verificación,\n// agregó una llave…), con la hora, el equipo y la red a medias. Para que la persona reconozca lo que no fue ella.\n//\n// Cada evento llega con su tipo (kind) y el texto sale de los textos del paquete (security:activity.kinds.<kind>); un tipo\n// propio del proyecto trae su texto en description.\n//\n// <ActivityLog events={events} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\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 Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';\nimport EventNoteOutlinedIcon from '@mui/icons-material/EventNoteOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport KeyOutlinedIcon from '@mui/icons-material/KeyOutlined';\nimport LoginOutlinedIcon from '@mui/icons-material/LoginOutlined';\nimport LogoutOutlinedIcon from '@mui/icons-material/LogoutOutlined';\nimport PasswordOutlinedIcon from '@mui/icons-material/PasswordOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport PhonelinkEraseOutlinedIcon from '@mui/icons-material/PhonelinkEraseOutlined';\nimport SyncLockOutlinedIcon from '@mui/icons-material/SyncLockOutlined';\n\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\n\n/** Los tipos de evento que el paquete conoce, con su texto en security:activity.kinds. */\nexport const ACCOUNT_EVENT_KIND = Object.freeze({\n signedIn: 'signedIn',\n signInFailed: 'signInFailed',\n signedOut: 'signedOut',\n passwordChanged: 'passwordChanged',\n twoStepEnabled: 'twoStepEnabled',\n twoStepDisabled: 'twoStepDisabled',\n passkeyAdded: 'passkeyAdded',\n passkeyRemoved: 'passkeyRemoved',\n recoveryCodesGenerated: 'recoveryCodesGenerated',\n recoveryCodeUsed: 'recoveryCodeUsed',\n sessionsClosed: 'sessionsClosed',\n});\n\n// el ícono de cada tipo, y en rojo lo que pide atención (un intento con contraseña equivocada, quitar protección)\nconst EVENT_LOOK = Object.freeze({\n [ACCOUNT_EVENT_KIND.signedIn]: {Icon: LoginOutlinedIcon},\n [ACCOUNT_EVENT_KIND.signInFailed]: {Icon: CancelOutlinedIcon, color: 'error'},\n [ACCOUNT_EVENT_KIND.signedOut]: {Icon: LogoutOutlinedIcon},\n [ACCOUNT_EVENT_KIND.passwordChanged]: {Icon: PasswordOutlinedIcon},\n [ACCOUNT_EVENT_KIND.twoStepEnabled]: {Icon: PhonelinkLockOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.twoStepDisabled]: {Icon: PhonelinkEraseOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.passkeyAdded]: {Icon: FingerprintOutlinedIcon, color: 'success'},\n [ACCOUNT_EVENT_KIND.passkeyRemoved]: {Icon: FingerprintOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.recoveryCodesGenerated]: {Icon: KeyOutlinedIcon},\n [ACCOUNT_EVENT_KIND.recoveryCodeUsed]: {Icon: KeyOutlinedIcon, color: 'warning'},\n [ACCOUNT_EVENT_KIND.sessionsClosed]: {Icon: SyncLockOutlinedIcon},\n});\nconst DEFAULT_LOOK = Object.freeze({Icon: EventNoteOutlinedIcon});\nconst LOADING_HEIGHT_PIXELS = 160;\n\n/**\n * @param {object} props\n * @param {Array<{id: string, kind: string, occurredAt: string, device?: string|null, network?: string|null, description?: string}>|null} props.events\n * del más nuevo al más viejo; null mientras llegan\n * @param {string|null} [props.error] ya traducido si no se pudieron traer\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function ActivityLog({events, error = null, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n\n return (\n <SectionCard title={t('activity.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!events && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {events?.length === 0 && <Typography variant=\"body2\" color=\"text.secondary\">{t('activity.empty')}</Typography>}\n {events?.length > 0 && (\n <List dense disablePadding>\n {events.map((event) => {\n const look = EVENT_LOOK[event.kind] ?? DEFAULT_LOOK;\n const where = event.device && event.network ? ` · ${t('activity.from', {device: event.device, network: event.network})}` : '';\n return (\n <ListItem key={event.id} disableGutters>\n <ListItemIcon sx={{minWidth: 36}}><look.Icon fontSize=\"small\" color={look.color ?? 'action'} /></ListItemIcon>\n <ListItemText primary={event.description ?? t(`activity.kinds.${event.kind}`)}\n secondary={`${formatDateTime(event.occurredAt)}${where}`} />\n </ListItem>\n );\n })}\n </List>\n )}\n </Stack>\n </SectionCard>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -20,8 +20,8 @@ const CODE_FONT = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
|
20
20
|
function todayText() {
|
|
21
21
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22
22
|
}
|
|
23
|
-
function RecoveryCodesDialog({ codes, account = "", onClose }) {
|
|
24
|
-
const { t } = useToolkitTranslation("security");
|
|
23
|
+
function RecoveryCodesDialog({ codes, account = "", onClose, onCodesSaved, texts }) {
|
|
24
|
+
const { t } = useToolkitTranslation("security", texts);
|
|
25
25
|
const { appName } = useToolkitSettings();
|
|
26
26
|
const [isSaved, setIsSaved] = useState(false);
|
|
27
27
|
const isOpen = Boolean(codes?.length);
|
|
@@ -39,6 +39,7 @@ function RecoveryCodesDialog({ codes, account = "", onClose }) {
|
|
|
39
39
|
""
|
|
40
40
|
];
|
|
41
41
|
saveBlob(new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" }), t("recoveryCodes.fileName", { date }));
|
|
42
|
+
onCodesSaved?.("download");
|
|
42
43
|
};
|
|
43
44
|
const close = () => {
|
|
44
45
|
setIsSaved(false);
|
|
@@ -63,7 +64,7 @@ function RecoveryCodesDialog({ codes, account = "", onClose }) {
|
|
|
63
64
|
columnGap: 4
|
|
64
65
|
}, children: (codes ?? []).map((code) => /* @__PURE__ */ jsx("li", { children: code }, code)) }),
|
|
65
66
|
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
|
|
66
|
-
/* @__PURE__ */ jsx(CopyButton, { value: (codes ?? []).join("\n"), label: t("recoveryCodes.copyAll") }),
|
|
67
|
+
/* @__PURE__ */ jsx(CopyButton, { value: (codes ?? []).join("\n"), label: t("recoveryCodes.copyAll"), onCopied: () => onCodesSaved?.("copy") }),
|
|
67
68
|
/* @__PURE__ */ jsx(Button, { size: "small", startIcon: /* @__PURE__ */ jsx(DownloadIcon, {}), onClick: download, children: t("recoveryCodes.download") })
|
|
68
69
|
] }),
|
|
69
70
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecoveryCodesDialog.js","sources":["../../src/security/RecoveryCodesDialog.jsx"],"sourcesContent":["// Los códigos de recuperación, que se enseñan UNA sola vez (el servidor solo guarda su huella, no los códigos).\n//\n// No se puede cerrar sin marcar «Ya los guardé»: es la única salida que tiene la persona el día que pierda su teléfono,\n// y un aviso que se cierra con Esc o con un clic afuera se cierra sin leer. Se pueden copiar todos o bajar en un .txt con\n// el nombre de la app, la cuenta y la fecha.\n//\n// <RecoveryCodesDialog codes={codes} account={session.email} onClose={() => setCodes(null)} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Checkbox from '@mui/material/Checkbox';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport DownloadIcon from '@mui/icons-material/Download';\n\nimport {saveBlob} from '@/files.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\n\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * La fecha de hoy como AAAA-MM-DD, para el nombre y el encabezado del archivo.\n *\n * @returns {string}\n */\nfunction todayText() {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * @param {object} props\n * @param {string[]|null} props.codes los códigos recién generados; sin códigos el diálogo está cerrado\n * @param {string} [props.account] la cuenta (normalmente el correo), para el archivo\n * @param {() => void} props.onClose solo se llama después de «Ya los guardé»\n */\nexport default function RecoveryCodesDialog({codes, account = '', onClose}) {\n const {t} = useToolkitTranslation('security');\n const {appName} = useToolkitSettings();\n const [isSaved, setIsSaved] = useState(false);\n const isOpen = Boolean(codes?.length);\n\n const download = () => {\n const date = todayText();\n const lines = [\n t('recoveryCodes.fileTitle', {appName}),\n t('recoveryCodes.fileAccount', {account}),\n t('recoveryCodes.fileGenerated', {date}),\n '',\n t('recoveryCodes.fileHowTo'),\n t('recoveryCodes.fileKeepSafe'),\n '',\n ...codes,\n '',\n ];\n saveBlob(new Blob([lines.join('\\n')], {type: 'text/plain;charset=utf-8'}), t('recoveryCodes.fileName', {date}));\n };\n\n const close = () => {\n setIsSaved(false);\n onClose();\n };\n\n return (\n // ni Esc ni un clic afuera lo cierran: solo «Listo», después de marcar que los guardó\n <Dialog open={isOpen} maxWidth=\"xs\" fullWidth disableEscapeKeyDown onClose={() => {}}>\n <DialogTitle>{t('recoveryCodes.title')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{t('recoveryCodes.warning')}</Alert>\n <Box component=\"ol\" sx={{\n m: 0, p: 2, pl: 5, borderRadius: 2, bgcolor: 'action.hover', fontFamily: CODE_FONT, fontSize: 15,\n columns: 2, columnGap: 4,\n }}>\n {(codes ?? []).map((code) => <li key={code}>{code}</li>)}\n </Box>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <CopyButton value={(codes ?? []).join('\\n')} label={t('recoveryCodes.copyAll')} />\n <Button size=\"small\" startIcon={<DownloadIcon />} onClick={download}>{t('recoveryCodes.download')}</Button>\n </Stack>\n <FormControlLabel control={<Checkbox checked={isSaved} onChange={(event) => setIsSaved(event.target.checked)} />}\n label={t('recoveryCodes.saved')} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" disabled={!isSaved} onClick={close}>{t('recoveryCodes.done')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAM,YAAY;AAOlB,SAAS,YAAY;AACjB,8BAAW,QAAO,cAAc,MAAM,GAAG,EAAE;AAC/C;
|
|
1
|
+
{"version":3,"file":"RecoveryCodesDialog.js","sources":["../../src/security/RecoveryCodesDialog.jsx"],"sourcesContent":["// Los códigos de recuperación, que se enseñan UNA sola vez (el servidor solo guarda su huella, no los códigos).\n//\n// No se puede cerrar sin marcar «Ya los guardé»: es la única salida que tiene la persona el día que pierda su teléfono,\n// y un aviso que se cierra con Esc o con un clic afuera se cierra sin leer. Se pueden copiar todos o bajar en un .txt con\n// el nombre de la app, la cuenta y la fecha.\n//\n// <RecoveryCodesDialog codes={codes} account={session.email} onClose={() => setCodes(null)} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Checkbox from '@mui/material/Checkbox';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport FormControlLabel from '@mui/material/FormControlLabel';\nimport Stack from '@mui/material/Stack';\nimport DownloadIcon from '@mui/icons-material/Download';\n\nimport {saveBlob} from '@/files.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\n\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * La fecha de hoy como AAAA-MM-DD, para el nombre y el encabezado del archivo.\n *\n * @returns {string}\n */\nfunction todayText() {\n return new Date().toISOString().slice(0, 10);\n}\n\n/**\n * @param {object} props\n * @param {string[]|null} props.codes los códigos recién generados; sin códigos el diálogo está cerrado\n * @param {string} [props.account] la cuenta (normalmente el correo), para el archivo\n * @param {() => void} props.onClose solo se llama después de «Ya los guardé»\n * @param {(method: 'copy'|'download') => void} [props.onCodesSaved] la persona los copió o los descargó (para la bitácora de\n * seguridad)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function RecoveryCodesDialog({codes, account = '', onClose, onCodesSaved, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const {appName} = useToolkitSettings();\n const [isSaved, setIsSaved] = useState(false);\n const isOpen = Boolean(codes?.length);\n\n const download = () => {\n const date = todayText();\n const lines = [\n t('recoveryCodes.fileTitle', {appName}),\n t('recoveryCodes.fileAccount', {account}),\n t('recoveryCodes.fileGenerated', {date}),\n '',\n t('recoveryCodes.fileHowTo'),\n t('recoveryCodes.fileKeepSafe'),\n '',\n ...codes,\n '',\n ];\n saveBlob(new Blob([lines.join('\\n')], {type: 'text/plain;charset=utf-8'}), t('recoveryCodes.fileName', {date}));\n onCodesSaved?.('download');\n };\n\n const close = () => {\n setIsSaved(false);\n onClose();\n };\n\n return (\n // ni Esc ni un clic afuera lo cierran: solo «Listo», después de marcar que los guardó\n <Dialog open={isOpen} maxWidth=\"xs\" fullWidth disableEscapeKeyDown onClose={() => {}}>\n <DialogTitle>{t('recoveryCodes.title')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Alert severity=\"warning\">{t('recoveryCodes.warning')}</Alert>\n <Box component=\"ol\" sx={{\n m: 0, p: 2, pl: 5, borderRadius: 2, bgcolor: 'action.hover', fontFamily: CODE_FONT, fontSize: 15,\n columns: 2, columnGap: 4,\n }}>\n {(codes ?? []).map((code) => <li key={code}>{code}</li>)}\n </Box>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <CopyButton value={(codes ?? []).join('\\n')} label={t('recoveryCodes.copyAll')} onCopied={() => onCodesSaved?.('copy')} />\n <Button size=\"small\" startIcon={<DownloadIcon />} onClick={download}>{t('recoveryCodes.download')}</Button>\n </Stack>\n <FormControlLabel control={<Checkbox checked={isSaved} onChange={(event) => setIsSaved(event.target.checked)} />}\n label={t('recoveryCodes.saved')} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button variant=\"contained\" disabled={!isSaved} onClick={close}>{t('recoveryCodes.done')}</Button>\n </DialogActions>\n </Dialog>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAM,YAAY;AAOlB,SAAS,YAAY;AACjB,8BAAW,QAAO,cAAc,MAAM,GAAG,EAAE;AAC/C;AAWA,SAAwB,oBAAoB,EAAC,OAAO,UAAU,IAAI,SAAS,cAAc,SAAQ;AAC7F,QAAM,EAAC,EAAA,IAAK,sBAAsB,YAAY,KAAK;AACnD,QAAM,EAAC,QAAA,IAAW,mBAAA;AAClB,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,SAAS,QAAQ,OAAO,MAAM;AAEpC,QAAM,WAAW,MAAM;AACnB,UAAM,OAAO,UAAA;AACb,UAAM,QAAQ;AAAA,MACV,EAAE,2BAA2B,EAAC,SAAQ;AAAA,MACtC,EAAE,6BAA6B,EAAC,SAAQ;AAAA,MACxC,EAAE,+BAA+B,EAAC,MAAK;AAAA,MACvC;AAAA,MACA,EAAE,yBAAyB;AAAA,MAC3B,EAAE,4BAA4B;AAAA,MAC9B;AAAA,MACA,GAAG;AAAA,MACH;AAAA,IAAA;AAEJ,aAAS,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG,EAAC,MAAM,2BAAA,CAA2B,GAAG,EAAE,0BAA0B,EAAC,KAAA,CAAK,CAAC;AAC9G,mBAAe,UAAU;AAAA,EAC7B;AAEA,QAAM,QAAQ,MAAM;AAChB,eAAW,KAAK;AAChB,YAAA;AAAA,EACJ;AAEA;AAAA;AAAA,IAEI,qBAAC,QAAA,EAAO,MAAM,QAAQ,UAAS,MAAK,WAAS,MAAC,sBAAoB,MAAC,SAAS,MAAM;AAAA,IAAC,GAC/E,UAAA;AAAA,MAAA,oBAAC,aAAA,EAAa,UAAA,EAAE,qBAAqB,EAAA,CAAE;AAAA,MACvC,oBAAC,eAAA,EACG,UAAA,qBAAC,OAAA,EAAM,SAAS,GACZ,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAM,UAAS,WAAW,UAAA,EAAE,uBAAuB,GAAE;AAAA,QACtD,oBAAC,KAAA,EAAI,WAAU,MAAK,IAAI;AAAA,UACpB,GAAG;AAAA,UAAG,GAAG;AAAA,UAAG,IAAI;AAAA,UAAG,cAAc;AAAA,UAAG,SAAS;AAAA,UAAgB,YAAY;AAAA,UAAW,UAAU;AAAA,UAC9F,SAAS;AAAA,UAAG,WAAW;AAAA,QAAA,GAErB,WAAA,SAAS,CAAA,GAAI,IAAI,CAAC,SAAS,oBAAC,MAAA,EAAe,UAAA,KAAA,GAAP,IAAY,CAAK,EAAA,CAC3D;AAAA,QACA,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,GAAG,IAAI,EAAC,YAAY,SAAA,GAChD,UAAA;AAAA,UAAA,oBAAC,cAAW,QAAQ,SAAS,CAAA,GAAI,KAAK,IAAI,GAAG,OAAO,EAAE,uBAAuB,GAAG,UAAU,MAAM,eAAe,MAAM,GAAG;AAAA,UACxH,oBAAC,QAAA,EAAO,MAAK,SAAQ,WAAW,oBAAC,cAAA,CAAA,CAAa,GAAI,SAAS,UAAW,UAAA,EAAE,wBAAwB,EAAA,CAAE;AAAA,QAAA,GACtG;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YAAiB,SAAS,oBAAC,UAAA,EAAS,SAAS,SAAS,UAAU,CAAC,UAAU,WAAW,MAAM,OAAO,OAAO,EAAA,CAAG;AAAA,YAC5F,OAAO,EAAE,qBAAqB;AAAA,UAAA;AAAA,QAAA;AAAA,MAAG,EAAA,CACvD,EAAA,CACJ;AAAA,MACA,oBAAC,eAAA,EACG,UAAA,oBAAC,QAAA,EAAO,SAAQ,aAAY,UAAU,CAAC,SAAS,SAAS,OAAQ,UAAA,EAAE,oBAAoB,GAAE,EAAA,CAC7F;AAAA,IAAA,EAAA,CACJ;AAAA;AAER;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import Alert from "@mui/material/Alert";
|
|
4
4
|
import Button from "@mui/material/Button";
|
|
5
5
|
import Divider from "@mui/material/Divider";
|
|
@@ -15,13 +15,15 @@ import PhoneIphoneOutlinedIcon from "@mui/icons-material/PhoneIphoneOutlined";
|
|
|
15
15
|
import ConfirmDialog from "../feedback/ConfirmDialog.js";
|
|
16
16
|
import { toastSuccess } from "../feedback/toast.js";
|
|
17
17
|
import { useConfirmAction } from "../feedback/useConfirmAction.js";
|
|
18
|
-
import {
|
|
18
|
+
import { useFormatters } from "../toolkit/useFormatters.js";
|
|
19
19
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
20
20
|
import Tag from "../ui/labels/Tag.js";
|
|
21
21
|
import SectionCard from "../ui/layout/SectionCard.js";
|
|
22
|
+
import ComponentTexts from "../toolkit/ComponentTexts.js";
|
|
22
23
|
const LOADING_HEIGHT_PIXELS = 96;
|
|
23
24
|
function SessionRow({ session }) {
|
|
24
25
|
const { t } = useToolkitTranslation("security");
|
|
26
|
+
const { formatDateTime } = useFormatters();
|
|
25
27
|
const DeviceIcon = session.isMobile ? PhoneIphoneOutlinedIcon : ComputerOutlinedIcon;
|
|
26
28
|
const device = t("sessions.device", {
|
|
27
29
|
browser: session.browser ?? t("sessions.otherBrowser"),
|
|
@@ -39,8 +41,8 @@ function SessionRow({ session }) {
|
|
|
39
41
|
] }), secondary: details.join(" · "), slotProps: { primary: { sx: { fontWeight: 600 } } } })
|
|
40
42
|
] });
|
|
41
43
|
}
|
|
42
|
-
function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
|
|
43
|
-
const { t } = useToolkitTranslation("security");
|
|
44
|
+
function SessionsCard({ sessions, error = null, onCloseOthers, onClosed, texts }) {
|
|
45
|
+
const { t } = useToolkitTranslation("security", texts);
|
|
44
46
|
const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;
|
|
45
47
|
const confirmation = useConfirmAction({
|
|
46
48
|
run: () => onCloseOthers(),
|
|
@@ -49,7 +51,7 @@ function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
|
|
|
49
51
|
onClosed?.();
|
|
50
52
|
}
|
|
51
53
|
});
|
|
52
|
-
return /* @__PURE__ */ jsxs(SectionCard, { title: t("sessions.title"), children: [
|
|
54
|
+
return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "security", texts, children: /* @__PURE__ */ jsxs(SectionCard, { title: t("sessions.title"), children: [
|
|
53
55
|
/* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
|
|
54
56
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("sessions.description") }),
|
|
55
57
|
error && /* @__PURE__ */ jsx(Alert, { severity: "error", children: error }),
|
|
@@ -73,7 +75,7 @@ function SessionsCard({ sessions, error = null, onCloseOthers, onClosed }) {
|
|
|
73
75
|
confirmLabel: t("sessions.confirm")
|
|
74
76
|
}
|
|
75
77
|
)
|
|
76
|
-
] });
|
|
78
|
+
] }) });
|
|
77
79
|
}
|
|
78
80
|
export {
|
|
79
81
|
SessionsCard as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsCard.js","sources":["../../src/security/SessionsCard.jsx"],"sourcesContent":["// «Mis sesiones»: los equipos donde la cuenta sigue abierta (este, marcado) y «Cerrar la sesión en los demás equipos».\n//\n// No pide la contraseña: cerrar sesiones no da acceso a nada, solo lo quita. Nunca se pinta la IP completa ni el user\n// agent: el servidor manda el navegador, el sistema y la red a medias (p. ej. «187.190.x.x»), que bastan para reconocer\n// un equipo sin exponer datos.\n//\n// <SessionsCard sessions={sessions} onCloseOthers={async () => (await api.post('/sessions/close-others')).closed} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\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 Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ComputerOutlinedIcon from '@mui/icons-material/ComputerOutlined';\nimport PhoneIphoneOutlinedIcon from '@mui/icons-material/PhoneIphoneOutlined';\n\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {
|
|
1
|
+
{"version":3,"file":"SessionsCard.js","sources":["../../src/security/SessionsCard.jsx"],"sourcesContent":["// «Mis sesiones»: los equipos donde la cuenta sigue abierta (este, marcado) y «Cerrar la sesión en los demás equipos».\n//\n// No pide la contraseña: cerrar sesiones no da acceso a nada, solo lo quita. Nunca se pinta la IP completa ni el user\n// agent: el servidor manda el navegador, el sistema y la red a medias (p. ej. «187.190.x.x»), que bastan para reconocer\n// un equipo sin exponer datos.\n//\n// <SessionsCard sessions={sessions} onCloseOthers={async () => (await api.post('/sessions/close-others')).closed} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport Alert from '@mui/material/Alert';\nimport Button from '@mui/material/Button';\nimport Divider from '@mui/material/Divider';\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 Skeleton from '@mui/material/Skeleton';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport ComputerOutlinedIcon from '@mui/icons-material/ComputerOutlined';\nimport PhoneIphoneOutlinedIcon from '@mui/icons-material/PhoneIphoneOutlined';\n\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\nconst LOADING_HEIGHT_PIXELS = 96;\n\n/**\n * Un renglón: el equipo (navegador en sistema), desde cuándo y su red a medias.\n *\n * @param {{session: object}} props\n */\nfunction SessionRow({session}) {\n const {t} = useToolkitTranslation('security');\n const {formatDateTime} = useFormatters();\n const DeviceIcon = session.isMobile ? PhoneIphoneOutlinedIcon : ComputerOutlinedIcon;\n const device = t('sessions.device', {\n browser: session.browser ?? t('sessions.otherBrowser'), platform: session.platform ?? t('sessions.otherPlatform'),\n });\n const details = [t('sessions.since', {date: formatDateTime(session.startedAt)})];\n if (session.network) {\n details.push(t('sessions.network', {network: session.network}));\n }\n\n return (\n <ListItem disableGutters alignItems=\"flex-start\" sx={{gap: 1}}>\n <ListItemIcon sx={{minWidth: 36, mt: 0.5}}><DeviceIcon /></ListItemIcon>\n <ListItemText primary={(\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{alignItems: 'center', flexWrap: 'wrap'}}>\n <span>{device}</span>\n {session.isCurrent && <Tag label={t('sessions.current')} color=\"success\" />}\n </Stack>\n )} secondary={details.join(' · ')} slotProps={{primary: {sx: {fontWeight: 600}}}} />\n </ListItem>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, browser?: string|null, platform?: string|null, isMobile?: boolean, startedAt: string, network?: string|null, isCurrent?: boolean}>|null} props.sessions\n * null mientras llegan\n * @param {string|null} [props.error] ya traducido (ApiError.message) si no se pudieron traer\n * @param {() => Promise<number>} props.onCloseOthers cierra las demás; regresa cuántas cerró\n * @param {() => void} [props.onClosed] p. ej. volver a pedir la lista\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function SessionsCard({sessions, error = null, onCloseOthers, onClosed, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const othersCount = (sessions ?? []).filter((session) => !session.isCurrent).length;\n const confirmation = useConfirmAction({\n run: () => onCloseOthers(),\n onDone: (closedCount) => {\n toastSuccess(closedCount ? t('sessions.closed', {count: closedCount}) : t('sessions.noneClosed'));\n onClosed?.();\n },\n });\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('sessions.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('sessions.description')}</Typography>\n {error && <Alert severity=\"error\">{error}</Alert>}\n {!sessions && !error && <Skeleton variant=\"rounded\" height={LOADING_HEIGHT_PIXELS} />}\n {sessions && (\n <List disablePadding>\n {sessions.map((session, index) => (\n <li key={session.id}>\n {index > 0 && <Divider component=\"div\" />}\n <SessionRow session={session} />\n </li>\n ))}\n </List>\n )}\n <Stack spacing={1} sx={{alignItems: 'flex-start'}}>\n {sessions && othersCount === 0 && <Typography variant=\"caption\" color=\"text.secondary\">{t('sessions.onlyThis')}</Typography>}\n <Button variant=\"outlined\" color=\"warning\" disabled={!sessions || othersCount === 0} onClick={() => confirmation.open({})}>\n {t('sessions.closeOthers')}\n </Button>\n </Stack>\n </Stack>\n <ConfirmDialog {...confirmation.dialogProps} tone=\"warning\" title={t('sessions.confirmTitle')}\n description={t('sessions.confirmText', {count: othersCount})} confirmLabel={t('sessions.confirm')} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/dist/table.js
CHANGED
|
@@ -11,6 +11,7 @@ import { toServerTableParams } from "./DataTable/serverTableParams.js";
|
|
|
11
11
|
import { buildTableActors, tableMachine } from "./DataTable/tableMachine.js";
|
|
12
12
|
import { useDebouncedFilter } from "./DataTable/useDebouncedFilter.js";
|
|
13
13
|
import { FILTER_KINDS, isFilterActive, useTableState } from "./DataTable/useTableState.js";
|
|
14
|
+
import { SELECTION_PHONE_LAYOUT } from "./ui/actions/SelectionActions.js";
|
|
14
15
|
export {
|
|
15
16
|
default2 as AuditTrailDialog,
|
|
16
17
|
default3 as AuditTrailTable,
|
|
@@ -18,6 +19,7 @@ export {
|
|
|
18
19
|
FILTER_KINDS,
|
|
19
20
|
default5 as FilterChips,
|
|
20
21
|
default6 as GoToPage,
|
|
22
|
+
SELECTION_PHONE_LAYOUT,
|
|
21
23
|
default7 as ServerTable,
|
|
22
24
|
TableFilterContext,
|
|
23
25
|
buildChipConfig,
|
package/dist/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"table.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|