@jmruthers/pace-core 0.4.1 → 0.5.1
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/CHANGELOG.md +26 -1
- package/README.md +231 -229
- package/dist/{DataTable-2LB6HI6V.js → DataTable-GX3XERFJ.js} +14 -17
- package/dist/{DataTable-BDBqkU-i.d.ts → DataTable-ltTFXHS3.d.ts} +25 -51
- package/dist/{Table-CIm9IWqk.d.ts → PublicLoadingSpinner-DztrzuJr.d.ts} +635 -122
- package/dist/UnifiedAuthProvider-w66zSCUf.d.ts +160 -0
- package/dist/{api-AIJ3IJX3.js → api-ETQ6YJ3C.js} +6 -4
- package/dist/{appConfig-fB1pP_v3.d.ts → appConfig-BVGyuvI7.d.ts} +1 -1
- package/dist/appNameResolver-7GHF5ED2.js +22 -0
- package/dist/{audit-PD5L5ZSC.js → audit-BUW3LMJB.js} +3 -3
- package/dist/chunk-5EL3KHOQ.js +388 -0
- package/dist/chunk-5EL3KHOQ.js.map +1 -0
- package/dist/{chunk-4MCJAK7J.js → chunk-6CR3MRZN.js} +1827 -4886
- package/dist/chunk-6CR3MRZN.js.map +1 -0
- package/dist/{chunk-YNU5QJ4S.js → chunk-7BNPOCLL.js} +22 -5
- package/dist/chunk-7BNPOCLL.js.map +1 -0
- package/dist/chunk-AUE24LVR.js +268 -0
- package/dist/chunk-AUE24LVR.js.map +1 -0
- package/dist/chunk-C5G2A4PO.js +1349 -0
- package/dist/chunk-C5G2A4PO.js.map +1 -0
- package/dist/{chunk-4ZTIEYU2.js → chunk-CDQ3PX7L.js} +1 -1
- package/dist/chunk-CDQ3PX7L.js.map +1 -0
- package/dist/chunk-COBPIXXQ.js +379 -0
- package/dist/chunk-COBPIXXQ.js.map +1 -0
- package/dist/chunk-GSNM5D6H.js +5441 -0
- package/dist/chunk-GSNM5D6H.js.map +1 -0
- package/dist/chunk-MZBUOP4P.js +119 -0
- package/dist/chunk-MZBUOP4P.js.map +1 -0
- package/dist/chunk-N2EUGZRW.js +98 -0
- package/dist/chunk-N2EUGZRW.js.map +1 -0
- package/dist/chunk-NQ4TOOO6.js +20 -0
- package/dist/chunk-NQ4TOOO6.js.map +1 -0
- package/dist/{chunk-KK6WIDK6.js → chunk-OEGRKULD.js} +12 -2
- package/dist/{chunk-KK6WIDK6.js.map → chunk-OEGRKULD.js.map} +1 -1
- package/dist/chunk-OYRY44Q2.js +62 -0
- package/dist/chunk-OYRY44Q2.js.map +1 -0
- package/dist/{chunk-DC5AMYBS.js → chunk-PLDDJCW6.js} +15 -5
- package/dist/chunk-PLDDJCW6.js.map +1 -0
- package/dist/{chunk-WHLSWC6W.js → chunk-SS3E6QLB.js} +16 -61
- package/dist/chunk-SS3E6QLB.js.map +1 -0
- package/dist/chunk-T3XIA4AJ.js +3295 -0
- package/dist/chunk-T3XIA4AJ.js.map +1 -0
- package/dist/{chunk-H4PZ4B3Y.js → chunk-TGDCLPP2.js} +129 -28
- package/dist/chunk-TGDCLPP2.js.map +1 -0
- package/dist/{chunk-IOX76PSM.js → chunk-U6JDHVC2.js} +273 -29
- package/dist/chunk-U6JDHVC2.js.map +1 -0
- package/dist/{chunk-JUUNUW3O.js → chunk-XJK2J4N6.js} +17 -6
- package/dist/chunk-XJK2J4N6.js.map +1 -0
- package/dist/chunk-YDJW5XTN.js +84 -0
- package/dist/chunk-YDJW5XTN.js.map +1 -0
- package/dist/components.d.ts +906 -10
- package/dist/components.js +3263 -84
- package/dist/components.js.map +1 -1
- package/dist/{database-CAMsquLm.d.ts → database-C3Szpi5J.d.ts} +28 -11
- package/dist/hooks.d.ts +7 -6
- package/dist/hooks.js +35 -11
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +245 -111
- package/dist/index.js +195 -185
- package/dist/index.js.map +1 -1
- package/dist/{organisation-DLNNQhPB.d.ts → organisation-CO3Sh3_D.d.ts} +1 -1
- package/dist/providers.d.ts +4 -4
- package/dist/providers.js +21 -6
- package/dist/rbac/index.d.ts +862 -806
- package/dist/rbac/index.js +953 -1032
- package/dist/rbac/index.js.map +1 -1
- package/dist/styles/core.css +422 -0
- package/dist/styles/fonts/georama-italic.woff2 +0 -0
- package/dist/styles/fonts/georama.woff2 +0 -0
- package/dist/styles/fonts/open-sans-italic.woff2 +0 -0
- package/dist/styles/fonts/open-sans.woff2 +0 -0
- package/dist/styles/fonts/reddit-mono.woff2 +0 -0
- package/dist/styles/index.d.ts +36 -0
- package/dist/styles/index.js +24 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/theming/runtime.d.ts +73 -0
- package/dist/theming/runtime.js +16 -0
- package/dist/theming/runtime.js.map +1 -0
- package/dist/{types-Bavn44NW.d.ts → types-BRDU7N6w.d.ts} +79 -33
- package/dist/types.d.ts +5 -5
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/dist/{unified-BtRpPbmp.d.ts → unified-CM7T0aTK.d.ts} +1 -2
- package/dist/usePublicRouteParams-B6i0KtXW.d.ts +477 -0
- package/dist/utils.d.ts +83 -60
- package/dist/utils.js +301 -55657
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +1 -1
- package/docs/INDEX.md +192 -0
- package/docs/README.md +46 -32
- package/docs/api/README.md +231 -229
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +132 -0
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/ButtonProps.md +2 -2
- package/docs/api/interfaces/CardProps.md +2 -2
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +98 -7
- package/docs/api/interfaces/DataTableColumn.md +131 -12
- package/docs/api/interfaces/DataTableProps.md +77 -274
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EventContextType.md +7 -7
- package/docs/api/interfaces/EventLogoProps.md +152 -0
- package/docs/api/interfaces/EventProviderProps.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +7 -0
- package/docs/api/interfaces/FileUploadProps.md +154 -0
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +115 -0
- package/docs/api/interfaces/InputProps.md +2 -2
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +2 -2
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +26 -26
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +94 -0
- package/docs/api/interfaces/PublicErrorBoundaryState.md +68 -0
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +86 -0
- package/docs/api/interfaces/PublicPageFooterProps.md +112 -0
- package/docs/api/interfaces/PublicPageHeaderProps.md +138 -0
- package/docs/api/interfaces/PublicPageLayoutProps.md +138 -0
- package/docs/api/interfaces/StorageConfig.md +41 -0
- package/docs/api/interfaces/StorageFileInfo.md +74 -0
- package/docs/api/interfaces/StorageFileMetadata.md +140 -0
- package/docs/api/interfaces/StorageListOptions.md +86 -0
- package/docs/api/interfaces/StorageListResult.md +41 -0
- package/docs/api/interfaces/StorageUploadOptions.md +88 -0
- package/docs/api/interfaces/StorageUploadResult.md +63 -0
- package/docs/api/interfaces/StorageUrlOptions.md +47 -0
- package/docs/api/interfaces/StyleImport.md +2 -2
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +447 -46
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +95 -9
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +136 -0
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +123 -0
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +87 -0
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +81 -0
- package/docs/api/interfaces/UsePublicEventOptions.md +34 -0
- package/docs/api/interfaces/UsePublicEventReturn.md +68 -0
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +94 -0
- package/docs/api/interfaces/UserEventAccess.md +14 -14
- package/docs/api/interfaces/UserMenuProps.md +6 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +1626 -768
- package/docs/api-reference/components.md +761 -43
- package/docs/api-reference/hooks.md +126 -0
- package/docs/api-reference/providers.md +141 -65
- package/docs/api-reference/types.md +66 -36
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -2
- package/docs/best-practices/README.md +400 -0
- package/docs/consuming-app-example.md +42 -96
- package/docs/consuming-app-vite-config.md +233 -0
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +0 -1
- package/docs/core-concepts/rbac-system.md +23 -10
- package/docs/documentation-style-checklist.md +8 -2
- package/docs/examples/navigation-menu-auth-fix.md +344 -0
- package/docs/getting-started/examples/README.md +15 -1
- package/docs/getting-started/examples/basic-auth-app.md +444 -119
- package/docs/getting-started/examples/full-featured-app.md +6 -6
- package/docs/getting-started/installation.md +231 -52
- package/docs/getting-started/quick-start.md +121 -24
- package/docs/implementation-guides/app-layout.md +133 -108
- package/docs/implementation-guides/data-tables.md +1011 -29
- package/docs/implementation-guides/forms.md +3 -3
- package/docs/implementation-guides/hierarchical-datatable.md +850 -0
- package/docs/implementation-guides/large-datasets.md +2 -2
- package/docs/implementation-guides/navigation.md +1 -1
- package/docs/implementation-guides/permission-enforcement.md +4 -4
- package/docs/implementation-guides/public-pages.md +752 -0
- package/docs/migration/README.md +18 -8
- package/docs/migration/quick-migration-guide.md +320 -0
- package/docs/migration/rbac-migration.md +50 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +272 -0
- package/docs/migration/v0.4.16-css-first-approach.md +306 -0
- package/docs/migration/v0.4.17-source-path-fix.md +229 -0
- package/docs/migration-guide.md +51 -104
- package/docs/performance/README.md +1 -4
- package/docs/print-components/README.md +258 -0
- package/docs/print-components/api-reference.md +636 -0
- package/docs/print-components/examples/README.md +204 -0
- package/docs/print-components/examples/basic-report.tsx +92 -0
- package/docs/print-components/examples/card-catalog.tsx +149 -0
- package/docs/print-components/examples/cover-page-report.tsx +163 -0
- package/docs/print-components/quick-start.md +363 -0
- package/docs/quick-reference.md +53 -36
- package/docs/rbac/README.md +136 -69
- package/docs/rbac/api-reference.md +39 -8
- package/docs/rbac/examples.md +237 -66
- package/docs/rbac/getting-started.md +131 -16
- package/docs/rbac/quick-start.md +499 -323
- package/docs/rbac/troubleshooting.md +240 -262
- package/docs/security/README.md +50 -1
- package/docs/styles/README.md +143 -117
- package/docs/testing/README.md +6 -10
- package/docs/troubleshooting/README.md +497 -0
- package/docs/troubleshooting/common-issues.md +604 -14
- package/docs/troubleshooting/styling-issues.md +219 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +213 -0
- package/docs/usage.md +28 -90
- package/docs/visual-testing.md +0 -7
- package/package.json +46 -24
- package/src/__mocks__/lucide-react.ts +181 -0
- package/src/__tests__/REBUILD_PLAN.md +223 -0
- package/src/__tests__/TESTING_GUIDELINES.md +341 -0
- package/src/__tests__/fixtures/mocks.ts +93 -0
- package/src/__tests__/helpers/component-test-utils.tsx +145 -0
- package/src/__tests__/helpers/test-utils.tsx +117 -0
- package/src/__tests__/integration/UserProfile.test.tsx +128 -0
- package/src/__tests__/setup.ts +71 -0
- package/src/__tests__/templates/accessibility.test.template.tsx +279 -0
- package/src/__tests__/templates/component.test.template.tsx +144 -0
- package/src/__tests__/templates/hook.test.template.ts +173 -0
- package/src/__tests__/templates/integration.test.template.tsx +199 -0
- package/src/__tests__/types/test.types.ts +106 -0
- package/src/components/Alert/Alert.test.tsx +496 -0
- package/src/components/Alert/Alert.tsx +134 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Avatar/Avatar.test.tsx +484 -0
- package/src/components/Avatar/Avatar.tsx +84 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Button/Button.test.tsx +662 -0
- package/src/components/Button/Button.tsx +270 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Card/Card.test.tsx +593 -0
- package/src/components/Card/Card.tsx +271 -0
- package/src/components/Card/index.ts +1 -0
- package/src/components/Checkbox/Checkbox.test.tsx +461 -0
- package/src/components/Checkbox/Checkbox.tsx +75 -0
- package/src/components/Checkbox/__mocks__/Checkbox.tsx +2 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/DataTable/DataTable.tsx +446 -0
- package/src/components/DataTable/__tests__/README.md +145 -0
- package/src/components/DataTable/__tests__/mocks/MockRBACProvider.tsx +66 -0
- package/src/components/DataTable/__tests__/test-utils/dataFactories.ts +103 -0
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +381 -0
- package/src/components/DataTable/__tests__/test-utils.ts +94 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +168 -0
- package/src/components/DataTable/components/ActionButtons.tsx +194 -0
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +160 -0
- package/src/components/DataTable/components/ColumnFilter.tsx +114 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +100 -0
- package/src/components/DataTable/components/DataTableBody.tsx +461 -0
- package/src/components/DataTable/components/DataTableCore.tsx +1027 -0
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +214 -0
- package/src/components/DataTable/components/DataTableModals.tsx +87 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +262 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
- package/src/components/DataTable/components/EditableRow.tsx +159 -0
- package/src/components/DataTable/components/EmptyState.tsx +64 -0
- package/src/components/DataTable/components/ExpandButton.tsx +113 -0
- package/src/components/DataTable/components/FilterRow.tsx +100 -0
- package/src/components/DataTable/components/GroupHeader.tsx +42 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +96 -0
- package/src/components/DataTable/components/ImportModal.tsx +345 -0
- package/src/components/DataTable/components/LoadingState.tsx +12 -0
- package/src/components/DataTable/components/PaginationControls.tsx +332 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +742 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +68 -0
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
- package/src/components/DataTable/components/index.ts +16 -0
- package/src/components/DataTable/context/DataTableContext.tsx +97 -0
- package/src/components/DataTable/core/ActionManager.ts +235 -0
- package/src/components/DataTable/core/ColumnFactory.ts +268 -0
- package/src/components/DataTable/core/ColumnManager.ts +205 -0
- package/src/components/DataTable/core/DataManager.ts +188 -0
- package/src/components/DataTable/core/DataTableContext.tsx +181 -0
- package/src/components/DataTable/core/LocalDataAdapter.ts +264 -0
- package/src/components/DataTable/core/PluginRegistry.ts +229 -0
- package/src/components/DataTable/core/StateManager.ts +311 -0
- package/src/components/DataTable/core/index.ts +8 -0
- package/src/components/DataTable/core/interfaces.ts +338 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +419 -0
- package/src/components/DataTable/examples/HierarchicalExample.tsx +475 -0
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +176 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +505 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +95 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +110 -0
- package/src/components/DataTable/hooks/useDataTableState.ts +325 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +174 -0
- package/src/components/DataTable/index.ts +68 -0
- package/src/components/DataTable/styles.ts +171 -0
- package/src/components/DataTable/types.ts +511 -0
- package/src/components/DataTable/utils/debugTools.ts +583 -0
- package/src/components/DataTable/utils/errorHandling.ts +494 -0
- package/src/components/DataTable/utils/exportUtils.ts +126 -0
- package/src/components/DataTable/utils/flexibleImport.ts +510 -0
- package/src/components/DataTable/utils/hierarchicalSorting.ts +151 -0
- package/src/components/DataTable/utils/hierarchicalUtils.ts +218 -0
- package/src/components/DataTable/utils/index.ts +1 -0
- package/src/components/DataTable/utils/performanceUtils.ts +351 -0
- package/src/components/Dialog/Dialog.test.tsx +1139 -0
- package/src/components/Dialog/Dialog.tsx +782 -0
- package/src/components/Dialog/README.md +804 -0
- package/src/components/Dialog/examples/BasicHtmlTest.tsx +55 -0
- package/src/components/Dialog/examples/DebugHtmlExample.tsx +68 -0
- package/src/components/Dialog/examples/HtmlDialogExample.tsx +202 -0
- package/src/components/Dialog/examples/SimpleHtmlTest.tsx +61 -0
- package/src/components/Dialog/examples/SmartDialogExample.tsx +322 -0
- package/src/components/Dialog/index.ts +12 -0
- package/src/components/Dialog/utils/safeHtml.ts +185 -0
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +752 -0
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +312 -0
- package/src/components/ErrorBoundary/index.ts +8 -0
- package/src/components/EventSelector/EventSelector.tsx +360 -0
- package/src/components/EventSelector/index.ts +3 -0
- package/src/components/EventSelector/types.ts +79 -0
- package/src/components/FileUpload/FileUpload.example.tsx +218 -0
- package/src/components/FileUpload/FileUpload.test.tsx +665 -0
- package/src/components/FileUpload/FileUpload.tsx +237 -0
- package/src/components/FileUpload/index.ts +6 -0
- package/src/components/Footer/Footer.tsx +197 -0
- package/src/components/Footer/index.ts +17 -0
- package/src/components/Form/Form.tsx +166 -0
- package/src/components/Form/FormErrorSummary.tsx +113 -0
- package/src/components/Form/FormField.tsx +249 -0
- package/src/components/Form/FormFieldset.tsx +127 -0
- package/src/components/Form/FormLiveRegion.tsx +198 -0
- package/src/components/Form/index.ts +26 -0
- package/src/components/Header/Header.tsx +301 -0
- package/src/components/Header/index.ts +4 -0
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +164 -0
- package/src/components/InactivityWarningModal/index.ts +9 -0
- package/src/components/Input/Input.tsx +201 -0
- package/src/components/Input/__mocks__/Input.tsx +2 -0
- package/src/components/Input/index.ts +9 -0
- package/src/components/Label/Label.tsx +186 -0
- package/src/components/Label/index.ts +2 -0
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +98 -0
- package/src/components/LoadingSpinner/index.ts +3 -0
- package/src/components/LoginForm/LoginForm.tsx +273 -0
- package/src/components/LoginForm/index.ts +3 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +698 -0
- package/src/components/NavigationMenu/index.ts +10 -0
- package/src/components/NavigationMenu/types.ts +85 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +304 -0
- package/src/components/OrganisationSelector/index.ts +9 -0
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +699 -0
- package/src/components/PaceAppLayout/README.md +278 -0
- package/src/components/PaceAppLayout/index.ts +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +221 -0
- package/src/components/PaceLoginPage/index.ts +1 -0
- package/src/components/PasswordReset/PasswordChangeForm.tsx +186 -0
- package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
- package/src/components/PasswordReset/index.ts +4 -0
- package/src/components/PrintButton/PrintButton.tsx +321 -0
- package/src/components/PrintButton/PrintButtonGroup.tsx +84 -0
- package/src/components/PrintButton/PrintToolbar.tsx +94 -0
- package/src/components/PrintButton/examples/PrintButtonShowcase.tsx +438 -0
- package/src/components/PrintButton/index.ts +33 -0
- package/src/components/PrintButton/types.ts +173 -0
- package/src/components/PrintCard/PrintCard.tsx +154 -0
- package/src/components/PrintCard/PrintCardContent.tsx +57 -0
- package/src/components/PrintCard/PrintCardFooter.tsx +60 -0
- package/src/components/PrintCard/PrintCardGrid.tsx +91 -0
- package/src/components/PrintCard/PrintCardHeader.tsx +78 -0
- package/src/components/PrintCard/PrintCardImage.tsx +81 -0
- package/src/components/PrintCard/examples/PrintCardShowcase.tsx +239 -0
- package/src/components/PrintCard/index.ts +34 -0
- package/src/components/PrintCard/types.ts +171 -0
- package/src/components/PrintDataTable/PrintDataTable.tsx +215 -0
- package/src/components/PrintDataTable/PrintTableGroup.tsx +90 -0
- package/src/components/PrintDataTable/PrintTableRow.tsx +76 -0
- package/src/components/PrintDataTable/index.ts +25 -0
- package/src/components/PrintDataTable/types.ts +67 -0
- package/src/components/PrintFooter/PrintFooter.tsx +183 -0
- package/src/components/PrintFooter/PrintFooterContent.tsx +71 -0
- package/src/components/PrintFooter/PrintFooterInfo.tsx +86 -0
- package/src/components/PrintFooter/PrintPageNumber.tsx +90 -0
- package/src/components/PrintFooter/examples/PrintFooterShowcase.tsx +390 -0
- package/src/components/PrintFooter/index.ts +30 -0
- package/src/components/PrintFooter/types.ts +149 -0
- package/src/components/PrintGrid/PrintGrid.tsx +180 -0
- package/src/components/PrintGrid/PrintGridBreakpoint.tsx +109 -0
- package/src/components/PrintGrid/PrintGridContainer.tsx +128 -0
- package/src/components/PrintGrid/PrintGridItem.tsx +220 -0
- package/src/components/PrintGrid/examples/PrintGridShowcase.tsx +359 -0
- package/src/components/PrintGrid/index.ts +31 -0
- package/src/components/PrintGrid/types.ts +159 -0
- package/src/components/PrintHeader/PrintCoverHeader.tsx +230 -0
- package/src/components/PrintHeader/PrintHeader.tsx +150 -0
- package/src/components/PrintHeader/index.ts +17 -0
- package/src/components/PrintHeader/types.ts +42 -0
- package/src/components/PrintLayout/PrintLayout.tsx +122 -0
- package/src/components/PrintLayout/PrintLayoutContext.tsx +66 -0
- package/src/components/PrintLayout/PrintPageBreak.tsx +52 -0
- package/src/components/PrintLayout/examples/PrintShowcase.tsx +230 -0
- package/src/components/PrintLayout/index.ts +19 -0
- package/src/components/PrintLayout/types.ts +37 -0
- package/src/components/PrintPageBreak/PrintPageBreak.tsx +120 -0
- package/src/components/PrintPageBreak/PrintPageBreakGroup.tsx +90 -0
- package/src/components/PrintPageBreak/PrintPageBreakIndicator.tsx +112 -0
- package/src/components/PrintPageBreak/examples/PrintPageBreakShowcase.tsx +279 -0
- package/src/components/PrintPageBreak/index.ts +23 -0
- package/src/components/PrintPageBreak/types.ts +94 -0
- package/src/components/PrintSection/PrintColumn.tsx +104 -0
- package/src/components/PrintSection/PrintDivider.tsx +101 -0
- package/src/components/PrintSection/PrintSection.tsx +129 -0
- package/src/components/PrintSection/PrintSectionContent.tsx +75 -0
- package/src/components/PrintSection/PrintSectionHeader.tsx +97 -0
- package/src/components/PrintSection/examples/PrintSectionShowcase.tsx +258 -0
- package/src/components/PrintSection/index.ts +33 -0
- package/src/components/PrintSection/types.ts +155 -0
- package/src/components/PrintText/PrintText.tsx +116 -0
- package/src/components/PrintText/index.ts +16 -0
- package/src/components/PrintText/types.ts +24 -0
- package/src/components/Progress/Progress.tsx +116 -0
- package/src/components/Progress/index.ts +3 -0
- package/src/components/PublicLayout/EventLogo.tsx +287 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +279 -0
- package/src/components/PublicLayout/PublicLoadingSpinner.tsx +208 -0
- package/src/components/PublicLayout/PublicPageContextChecker.tsx +130 -0
- package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
- package/src/components/PublicLayout/PublicPageFooter.tsx +124 -0
- package/src/components/PublicLayout/PublicPageHeader.tsx +178 -0
- package/src/components/PublicLayout/PublicPageLayout.tsx +232 -0
- package/src/components/PublicLayout/PublicPageProvider.tsx +137 -0
- package/src/components/PublicLayout/index.ts +51 -0
- package/src/components/RBAC/PagePermissionGuard.tsx +287 -0
- package/src/components/RBAC/RBACGuard.tsx +143 -0
- package/src/components/RBAC/RBACProvider.tsx +186 -0
- package/src/components/RBAC/RoleBasedContent.tsx +129 -0
- package/src/components/RBAC/index.ts +23 -0
- package/src/components/Select/Select.tsx +660 -0
- package/src/components/Select/index.ts +1 -0
- package/src/components/SuperAdminGuard.tsx +116 -0
- package/src/components/Table/Table.tsx +222 -0
- package/src/components/Table/index.ts +11 -0
- package/src/components/Toast/Toast.tsx +339 -0
- package/src/components/Toast/index.ts +14 -0
- package/src/components/Tooltip/Tooltip.tsx +167 -0
- package/src/components/Tooltip/index.ts +7 -0
- package/src/components/UserMenu/UserMenu.tsx +243 -0
- package/src/components/UserMenu/index.ts +3 -0
- package/src/components/examples/PermissionExample.tsx +150 -0
- package/src/components/index.ts +434 -0
- package/src/components.ts +19 -0
- package/src/constants/performance.ts +14 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/index.ts +56 -0
- package/src/hooks/public/index.ts +34 -0
- package/src/hooks/public/usePublicEvent.ts +261 -0
- package/src/hooks/public/usePublicEventLogo.ts +285 -0
- package/src/hooks/public/usePublicRouteParams.ts +259 -0
- package/src/hooks/useAppConfig.ts +94 -0
- package/src/hooks/useComponentPerformance.ts +39 -0
- package/src/hooks/useCounter.test.ts +135 -0
- package/src/hooks/useDataTablePerformance.ts +387 -0
- package/src/hooks/useDataTableState.ts +110 -0
- package/src/hooks/useDebounce.ts +18 -0
- package/src/hooks/useFocusManagement.ts +161 -0
- package/src/hooks/useFocusTrap.ts +155 -0
- package/src/hooks/useInactivityTracker.ts +372 -0
- package/src/hooks/useIsMobile.ts +42 -0
- package/src/hooks/useKeyboardShortcuts.ts +237 -0
- package/src/hooks/useOrganisationPermissions.ts +208 -0
- package/src/hooks/useOrganisationSecurity.ts +262 -0
- package/src/hooks/usePerformanceMonitor.ts +128 -0
- package/src/hooks/usePermissionCache.ts +455 -0
- package/src/hooks/useRBAC.ts +262 -0
- package/src/hooks/useSecureDataAccess.ts +586 -0
- package/src/hooks/useStorage.ts +274 -0
- package/src/hooks/useToast.ts +242 -0
- package/src/hooks/useZodForm.ts +28 -0
- package/src/index.ts +200 -0
- package/src/providers/AuthProvider.tsx +369 -0
- package/src/providers/EventProvider.tsx +324 -0
- package/src/providers/InactivityProvider.tsx +238 -0
- package/src/providers/OrganisationProvider.tsx +588 -0
- package/src/providers/RBACProvider.tsx +634 -0
- package/src/providers/UnifiedAuthProvider.tsx +327 -0
- package/src/providers/index.ts +15 -0
- package/src/rbac/README.md +885 -0
- package/src/rbac/adapters.tsx +726 -0
- package/src/rbac/api.ts +339 -0
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/rbac/audit.ts +338 -0
- package/src/rbac/cache.ts +215 -0
- package/src/rbac/components/EnhancedNavigationMenu.tsx +294 -0
- package/src/rbac/components/NavigationGuard.tsx +294 -0
- package/src/rbac/components/NavigationProvider.tsx +314 -0
- package/src/rbac/components/PagePermissionGuard.tsx +430 -0
- package/src/rbac/components/PagePermissionProvider.tsx +274 -0
- package/src/rbac/components/PermissionEnforcer.tsx +307 -0
- package/src/rbac/components/RoleBasedRouter.tsx +425 -0
- package/src/rbac/components/SecureDataProvider.tsx +319 -0
- package/src/rbac/components/index.ts +64 -0
- package/src/rbac/config.ts +133 -0
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/engine.ts +1026 -0
- package/src/rbac/eslint-rules.js +285 -0
- package/src/rbac/examples/CompleteRBACExample.tsx +323 -0
- package/src/rbac/examples/EventBasedApp.tsx +238 -0
- package/src/rbac/hooks.ts +570 -0
- package/src/rbac/index.ts +114 -0
- package/src/rbac/permissions.ts +293 -0
- package/src/rbac/secureClient.ts +244 -0
- package/src/rbac/security.ts +346 -0
- package/src/rbac/testing/index.tsx +340 -0
- package/src/rbac/types.ts +343 -0
- package/src/rbac/utils/eventContext.ts +83 -0
- package/src/styles/core.css +422 -0
- package/src/styles/index.ts +51 -0
- package/src/theming/runtime.ts +187 -0
- package/src/types/database.ts +472 -0
- package/src/types/guards.ts +30 -0
- package/src/types/index.ts +25 -0
- package/src/types/organisation.ts +184 -0
- package/src/types/security.ts +70 -0
- package/src/types/supabase.ts +166 -0
- package/src/types/theme.ts +6 -0
- package/src/types/unified.ts +262 -0
- package/src/types/validation.ts +164 -0
- package/src/types/vitest-globals.d.ts +43 -0
- package/src/utils/__mocks__/supabaseMock.ts +75 -0
- package/src/utils/__mocks__/supabaseMock.tsx +198 -0
- package/src/utils/appConfig.ts +47 -0
- package/src/utils/appIdResolver.ts +130 -0
- package/src/utils/appNameResolver.ts +190 -0
- package/src/utils/audit.ts +127 -0
- package/src/utils/auth-utils.ts +96 -0
- package/src/utils/bundleAnalysis.ts +129 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/debugLogger.ts +46 -0
- package/src/utils/deviceFingerprint.ts +215 -0
- package/src/utils/dynamicUtils.ts +105 -0
- package/src/utils/formatting.ts +77 -0
- package/src/utils/index.ts +145 -0
- package/src/utils/lazyLoad.tsx +44 -0
- package/src/utils/organisationContext.ts +135 -0
- package/src/utils/performanceBenchmark.ts +64 -0
- package/src/utils/performanceBudgets.ts +111 -0
- package/src/utils/permissionTypes.ts +37 -0
- package/src/utils/permissionUtils.ts +31 -0
- package/src/utils/print/PrintDataProcessor.ts +390 -0
- package/src/utils/print/examples/PrintUtilitiesShowcase.tsx +397 -0
- package/src/utils/print/index.ts +29 -0
- package/src/utils/print/types.ts +196 -0
- package/src/utils/print/usePrintOptimization.ts +272 -0
- package/src/utils/sanitization.ts +264 -0
- package/src/utils/schemaUtils.ts +37 -0
- package/src/utils/secureDataAccess.ts +361 -0
- package/src/utils/secureErrors.ts +79 -0
- package/src/utils/secureStorage.ts +244 -0
- package/src/utils/security.ts +156 -0
- package/src/utils/securityMonitor.ts +45 -0
- package/src/utils/sessionTracking.ts +170 -0
- package/src/utils/storage/README.md +348 -0
- package/src/utils/storage/config.ts +100 -0
- package/src/utils/storage/helpers.ts +359 -0
- package/src/utils/storage/index.ts +36 -0
- package/src/utils/storage/types.ts +90 -0
- package/src/utils/validation.ts +111 -0
- package/src/utils/validationUtils.ts +120 -0
- package/src/validation/common.ts +53 -0
- package/src/validation/csrf.ts +214 -0
- package/src/validation/index.ts +43 -0
- package/src/validation/passwordSchema.ts +125 -0
- package/src/validation/sanitization.ts +96 -0
- package/src/validation/schemaUtils.ts +42 -0
- package/src/validation/sqlInjectionProtection.ts +242 -0
- package/src/validation/user.ts +34 -0
- package/dist/UnifiedAuthProvider-V7y63NjT.d.ts +0 -88
- package/dist/chunk-4MCJAK7J.js.map +0 -1
- package/dist/chunk-4ZTIEYU2.js.map +0 -1
- package/dist/chunk-H4PZ4B3Y.js.map +0 -1
- package/dist/chunk-IOX76PSM.js.map +0 -1
- package/dist/chunk-JUUNUW3O.js.map +0 -1
- package/dist/chunk-U7DY5T33.js +0 -11
- package/dist/chunk-U7DY5T33.js.map +0 -1
- package/dist/chunk-WHLSWC6W.js.map +0 -1
- package/dist/chunk-XI7QFSSC.js +0 -790
- package/dist/chunk-XI7QFSSC.js.map +0 -1
- package/dist/chunk-XIJMMBDD.js +0 -73
- package/dist/chunk-XIJMMBDD.js.map +0 -1
- package/dist/chunk-YNU5QJ4S.js.map +0 -1
- package/dist/chunk-YWYCNGWH.js +0 -2070
- package/dist/chunk-YWYCNGWH.js.map +0 -1
- package/dist/chunk-ZJ3UKPIW.js +0 -952
- package/dist/chunk-ZJ3UKPIW.js.map +0 -1
- package/dist/useAppConfig-CZNJJsT_.d.ts +0 -148
- package/dist/{DataTable-2LB6HI6V.js.map → DataTable-GX3XERFJ.js.map} +0 -0
- package/dist/{api-AIJ3IJX3.js.map → api-ETQ6YJ3C.js.map} +0 -0
- package/dist/{audit-PD5L5ZSC.js.map → appNameResolver-7GHF5ED2.js.map} +0 -0
- package/dist/{chunk-DC5AMYBS.js.map → audit-BUW3LMJB.js.map} +0 -0
- package/dist/{validation-D2-NNCCE.d.ts → validation-PM_iOaTI.d.ts} +6 -6
|
@@ -0,0 +1,3295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
AlertDescription,
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardFooter,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
Input,
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectLabel,
|
|
15
|
+
SelectSeparator,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue
|
|
18
|
+
} from "./chunk-GSNM5D6H.js";
|
|
19
|
+
import {
|
|
20
|
+
isPermitted
|
|
21
|
+
} from "./chunk-C5G2A4PO.js";
|
|
22
|
+
import {
|
|
23
|
+
EventProvider_exports,
|
|
24
|
+
init_EventProvider,
|
|
25
|
+
useEvents
|
|
26
|
+
} from "./chunk-TGDCLPP2.js";
|
|
27
|
+
import {
|
|
28
|
+
PublicErrorBoundary,
|
|
29
|
+
useAppConfig,
|
|
30
|
+
usePublicEventLogo,
|
|
31
|
+
usePublicPageContext
|
|
32
|
+
} from "./chunk-5EL3KHOQ.js";
|
|
33
|
+
import {
|
|
34
|
+
OrganisationProvider_exports,
|
|
35
|
+
init_OrganisationProvider,
|
|
36
|
+
useOrganisations
|
|
37
|
+
} from "./chunk-COBPIXXQ.js";
|
|
38
|
+
import {
|
|
39
|
+
Button,
|
|
40
|
+
Dialog,
|
|
41
|
+
DialogContent,
|
|
42
|
+
DialogHeader,
|
|
43
|
+
DialogOverlay,
|
|
44
|
+
DialogTitle,
|
|
45
|
+
DialogTrigger,
|
|
46
|
+
UnifiedAuthProvider_exports,
|
|
47
|
+
init_Button,
|
|
48
|
+
init_UnifiedAuthProvider,
|
|
49
|
+
useUnifiedAuth
|
|
50
|
+
} from "./chunk-6CR3MRZN.js";
|
|
51
|
+
import {
|
|
52
|
+
LoadingSpinner
|
|
53
|
+
} from "./chunk-CDQ3PX7L.js";
|
|
54
|
+
import {
|
|
55
|
+
performanceBudgetMonitor
|
|
56
|
+
} from "./chunk-74C6SNEC.js";
|
|
57
|
+
import {
|
|
58
|
+
cn,
|
|
59
|
+
init_cn
|
|
60
|
+
} from "./chunk-OYRY44Q2.js";
|
|
61
|
+
import {
|
|
62
|
+
__require,
|
|
63
|
+
__toCommonJS
|
|
64
|
+
} from "./chunk-PLDDJCW6.js";
|
|
65
|
+
|
|
66
|
+
// src/components/Label/Label.tsx
|
|
67
|
+
init_cn();
|
|
68
|
+
import * as React from "react";
|
|
69
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
70
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
71
|
+
var getLabelClasses = () => {
|
|
72
|
+
return "font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70";
|
|
73
|
+
};
|
|
74
|
+
var Label = React.forwardRef(({
|
|
75
|
+
className,
|
|
76
|
+
required = false,
|
|
77
|
+
requiredIndicator,
|
|
78
|
+
hideRequiredIndicator = false,
|
|
79
|
+
helperText,
|
|
80
|
+
helperTextClassName,
|
|
81
|
+
error,
|
|
82
|
+
errorClassName,
|
|
83
|
+
children,
|
|
84
|
+
htmlFor,
|
|
85
|
+
...props
|
|
86
|
+
}, ref) => {
|
|
87
|
+
const hasError = !!error;
|
|
88
|
+
const showHelperText = helperText && !hasError;
|
|
89
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
90
|
+
/* @__PURE__ */ jsxs(
|
|
91
|
+
LabelPrimitive.Root,
|
|
92
|
+
{
|
|
93
|
+
ref,
|
|
94
|
+
className: cn(
|
|
95
|
+
getLabelClasses(),
|
|
96
|
+
hasError && "text-destructive",
|
|
97
|
+
className
|
|
98
|
+
),
|
|
99
|
+
htmlFor,
|
|
100
|
+
...props,
|
|
101
|
+
children: [
|
|
102
|
+
children,
|
|
103
|
+
required && /* @__PURE__ */ jsx(
|
|
104
|
+
"label",
|
|
105
|
+
{
|
|
106
|
+
"aria-label": "required",
|
|
107
|
+
className: cn(
|
|
108
|
+
"text-destructive ml-1",
|
|
109
|
+
hideRequiredIndicator && "sr-only"
|
|
110
|
+
),
|
|
111
|
+
children: requiredIndicator || "*"
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
showHelperText && /* @__PURE__ */ jsx("p", { className: cn("text-muted-foreground", helperTextClassName), children: helperText }),
|
|
118
|
+
hasError && /* @__PURE__ */ jsx(
|
|
119
|
+
"p",
|
|
120
|
+
{
|
|
121
|
+
role: "alert",
|
|
122
|
+
"aria-live": "polite",
|
|
123
|
+
className: cn("text-destructive", errorClassName),
|
|
124
|
+
children: error
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
] });
|
|
128
|
+
});
|
|
129
|
+
Label.displayName = LabelPrimitive.Root.displayName;
|
|
130
|
+
|
|
131
|
+
// src/components/Avatar/Avatar.tsx
|
|
132
|
+
init_cn();
|
|
133
|
+
import * as React2 from "react";
|
|
134
|
+
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
135
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
136
|
+
var Avatar = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
|
|
137
|
+
AvatarPrimitive.Root,
|
|
138
|
+
{
|
|
139
|
+
ref,
|
|
140
|
+
className: cn(
|
|
141
|
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
|
142
|
+
className
|
|
143
|
+
),
|
|
144
|
+
...props
|
|
145
|
+
}
|
|
146
|
+
));
|
|
147
|
+
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
|
148
|
+
var AvatarImage = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
|
|
149
|
+
AvatarPrimitive.Image,
|
|
150
|
+
{
|
|
151
|
+
ref,
|
|
152
|
+
className: cn("object-cover", className),
|
|
153
|
+
...props
|
|
154
|
+
}
|
|
155
|
+
));
|
|
156
|
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
|
157
|
+
var AvatarFallback = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx2(
|
|
158
|
+
AvatarPrimitive.Fallback,
|
|
159
|
+
{
|
|
160
|
+
ref,
|
|
161
|
+
className: cn(
|
|
162
|
+
"flex h-full w-full items-center justify-center rounded-full text-sec-50 bg-sec-500",
|
|
163
|
+
className
|
|
164
|
+
),
|
|
165
|
+
...props
|
|
166
|
+
}
|
|
167
|
+
));
|
|
168
|
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
|
169
|
+
|
|
170
|
+
// src/components/Progress/Progress.tsx
|
|
171
|
+
init_cn();
|
|
172
|
+
import * as React3 from "react";
|
|
173
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
174
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
175
|
+
var Progress = React3.forwardRef(({ className, value, max = 100, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
176
|
+
ProgressPrimitive.Root,
|
|
177
|
+
{
|
|
178
|
+
ref,
|
|
179
|
+
className: cn(
|
|
180
|
+
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
|
181
|
+
className
|
|
182
|
+
),
|
|
183
|
+
...props,
|
|
184
|
+
value,
|
|
185
|
+
max,
|
|
186
|
+
"aria-valuenow": value,
|
|
187
|
+
"aria-valuemax": max,
|
|
188
|
+
"aria-valuemin": 0,
|
|
189
|
+
children: /* @__PURE__ */ jsx3(
|
|
190
|
+
ProgressPrimitive.Indicator,
|
|
191
|
+
{
|
|
192
|
+
className: "h-full w-full flex-1 bg-primary transition-all",
|
|
193
|
+
style: { transform: `translateX(-${100 - (value || 0)}%)` }
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
));
|
|
198
|
+
Progress.displayName = ProgressPrimitive.Root.displayName;
|
|
199
|
+
|
|
200
|
+
// src/components/Toast/Toast.tsx
|
|
201
|
+
init_cn();
|
|
202
|
+
import * as React4 from "react";
|
|
203
|
+
import * as ToastPrimitives from "@radix-ui/react-toast";
|
|
204
|
+
import { X } from "lucide-react";
|
|
205
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
206
|
+
var ToastProvider = ToastPrimitives.Provider;
|
|
207
|
+
var ToastViewport = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
208
|
+
ToastPrimitives.Viewport,
|
|
209
|
+
{
|
|
210
|
+
ref,
|
|
211
|
+
"data-testid": "toast-viewport",
|
|
212
|
+
className: cn(
|
|
213
|
+
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
|
214
|
+
className
|
|
215
|
+
),
|
|
216
|
+
...props
|
|
217
|
+
}
|
|
218
|
+
));
|
|
219
|
+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
|
220
|
+
var Toast = React4.forwardRef(({ className, ...props }, ref) => {
|
|
221
|
+
return /* @__PURE__ */ jsx4(
|
|
222
|
+
ToastPrimitives.Root,
|
|
223
|
+
{
|
|
224
|
+
ref,
|
|
225
|
+
"data-testid": "toast-root",
|
|
226
|
+
className: cn(
|
|
227
|
+
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
|
228
|
+
className
|
|
229
|
+
),
|
|
230
|
+
...props
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
Toast.displayName = ToastPrimitives.Root.displayName;
|
|
235
|
+
var ToastAction = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
236
|
+
ToastPrimitives.Action,
|
|
237
|
+
{
|
|
238
|
+
ref,
|
|
239
|
+
"data-testid": "toast-action",
|
|
240
|
+
className: cn(
|
|
241
|
+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
|
242
|
+
className
|
|
243
|
+
),
|
|
244
|
+
...props
|
|
245
|
+
}
|
|
246
|
+
));
|
|
247
|
+
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
|
248
|
+
var ToastClose = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
249
|
+
ToastPrimitives.Close,
|
|
250
|
+
{
|
|
251
|
+
ref,
|
|
252
|
+
"data-testid": "toast-close",
|
|
253
|
+
className: cn(
|
|
254
|
+
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-acc-300 group-[.destructive]:hover:text-acc-50 group-[.destructive]:focus:ring-acc-400 group-[.destructive]:focus:ring-offset-acc-600",
|
|
255
|
+
className
|
|
256
|
+
),
|
|
257
|
+
"toast-close": "",
|
|
258
|
+
...props,
|
|
259
|
+
children: /* @__PURE__ */ jsx4(X, { className: "h-4 w-4" })
|
|
260
|
+
}
|
|
261
|
+
));
|
|
262
|
+
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
|
263
|
+
var ToastTitle = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
264
|
+
ToastPrimitives.Title,
|
|
265
|
+
{
|
|
266
|
+
ref,
|
|
267
|
+
"data-testid": "toast-title",
|
|
268
|
+
className: cn("text-sm font-semibold", className),
|
|
269
|
+
...props
|
|
270
|
+
}
|
|
271
|
+
));
|
|
272
|
+
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
|
273
|
+
var ToastDescription = React4.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx4(
|
|
274
|
+
ToastPrimitives.Description,
|
|
275
|
+
{
|
|
276
|
+
ref,
|
|
277
|
+
"data-testid": "toast-description",
|
|
278
|
+
className: cn("text-sm opacity-90", className),
|
|
279
|
+
...props
|
|
280
|
+
}
|
|
281
|
+
));
|
|
282
|
+
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
|
283
|
+
function useToast() {
|
|
284
|
+
return {
|
|
285
|
+
toast: (props) => {
|
|
286
|
+
console.log("Toast:", props);
|
|
287
|
+
},
|
|
288
|
+
dismiss: () => {
|
|
289
|
+
console.log("Toast dismissed");
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function Toaster() {
|
|
294
|
+
return /* @__PURE__ */ jsx4(ToastProvider, { "data-testid": "toast-provider", children: /* @__PURE__ */ jsx4(ToastViewport, {}) });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/components/Form/Form.tsx
|
|
298
|
+
init_cn();
|
|
299
|
+
import { useForm, FormProvider } from "react-hook-form";
|
|
300
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
301
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
302
|
+
function Form({
|
|
303
|
+
schema,
|
|
304
|
+
defaultValues,
|
|
305
|
+
onSubmit,
|
|
306
|
+
onError,
|
|
307
|
+
mode = "onSubmit",
|
|
308
|
+
children,
|
|
309
|
+
className
|
|
310
|
+
}) {
|
|
311
|
+
const methods = useForm({
|
|
312
|
+
resolver: schema ? zodResolver(schema) : void 0,
|
|
313
|
+
defaultValues,
|
|
314
|
+
mode
|
|
315
|
+
});
|
|
316
|
+
const handleSubmit = methods.handleSubmit(onSubmit, onError);
|
|
317
|
+
return /* @__PURE__ */ jsx5(FormProvider, { ...methods, children: /* @__PURE__ */ jsx5("form", { onSubmit: handleSubmit, className: cn("space-y-4", className), children: typeof children === "function" ? children(methods) : children }) });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/components/LoginForm/LoginForm.tsx
|
|
321
|
+
init_Button();
|
|
322
|
+
import React5, { useState, useCallback, useMemo } from "react";
|
|
323
|
+
init_cn();
|
|
324
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
325
|
+
var LoginForm = React5.memo(({
|
|
326
|
+
onSignIn,
|
|
327
|
+
onSuccess,
|
|
328
|
+
onError,
|
|
329
|
+
isLoading = false,
|
|
330
|
+
appName,
|
|
331
|
+
title,
|
|
332
|
+
subtitle,
|
|
333
|
+
showSignUp = false,
|
|
334
|
+
onSignUp,
|
|
335
|
+
className
|
|
336
|
+
}) => {
|
|
337
|
+
const [formData, setFormData] = useState({ email: "", password: "" });
|
|
338
|
+
const [error, setError] = useState(null);
|
|
339
|
+
const isFormValid = useMemo(() => {
|
|
340
|
+
return formData.email.length > 0 && formData.password.length > 0;
|
|
341
|
+
}, [formData.email, formData.password]);
|
|
342
|
+
const handleEmailChange = useCallback((e) => {
|
|
343
|
+
setFormData((prev) => ({ ...prev, email: e.target.value }));
|
|
344
|
+
}, []);
|
|
345
|
+
const handlePasswordChange = useCallback((e) => {
|
|
346
|
+
setFormData((prev) => ({ ...prev, password: e.target.value }));
|
|
347
|
+
}, []);
|
|
348
|
+
const handleSubmit = useCallback(async (e) => {
|
|
349
|
+
e.preventDefault();
|
|
350
|
+
setError(null);
|
|
351
|
+
if (!isFormValid || isLoading) return;
|
|
352
|
+
try {
|
|
353
|
+
await onSignIn(formData);
|
|
354
|
+
onSuccess?.();
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const errorMessage = err instanceof Error ? err.message : "Sign in failed";
|
|
357
|
+
setError(errorMessage);
|
|
358
|
+
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
359
|
+
}
|
|
360
|
+
}, [formData, isFormValid, isLoading, onSignIn, onSuccess, onError]);
|
|
361
|
+
const handleSignUpClick = useCallback(() => {
|
|
362
|
+
onSignUp?.();
|
|
363
|
+
}, [onSignUp]);
|
|
364
|
+
const displayTitle = useMemo(() => title || (appName ? `Sign in to ${appName}` : "Sign In"), [title, appName]);
|
|
365
|
+
const displaySubtitle = useMemo(() => subtitle || "Enter your credentials to continue.", [subtitle]);
|
|
366
|
+
return /* @__PURE__ */ jsx6(Card, { className: cn("w-full max-w-md mx-auto", className), children: /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, "data-testid": "login-form", children: [
|
|
367
|
+
/* @__PURE__ */ jsxs2(CardHeader, { className: "space-y-1", children: [
|
|
368
|
+
/* @__PURE__ */ jsx6(CardTitle, { className: "text-2xl text-center", children: displayTitle }),
|
|
369
|
+
/* @__PURE__ */ jsx6(CardDescription, { className: "text-center", children: displaySubtitle })
|
|
370
|
+
] }),
|
|
371
|
+
/* @__PURE__ */ jsxs2(CardContent, { className: "space-y-4", children: [
|
|
372
|
+
error && /* @__PURE__ */ jsx6(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx6(AlertDescription, { children: error }) }),
|
|
373
|
+
/* @__PURE__ */ jsx6(Label, { htmlFor: "email", children: "Email" }),
|
|
374
|
+
/* @__PURE__ */ jsx6(
|
|
375
|
+
Input,
|
|
376
|
+
{
|
|
377
|
+
id: "email",
|
|
378
|
+
type: "email",
|
|
379
|
+
placeholder: "Enter your email",
|
|
380
|
+
value: formData.email,
|
|
381
|
+
onChange: handleEmailChange,
|
|
382
|
+
required: true,
|
|
383
|
+
disabled: isLoading
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
/* @__PURE__ */ jsx6(Label, { htmlFor: "password", children: "Password" }),
|
|
387
|
+
/* @__PURE__ */ jsx6(
|
|
388
|
+
Input,
|
|
389
|
+
{
|
|
390
|
+
id: "password",
|
|
391
|
+
type: "password",
|
|
392
|
+
placeholder: "Enter your password",
|
|
393
|
+
value: formData.password,
|
|
394
|
+
onChange: handlePasswordChange,
|
|
395
|
+
required: true,
|
|
396
|
+
disabled: isLoading
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
] }),
|
|
400
|
+
/* @__PURE__ */ jsxs2(CardFooter, { className: "flex flex-col space-y-4", children: [
|
|
401
|
+
/* @__PURE__ */ jsx6(
|
|
402
|
+
Button,
|
|
403
|
+
{
|
|
404
|
+
type: "submit",
|
|
405
|
+
className: "w-full",
|
|
406
|
+
disabled: isLoading || !isFormValid,
|
|
407
|
+
children: isLoading ? "Signing in..." : "Sign In"
|
|
408
|
+
}
|
|
409
|
+
),
|
|
410
|
+
showSignUp && (onSignUp ? /* @__PURE__ */ jsx6("div", { className: "text-sm text-center text-muted-foreground", children: /* @__PURE__ */ jsx6(
|
|
411
|
+
"button",
|
|
412
|
+
{
|
|
413
|
+
type: "button",
|
|
414
|
+
onClick: handleSignUpClick,
|
|
415
|
+
className: "text-primary hover:underline",
|
|
416
|
+
children: "Don't have an account? Sign up"
|
|
417
|
+
}
|
|
418
|
+
) }) : /* @__PURE__ */ jsxs2("p", { className: "text-center text-muted-foreground", children: [
|
|
419
|
+
"Don't have an account?",
|
|
420
|
+
" ",
|
|
421
|
+
/* @__PURE__ */ jsx6("a", { href: "/signup", className: "text-primary hover:underline", children: "Sign up" })
|
|
422
|
+
] }))
|
|
423
|
+
] })
|
|
424
|
+
] }) });
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// src/components/EventSelector/EventSelector.tsx
|
|
428
|
+
init_Button();
|
|
429
|
+
init_EventProvider();
|
|
430
|
+
import { RefreshCw, AlertCircle, Lock, Calendar, Star } from "lucide-react";
|
|
431
|
+
import { useEffect, useMemo as useMemo2 } from "react";
|
|
432
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
433
|
+
function EventSelector({
|
|
434
|
+
placeholder = "Select an event",
|
|
435
|
+
className,
|
|
436
|
+
onEventChange,
|
|
437
|
+
showNoEventsMessage = true,
|
|
438
|
+
showRetryButton = true,
|
|
439
|
+
showEventDetails = true,
|
|
440
|
+
showNextEventIndicator = true
|
|
441
|
+
}) {
|
|
442
|
+
const {
|
|
443
|
+
events,
|
|
444
|
+
selectedEvent,
|
|
445
|
+
isLoading,
|
|
446
|
+
error,
|
|
447
|
+
setSelectedEvent,
|
|
448
|
+
refreshEvents
|
|
449
|
+
} = useEvents();
|
|
450
|
+
const handleValueChange = (eventId) => {
|
|
451
|
+
const event = events.find((e) => (e.event_id || e.id) === eventId);
|
|
452
|
+
if (event) {
|
|
453
|
+
setSelectedEvent(event);
|
|
454
|
+
if (onEventChange) {
|
|
455
|
+
onEventChange(event);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
const handleRetry = () => {
|
|
460
|
+
refreshEvents();
|
|
461
|
+
};
|
|
462
|
+
const isNextEvent = (event) => {
|
|
463
|
+
if (!event.event_date) return false;
|
|
464
|
+
const now = /* @__PURE__ */ new Date();
|
|
465
|
+
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
466
|
+
const eventDate = new Date(event.event_date);
|
|
467
|
+
return eventDate >= today;
|
|
468
|
+
};
|
|
469
|
+
const formatEventDate = (dateString) => {
|
|
470
|
+
const date = new Date(dateString);
|
|
471
|
+
const today = /* @__PURE__ */ new Date();
|
|
472
|
+
const tomorrow = new Date(today);
|
|
473
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
474
|
+
const normalizeDate = (d) => {
|
|
475
|
+
const normalized = new Date(d);
|
|
476
|
+
normalized.setHours(0, 0, 0, 0);
|
|
477
|
+
return normalized;
|
|
478
|
+
};
|
|
479
|
+
const normalizedDate = normalizeDate(date);
|
|
480
|
+
const normalizedToday = normalizeDate(today);
|
|
481
|
+
const normalizedTomorrow = normalizeDate(tomorrow);
|
|
482
|
+
if (normalizedDate.getTime() === normalizedToday.getTime()) {
|
|
483
|
+
return "Today";
|
|
484
|
+
} else if (normalizedDate.getTime() === normalizedTomorrow.getTime()) {
|
|
485
|
+
return "Tomorrow";
|
|
486
|
+
} else {
|
|
487
|
+
return date.toLocaleDateString();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
const sortedEvents = useMemo2(() => {
|
|
491
|
+
const getTime = (e) => e.event_date ? new Date(e.event_date).getTime() : Number.NEGATIVE_INFINITY;
|
|
492
|
+
return [...events].sort((a, b) => getTime(b) - getTime(a));
|
|
493
|
+
}, [events]);
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
if (!selectedEvent && events.length > 0) {
|
|
496
|
+
const today = /* @__PURE__ */ new Date();
|
|
497
|
+
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
498
|
+
const next = [...events].filter((e) => e.event_date && new Date(e.event_date).getTime() >= startOfToday).sort((a, b) => new Date(a.event_date).getTime() - new Date(b.event_date).getTime())[0];
|
|
499
|
+
if (next) {
|
|
500
|
+
setSelectedEvent(next);
|
|
501
|
+
if (onEventChange) onEventChange(next);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}, [events, selectedEvent, setSelectedEvent, onEventChange]);
|
|
505
|
+
if (isLoading) {
|
|
506
|
+
return /* @__PURE__ */ jsxs3("div", { className: `flex items-center gap-2 ${className}`, children: [
|
|
507
|
+
/* @__PURE__ */ jsx7(LoadingSpinner, { size: "sm" }),
|
|
508
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm text-muted-foreground", children: "Loading events..." })
|
|
509
|
+
] });
|
|
510
|
+
}
|
|
511
|
+
if (error) {
|
|
512
|
+
return /* @__PURE__ */ jsx7("div", { className, children: /* @__PURE__ */ jsxs3(Alert, { variant: "destructive", children: [
|
|
513
|
+
/* @__PURE__ */ jsx7(Lock, { className: "h-4 w-4" }),
|
|
514
|
+
/* @__PURE__ */ jsxs3(AlertDescription, { className: "flex items-center justify-between", children: [
|
|
515
|
+
/* @__PURE__ */ jsx7("span", { children: error.message }),
|
|
516
|
+
showRetryButton && /* @__PURE__ */ jsxs3(
|
|
517
|
+
Button,
|
|
518
|
+
{
|
|
519
|
+
variant: "outline",
|
|
520
|
+
size: "sm",
|
|
521
|
+
onClick: handleRetry,
|
|
522
|
+
className: "ml-2",
|
|
523
|
+
children: [
|
|
524
|
+
/* @__PURE__ */ jsx7(RefreshCw, { className: "h-3 w-3 mr-1" }),
|
|
525
|
+
"Retry"
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
] })
|
|
530
|
+
] }) });
|
|
531
|
+
}
|
|
532
|
+
if (events.length === 0) {
|
|
533
|
+
if (showNoEventsMessage) {
|
|
534
|
+
return /* @__PURE__ */ jsx7("div", { className, children: /* @__PURE__ */ jsxs3(Alert, { variant: "inline", children: [
|
|
535
|
+
/* @__PURE__ */ jsx7(AlertCircle, { className: "h-4 w-4 text-acc-700" }),
|
|
536
|
+
/* @__PURE__ */ jsxs3(AlertDescription, { className: "flex items-center justify-between", children: [
|
|
537
|
+
/* @__PURE__ */ jsx7("span", { children: "No events available." }),
|
|
538
|
+
showRetryButton && /* @__PURE__ */ jsxs3(
|
|
539
|
+
Button,
|
|
540
|
+
{
|
|
541
|
+
variant: "outline",
|
|
542
|
+
size: "sm",
|
|
543
|
+
onClick: handleRetry,
|
|
544
|
+
className: "ml-2",
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsx7(RefreshCw, { className: "h-3 w-3 mr-1" }),
|
|
547
|
+
"Refresh"
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
] })
|
|
552
|
+
] }) });
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
return /* @__PURE__ */ jsxs3(
|
|
557
|
+
Select,
|
|
558
|
+
{
|
|
559
|
+
value: selectedEvent ? selectedEvent.event_id || selectedEvent.id : "",
|
|
560
|
+
onValueChange: handleValueChange,
|
|
561
|
+
className,
|
|
562
|
+
children: [
|
|
563
|
+
/* @__PURE__ */ jsx7(SelectTrigger, { className: "text-left", variant: "outline", children: /* @__PURE__ */ jsx7(SelectValue, { placeholder, children: selectedEvent && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
564
|
+
/* @__PURE__ */ jsx7(Calendar, { className: "h-4 w-4 flex-shrink-0" }),
|
|
565
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate", children: selectedEvent.event_name || selectedEvent.name }),
|
|
566
|
+
selectedEvent.event_date && /* @__PURE__ */ jsxs3("span", { className: "text-xs text-muted-foreground flex-shrink-0", children: [
|
|
567
|
+
"(",
|
|
568
|
+
formatEventDate(selectedEvent.event_date),
|
|
569
|
+
")"
|
|
570
|
+
] })
|
|
571
|
+
] }) }) }),
|
|
572
|
+
/* @__PURE__ */ jsx7(SelectContent, { children: sortedEvents.map((event) => {
|
|
573
|
+
const isNext = isNextEvent(event);
|
|
574
|
+
const isSelected = selectedEvent && (selectedEvent.event_id === event.event_id || selectedEvent.id === event.id);
|
|
575
|
+
return /* @__PURE__ */ jsx7(
|
|
576
|
+
SelectItem,
|
|
577
|
+
{
|
|
578
|
+
value: event.event_id || event.id,
|
|
579
|
+
className: "flex items-center justify-between",
|
|
580
|
+
children: /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 w-full", children: [
|
|
581
|
+
showNextEventIndicator && isNext && /* @__PURE__ */ jsx7(Star, { className: "h-3 w-3 text-acc-500" }),
|
|
582
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex-1", children: [
|
|
583
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
584
|
+
/* @__PURE__ */ jsx7("span", { className: isSelected ? "font-semibold" : "", children: event.event_name || event.name }),
|
|
585
|
+
isSelected && /* @__PURE__ */ jsx7("span", { className: "text-xs bg-primary text-primary-foreground px-1 rounded", children: "Current" })
|
|
586
|
+
] }),
|
|
587
|
+
showEventDetails && event.event_date && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
|
|
588
|
+
/* @__PURE__ */ jsx7(Calendar, { className: "h-3 w-3" }),
|
|
589
|
+
/* @__PURE__ */ jsx7("span", { children: formatEventDate(event.event_date) }),
|
|
590
|
+
showNextEventIndicator && isNext && /* @__PURE__ */ jsx7("span", { className: "text-acc-600 font-medium", children: "(Next)" })
|
|
591
|
+
] }),
|
|
592
|
+
showEventDetails && event.event_venue && /* @__PURE__ */ jsxs3("div", { className: "text-xs text-muted-foreground", children: [
|
|
593
|
+
"\u{1F4CD} ",
|
|
594
|
+
event.event_venue
|
|
595
|
+
] })
|
|
596
|
+
] })
|
|
597
|
+
] })
|
|
598
|
+
},
|
|
599
|
+
event.event_id || event.id
|
|
600
|
+
);
|
|
601
|
+
}) })
|
|
602
|
+
]
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/components/PasswordReset/PasswordChangeForm.tsx
|
|
608
|
+
init_Button();
|
|
609
|
+
import { useState as useState2 } from "react";
|
|
610
|
+
init_cn();
|
|
611
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
612
|
+
function PasswordChangeForm({ onSubmit, className }) {
|
|
613
|
+
const [newPassword, setNewPassword] = useState2("");
|
|
614
|
+
const [confirmPassword, setConfirmPassword] = useState2("");
|
|
615
|
+
const [error, setError] = useState2(null);
|
|
616
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
617
|
+
const handleSubmit = async (e) => {
|
|
618
|
+
e.preventDefault();
|
|
619
|
+
setError(null);
|
|
620
|
+
if (newPassword.length < 8) {
|
|
621
|
+
setError("Password must be at least 8 characters.");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (newPassword !== confirmPassword) {
|
|
625
|
+
setError("Passwords do not match.");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
setIsSubmitting(true);
|
|
629
|
+
try {
|
|
630
|
+
const result = await onSubmit({ newPassword, confirmPassword });
|
|
631
|
+
if (result && result.error) {
|
|
632
|
+
setError(result.error.message || "Failed to change password.");
|
|
633
|
+
}
|
|
634
|
+
} catch (err) {
|
|
635
|
+
setError(err?.message || "An unexpected error occurred.");
|
|
636
|
+
} finally {
|
|
637
|
+
setIsSubmitting(false);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
return /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className: cn("space-y-4", className), children: [
|
|
641
|
+
error && /* @__PURE__ */ jsx8("div", { role: "alert", children: error }),
|
|
642
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
643
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "new-password", children: "New Password" }),
|
|
644
|
+
/* @__PURE__ */ jsx8(
|
|
645
|
+
Input,
|
|
646
|
+
{
|
|
647
|
+
id: "new-password",
|
|
648
|
+
type: "password",
|
|
649
|
+
value: newPassword,
|
|
650
|
+
onChange: (e) => setNewPassword(e.target.value),
|
|
651
|
+
required: true,
|
|
652
|
+
disabled: isSubmitting
|
|
653
|
+
}
|
|
654
|
+
)
|
|
655
|
+
] }),
|
|
656
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
657
|
+
/* @__PURE__ */ jsx8(Label, { htmlFor: "confirm-password", children: "Confirm Password" }),
|
|
658
|
+
/* @__PURE__ */ jsx8(
|
|
659
|
+
Input,
|
|
660
|
+
{
|
|
661
|
+
id: "confirm-password",
|
|
662
|
+
type: "password",
|
|
663
|
+
value: confirmPassword,
|
|
664
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
665
|
+
required: true,
|
|
666
|
+
disabled: isSubmitting
|
|
667
|
+
}
|
|
668
|
+
)
|
|
669
|
+
] }),
|
|
670
|
+
/* @__PURE__ */ jsx8(
|
|
671
|
+
Button,
|
|
672
|
+
{
|
|
673
|
+
type: "submit",
|
|
674
|
+
className: "w-full",
|
|
675
|
+
disabled: isSubmitting || !newPassword || !confirmPassword,
|
|
676
|
+
children: isSubmitting ? "Changing..." : "Change Password"
|
|
677
|
+
}
|
|
678
|
+
)
|
|
679
|
+
] });
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/components/Button/index.ts
|
|
683
|
+
init_Button();
|
|
684
|
+
|
|
685
|
+
// src/components/UserMenu/UserMenu.tsx
|
|
686
|
+
import React7, { useCallback as useCallback2, useMemo as useMemo3, useState as useState3 } from "react";
|
|
687
|
+
import { ChevronDown, LogOut, KeyRound } from "lucide-react";
|
|
688
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
689
|
+
var UserMenu = React7.memo(function UserMenu2({
|
|
690
|
+
user,
|
|
691
|
+
onSignOut,
|
|
692
|
+
onChangePassword,
|
|
693
|
+
className,
|
|
694
|
+
showAvatar = true
|
|
695
|
+
}) {
|
|
696
|
+
const [isPasswordDialogOpen, setPasswordDialogOpen] = useState3(false);
|
|
697
|
+
const userInfo = useMemo3(() => {
|
|
698
|
+
if (!user) return null;
|
|
699
|
+
return {
|
|
700
|
+
email: user.email,
|
|
701
|
+
displayName: user.user_metadata?.display_name || user.user_metadata?.full_name || user.email?.split("@")[0],
|
|
702
|
+
avatarUrl: user.user_metadata?.avatar_url,
|
|
703
|
+
initial: (user.user_metadata?.display_name || user.user_metadata?.full_name || user.email || "U").charAt(0).toUpperCase()
|
|
704
|
+
};
|
|
705
|
+
}, [user]);
|
|
706
|
+
const handleSignOut = useCallback2(async () => {
|
|
707
|
+
if (onSignOut) await onSignOut();
|
|
708
|
+
}, [onSignOut]);
|
|
709
|
+
if (!user || !userInfo) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
return /* @__PURE__ */ jsxs5(Dialog, { open: isPasswordDialogOpen, onOpenChange: setPasswordDialogOpen, children: [
|
|
713
|
+
/* @__PURE__ */ jsxs5(Select, { className, children: [
|
|
714
|
+
/* @__PURE__ */ jsx9(SelectTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(Button, { variant: "outline", className: "flex items-center gap-2", "aria-label": userInfo.displayName, children: [
|
|
715
|
+
showAvatar && /* @__PURE__ */ jsxs5(Avatar, { className: "h-8 w-8", children: [
|
|
716
|
+
/* @__PURE__ */ jsx9(AvatarImage, { src: userInfo.avatarUrl, alt: userInfo.displayName }),
|
|
717
|
+
/* @__PURE__ */ jsx9(AvatarFallback, { children: userInfo.initial })
|
|
718
|
+
] }),
|
|
719
|
+
/* @__PURE__ */ jsx9("span", { className: "hidden sm:inline-block", children: userInfo.displayName }),
|
|
720
|
+
/* @__PURE__ */ jsx9(ChevronDown, { className: "h-4 w-4" })
|
|
721
|
+
] }) }),
|
|
722
|
+
/* @__PURE__ */ jsxs5(SelectContent, { children: [
|
|
723
|
+
/* @__PURE__ */ jsx9(SelectLabel, { className: "font-normal", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col space-y-1", children: [
|
|
724
|
+
/* @__PURE__ */ jsx9("p", { className: "font-medium", children: userInfo.displayName }),
|
|
725
|
+
/* @__PURE__ */ jsx9("p", { className: "text-muted-foreground", children: userInfo.email })
|
|
726
|
+
] }) }),
|
|
727
|
+
/* @__PURE__ */ jsx9(SelectSeparator, {}),
|
|
728
|
+
/* @__PURE__ */ jsx9(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(SelectItem, { value: "change-password", children: [
|
|
729
|
+
/* @__PURE__ */ jsx9(KeyRound, { className: "mr-2 h-4 w-4" }),
|
|
730
|
+
/* @__PURE__ */ jsx9("span", { children: "Change Password" })
|
|
731
|
+
] }) }),
|
|
732
|
+
/* @__PURE__ */ jsxs5(SelectItem, { value: "sign-out", onClick: handleSignOut, children: [
|
|
733
|
+
/* @__PURE__ */ jsx9(LogOut, { className: "mr-2 h-4 w-4" }),
|
|
734
|
+
/* @__PURE__ */ jsx9("span", { children: "Sign out" })
|
|
735
|
+
] })
|
|
736
|
+
] })
|
|
737
|
+
] }),
|
|
738
|
+
/* @__PURE__ */ jsx9(DialogOverlay, {}),
|
|
739
|
+
/* @__PURE__ */ jsxs5(DialogContent, { className, children: [
|
|
740
|
+
/* @__PURE__ */ jsx9(DialogHeader, { children: /* @__PURE__ */ jsx9(DialogTitle, { children: "Change Password" }) }),
|
|
741
|
+
/* @__PURE__ */ jsx9(
|
|
742
|
+
PasswordChangeForm,
|
|
743
|
+
{
|
|
744
|
+
onSubmit: async ({ newPassword, confirmPassword }) => {
|
|
745
|
+
if (onChangePassword) {
|
|
746
|
+
const { error } = await onChangePassword(newPassword, confirmPassword);
|
|
747
|
+
if (!error) {
|
|
748
|
+
setPasswordDialogOpen(false);
|
|
749
|
+
}
|
|
750
|
+
return { error };
|
|
751
|
+
}
|
|
752
|
+
return {};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
)
|
|
756
|
+
] })
|
|
757
|
+
] });
|
|
758
|
+
});
|
|
759
|
+
var UserMenuLoading = React7.memo(function UserMenuLoading2() {
|
|
760
|
+
return /* @__PURE__ */ jsxs5("div", { className: "relative inline-block text-left", children: [
|
|
761
|
+
/* @__PURE__ */ jsxs5(
|
|
762
|
+
"button",
|
|
763
|
+
{
|
|
764
|
+
type: "button",
|
|
765
|
+
disabled: true,
|
|
766
|
+
className: "flex items-center space-x-2 px-3 py-2 text-sm font-medium text-muted-foreground bg-muted border border-input rounded-md",
|
|
767
|
+
children: [
|
|
768
|
+
/* @__PURE__ */ jsx9("div", { className: "w-6 h-6 rounded-full bg-muted animate-pulse" }),
|
|
769
|
+
/* @__PURE__ */ jsx9("span", { className: "truncate max-w-[150px]", children: "Loading..." }),
|
|
770
|
+
/* @__PURE__ */ jsx9(ChevronDown, { className: "w-4 h-4 text-muted-foreground" })
|
|
771
|
+
]
|
|
772
|
+
}
|
|
773
|
+
),
|
|
774
|
+
/* @__PURE__ */ jsx9("div", { role: "status", "aria-label": "Loading user menu", "aria-live": "polite", className: "w-6 h-6 rounded-full bg-muted animate-pulse" })
|
|
775
|
+
] });
|
|
776
|
+
});
|
|
777
|
+
UserMenu.Loading = UserMenuLoading;
|
|
778
|
+
|
|
779
|
+
// src/components/NavigationMenu/NavigationMenu.tsx
|
|
780
|
+
import * as React8 from "react";
|
|
781
|
+
import { ChevronDown as ChevronDown2 } from "lucide-react";
|
|
782
|
+
init_UnifiedAuthProvider();
|
|
783
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
784
|
+
var NavigationMenu = React8.forwardRef(({
|
|
785
|
+
items,
|
|
786
|
+
mode = "dropdown",
|
|
787
|
+
currentPath,
|
|
788
|
+
onNavigate,
|
|
789
|
+
className,
|
|
790
|
+
disabled = false,
|
|
791
|
+
buttonText = "Menu",
|
|
792
|
+
showIcons = true,
|
|
793
|
+
navigationLabel = "Main navigation",
|
|
794
|
+
// NEW: Phase 2 - Enhanced Security Features
|
|
795
|
+
strictMode = true,
|
|
796
|
+
auditLog = true,
|
|
797
|
+
onNavigationAccessDenied,
|
|
798
|
+
onStrictModeViolation,
|
|
799
|
+
filterByPermissions = true,
|
|
800
|
+
...props
|
|
801
|
+
}, ref) => {
|
|
802
|
+
const [expandedItems, setExpandedItems] = React8.useState(/* @__PURE__ */ new Set());
|
|
803
|
+
const buttonRef = React8.useRef(null);
|
|
804
|
+
let authContext = null;
|
|
805
|
+
try {
|
|
806
|
+
authContext = useUnifiedAuth();
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.warn("[NavigationMenu] useUnifiedAuth not available, running in unauthenticated mode");
|
|
809
|
+
}
|
|
810
|
+
const filteredItems = React8.useMemo(() => {
|
|
811
|
+
if (!filterByPermissions || !authContext) return items || [];
|
|
812
|
+
return (items || []).filter((item) => {
|
|
813
|
+
if (item.meta?.hidden) return false;
|
|
814
|
+
if (item.permissions && item.permissions.length > 0) {
|
|
815
|
+
const hasPermission = item.permissions.some((permission) => {
|
|
816
|
+
if (typeof permission !== "string") return true;
|
|
817
|
+
return authContext.hasPermission(permission);
|
|
818
|
+
});
|
|
819
|
+
if (!hasPermission) return false;
|
|
820
|
+
}
|
|
821
|
+
if (item.roles && item.roles.length > 0) {
|
|
822
|
+
const hasRole = item.roles.some((role) => {
|
|
823
|
+
if (typeof role !== "string") return true;
|
|
824
|
+
return authContext.hasRole(role);
|
|
825
|
+
});
|
|
826
|
+
if (!hasRole) return false;
|
|
827
|
+
}
|
|
828
|
+
if (item.accessLevel) {
|
|
829
|
+
if (typeof item.accessLevel === "string") {
|
|
830
|
+
const accessLevel = item.accessLevel;
|
|
831
|
+
const hasAccessLevel = authContext.hasAccessLevel(accessLevel);
|
|
832
|
+
if (!hasAccessLevel) return false;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return true;
|
|
836
|
+
});
|
|
837
|
+
}, [items, filterByPermissions, authContext]);
|
|
838
|
+
React8.useEffect(() => {
|
|
839
|
+
if (auditLog && authContext) {
|
|
840
|
+
console.log("[NavigationMenu] Navigation access attempt:", {
|
|
841
|
+
itemId: "navigation-menu",
|
|
842
|
+
label: "Navigation Menu",
|
|
843
|
+
href: currentPath,
|
|
844
|
+
permissions: authContext.permissions,
|
|
845
|
+
roles: authContext.roles,
|
|
846
|
+
accessLevel: authContext.accessLevel,
|
|
847
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
}, [auditLog, authContext, currentPath]);
|
|
851
|
+
const handleHierarchicalKeyDown = (event, item) => {
|
|
852
|
+
switch (event.key) {
|
|
853
|
+
case "Enter":
|
|
854
|
+
case " ":
|
|
855
|
+
event.preventDefault();
|
|
856
|
+
if (item.children && item.children.length > 0) {
|
|
857
|
+
toggleExpanded(item.id);
|
|
858
|
+
} else if (item.href) {
|
|
859
|
+
onNavigate?.(item);
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
case "Escape":
|
|
863
|
+
if (expandedItems.has(item.id)) {
|
|
864
|
+
toggleExpanded(item.id);
|
|
865
|
+
}
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
const toggleExpanded = (itemId) => {
|
|
870
|
+
const newExpanded = new Set(expandedItems);
|
|
871
|
+
if (newExpanded.has(itemId)) {
|
|
872
|
+
newExpanded.delete(itemId);
|
|
873
|
+
} else {
|
|
874
|
+
newExpanded.add(itemId);
|
|
875
|
+
}
|
|
876
|
+
setExpandedItems(newExpanded);
|
|
877
|
+
};
|
|
878
|
+
const handleItemClick = (item) => {
|
|
879
|
+
if (auditLog) {
|
|
880
|
+
console.log(`[NavigationMenu] Navigation access attempt:`, {
|
|
881
|
+
itemId: item.id,
|
|
882
|
+
label: item.label,
|
|
883
|
+
href: item.href,
|
|
884
|
+
permissions: item.permissions,
|
|
885
|
+
roles: item.roles,
|
|
886
|
+
accessLevel: item.accessLevel,
|
|
887
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
const hasPermission = true;
|
|
891
|
+
if (!hasPermission) {
|
|
892
|
+
if (onNavigationAccessDenied) {
|
|
893
|
+
onNavigationAccessDenied(item.id, "Insufficient permissions");
|
|
894
|
+
}
|
|
895
|
+
if (strictMode) {
|
|
896
|
+
console.error(`[NavigationMenu] STRICT MODE VIOLATION: User attempted to access protected navigation item without permission`, {
|
|
897
|
+
itemId: item.id,
|
|
898
|
+
label: item.label,
|
|
899
|
+
href: item.href,
|
|
900
|
+
permissions: item.permissions,
|
|
901
|
+
roles: item.roles,
|
|
902
|
+
accessLevel: item.accessLevel,
|
|
903
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
904
|
+
});
|
|
905
|
+
if (onStrictModeViolation) {
|
|
906
|
+
onStrictModeViolation(item.id, "Insufficient permissions");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (onNavigate) {
|
|
912
|
+
onNavigate(item);
|
|
913
|
+
} else if (item.href) {
|
|
914
|
+
window.location.href = item.href;
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
const isActiveItem = (item) => {
|
|
918
|
+
if (item.isActive !== void 0) return item.isActive;
|
|
919
|
+
if (currentPath === item.href) return true;
|
|
920
|
+
if (item.children && item.children.length > 0) {
|
|
921
|
+
return item.children.some((child) => isActiveItem(child));
|
|
922
|
+
}
|
|
923
|
+
return false;
|
|
924
|
+
};
|
|
925
|
+
const handleNavigationSelect = (itemId) => {
|
|
926
|
+
const item = filteredItems.find((i) => i.id === itemId);
|
|
927
|
+
if (item) {
|
|
928
|
+
handleItemClick(item);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
const renderHierarchicalItem = (item, level = 0) => {
|
|
932
|
+
const hasChildren = item.children && item.children.length > 0;
|
|
933
|
+
const isExpanded = expandedItems.has(item.id);
|
|
934
|
+
const itemIsActive = isActiveItem(item);
|
|
935
|
+
return /* @__PURE__ */ jsx10("li", { role: "none", children: hasChildren ? /* @__PURE__ */ jsxs6("div", { children: [
|
|
936
|
+
/* @__PURE__ */ jsxs6(
|
|
937
|
+
"button",
|
|
938
|
+
{
|
|
939
|
+
onClick: () => toggleExpanded(item.id),
|
|
940
|
+
onKeyDown: (e) => handleHierarchicalKeyDown(e, item),
|
|
941
|
+
"aria-expanded": isExpanded,
|
|
942
|
+
"aria-controls": `submenu-${item.id}`,
|
|
943
|
+
"aria-current": itemIsActive ? "page" : void 0,
|
|
944
|
+
children: [
|
|
945
|
+
/* @__PURE__ */ jsx10("span", { children: item.label }),
|
|
946
|
+
/* @__PURE__ */ jsx10(ChevronDown2, { "aria-hidden": "true" })
|
|
947
|
+
]
|
|
948
|
+
}
|
|
949
|
+
),
|
|
950
|
+
isExpanded && item.children && /* @__PURE__ */ jsx10(
|
|
951
|
+
"ul",
|
|
952
|
+
{
|
|
953
|
+
id: `submenu-${item.id}`,
|
|
954
|
+
role: "menu",
|
|
955
|
+
"aria-label": `${item.label} submenu`,
|
|
956
|
+
children: item.children.map((child) => /* @__PURE__ */ jsx10(React8.Fragment, { children: renderHierarchicalItem(child, level + 1) }, child.id))
|
|
957
|
+
}
|
|
958
|
+
)
|
|
959
|
+
] }) : /* @__PURE__ */ jsx10(
|
|
960
|
+
"a",
|
|
961
|
+
{
|
|
962
|
+
href: item.href || "#",
|
|
963
|
+
onClick: (e) => {
|
|
964
|
+
if (onNavigate && item.href) {
|
|
965
|
+
e.preventDefault();
|
|
966
|
+
onNavigate(item);
|
|
967
|
+
}
|
|
968
|
+
},
|
|
969
|
+
onKeyDown: (e) => handleHierarchicalKeyDown(e, item),
|
|
970
|
+
role: "menuitem",
|
|
971
|
+
"aria-current": itemIsActive ? "page" : void 0,
|
|
972
|
+
children: item.label
|
|
973
|
+
}
|
|
974
|
+
) });
|
|
975
|
+
};
|
|
976
|
+
if (mode === "dropdown") {
|
|
977
|
+
return /* @__PURE__ */ jsxs6(
|
|
978
|
+
Select,
|
|
979
|
+
{
|
|
980
|
+
onValueChange: handleNavigationSelect,
|
|
981
|
+
className,
|
|
982
|
+
"data-testid": "navigation-menu-root",
|
|
983
|
+
children: [
|
|
984
|
+
/* @__PURE__ */ jsx10(
|
|
985
|
+
SelectTrigger,
|
|
986
|
+
{
|
|
987
|
+
ref: buttonRef,
|
|
988
|
+
disabled,
|
|
989
|
+
"aria-label": buttonText,
|
|
990
|
+
"data-testid": "navigation-menu-trigger",
|
|
991
|
+
children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: buttonText })
|
|
992
|
+
}
|
|
993
|
+
),
|
|
994
|
+
/* @__PURE__ */ jsx10(SelectContent, { children: filteredItems.map((item) => {
|
|
995
|
+
const isActive = isActiveItem(item);
|
|
996
|
+
return /* @__PURE__ */ jsx10(
|
|
997
|
+
SelectItem,
|
|
998
|
+
{
|
|
999
|
+
value: item.id,
|
|
1000
|
+
disabled: !item.href,
|
|
1001
|
+
"data-testid": `navigation-menu-item-${item.id}`,
|
|
1002
|
+
children: item.label
|
|
1003
|
+
},
|
|
1004
|
+
item.id
|
|
1005
|
+
);
|
|
1006
|
+
}) })
|
|
1007
|
+
]
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
return /* @__PURE__ */ jsx10(
|
|
1012
|
+
"nav",
|
|
1013
|
+
{
|
|
1014
|
+
ref,
|
|
1015
|
+
className,
|
|
1016
|
+
"aria-label": navigationLabel,
|
|
1017
|
+
...props,
|
|
1018
|
+
children: /* @__PURE__ */ jsx10("ul", { role: "menubar", children: filteredItems.map((item) => /* @__PURE__ */ jsx10(React8.Fragment, { children: renderHierarchicalItem(item, 0) }, item.id)) })
|
|
1019
|
+
}
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
NavigationMenu.displayName = "NavigationMenu";
|
|
1023
|
+
|
|
1024
|
+
// src/components/Header/Header.tsx
|
|
1025
|
+
init_cn();
|
|
1026
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1027
|
+
function Header({
|
|
1028
|
+
logoUrl,
|
|
1029
|
+
logoAlt = "Logo",
|
|
1030
|
+
logo,
|
|
1031
|
+
navItems = [],
|
|
1032
|
+
user,
|
|
1033
|
+
onSignOut,
|
|
1034
|
+
onChangePassword,
|
|
1035
|
+
actions,
|
|
1036
|
+
userMenu,
|
|
1037
|
+
className,
|
|
1038
|
+
showEventSelector = true,
|
|
1039
|
+
showUserMenu = true,
|
|
1040
|
+
currentPath,
|
|
1041
|
+
onNavigate
|
|
1042
|
+
}) {
|
|
1043
|
+
return /* @__PURE__ */ jsx11("header", { className: cn(
|
|
1044
|
+
"w-full border-b border-main-200 h-16 shadow-sm bg-main-100 ",
|
|
1045
|
+
className
|
|
1046
|
+
), role: "banner", children: /* @__PURE__ */ jsxs7("nav", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto grid grid-cols-[auto_auto_1fr_auto] gap-4 h-full items-center", children: [
|
|
1047
|
+
logo ? logo : logoUrl ? /* @__PURE__ */ jsx11(
|
|
1048
|
+
"img",
|
|
1049
|
+
{
|
|
1050
|
+
src: logoUrl,
|
|
1051
|
+
alt: logoAlt || "Logo",
|
|
1052
|
+
className: "h-[2.15rem] w-auto max-w-[200px] object-contain rounded-md shadow-md bg-transparent"
|
|
1053
|
+
}
|
|
1054
|
+
) : /* @__PURE__ */ jsx11(
|
|
1055
|
+
"img",
|
|
1056
|
+
{
|
|
1057
|
+
src: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23000'/%3E%3Ctext x='16' y='20' text-anchor='middle' fill='white' font-family='Arial' font-size='14' font-weight='bold'%3EL%3C/text%3E%3C/svg%3E",
|
|
1058
|
+
alt: logoAlt || "Logo",
|
|
1059
|
+
className: "h-8 w-8 shadow-md"
|
|
1060
|
+
}
|
|
1061
|
+
),
|
|
1062
|
+
navItems && navItems.length > 0 && /* @__PURE__ */ jsx11(
|
|
1063
|
+
NavigationMenu,
|
|
1064
|
+
{
|
|
1065
|
+
items: navItems,
|
|
1066
|
+
currentPath,
|
|
1067
|
+
onNavigate,
|
|
1068
|
+
buttonText: "Menu",
|
|
1069
|
+
className: "w-48"
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
showEventSelector && /* @__PURE__ */ jsx11(
|
|
1073
|
+
EventSelector,
|
|
1074
|
+
{
|
|
1075
|
+
placeholder: "Select event",
|
|
1076
|
+
className: "justify-self-end hidden sm:block w-96",
|
|
1077
|
+
"data-testid": "event-selector"
|
|
1078
|
+
}
|
|
1079
|
+
),
|
|
1080
|
+
actions,
|
|
1081
|
+
showUserMenu && (userMenu ? userMenu : /* @__PURE__ */ jsx11(
|
|
1082
|
+
UserMenu,
|
|
1083
|
+
{
|
|
1084
|
+
user: user || null,
|
|
1085
|
+
onSignOut,
|
|
1086
|
+
onChangePassword,
|
|
1087
|
+
className: "w-70"
|
|
1088
|
+
}
|
|
1089
|
+
))
|
|
1090
|
+
] }) });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/components/Footer/Footer.tsx
|
|
1094
|
+
init_cn();
|
|
1095
|
+
import React9 from "react";
|
|
1096
|
+
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1097
|
+
var FooterComponent = ({
|
|
1098
|
+
companyName = "Solvera Solutions Pty Ltd",
|
|
1099
|
+
year = (/* @__PURE__ */ new Date()).getFullYear(),
|
|
1100
|
+
links,
|
|
1101
|
+
className = "",
|
|
1102
|
+
logo,
|
|
1103
|
+
copyright,
|
|
1104
|
+
children
|
|
1105
|
+
}) => {
|
|
1106
|
+
const copyrightText = copyright || `\xA9 Copyright 2022\u2013${year} all rights reserved, ${companyName}.`;
|
|
1107
|
+
return /* @__PURE__ */ jsx12("footer", { className: cn("mt-8 py-6 flex justify-center border-t border-border bg-main-100", className), children: /* @__PURE__ */ jsxs8("section", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto text-center", children: [
|
|
1108
|
+
logo && /* @__PURE__ */ jsx12("img", { src: logo, alt: "Logo", className: "h-8 w-auto" }),
|
|
1109
|
+
children && /* @__PURE__ */ jsx12(Fragment3, { children }),
|
|
1110
|
+
/* @__PURE__ */ jsx12("span", { className: "text-muted-foreground", children: copyrightText }),
|
|
1111
|
+
links && links.length > 0 && /* @__PURE__ */ jsx12("ul", { className: "flex gap-4 mt-2 md:mt-0", children: links.map((link, index) => /* @__PURE__ */ jsx12("li", { children: /* @__PURE__ */ jsx12("a", { href: link.href, className: "text-muted-foreground hover:text-foreground", children: link.label }) }, index)) })
|
|
1112
|
+
] }) });
|
|
1113
|
+
};
|
|
1114
|
+
FooterComponent.displayName = "Footer";
|
|
1115
|
+
var Footer = React9.memo(FooterComponent);
|
|
1116
|
+
Footer.displayName = "Footer";
|
|
1117
|
+
|
|
1118
|
+
// src/components/PaceAppLayout/PaceAppLayout.tsx
|
|
1119
|
+
init_UnifiedAuthProvider();
|
|
1120
|
+
import { useState as useState5, useEffect as useEffect3, useMemo as useMemo5, useCallback as useCallback3 } from "react";
|
|
1121
|
+
import { Outlet, useNavigate, useLocation } from "react-router-dom";
|
|
1122
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1123
|
+
var EMPTY_PAGE_ID_MAPPING = {};
|
|
1124
|
+
var EMPTY_ROUTE_PERMISSIONS = {};
|
|
1125
|
+
function PaceAppLayout({
|
|
1126
|
+
appName,
|
|
1127
|
+
navItems,
|
|
1128
|
+
showEventSelector,
|
|
1129
|
+
headerActions,
|
|
1130
|
+
customLogo,
|
|
1131
|
+
customUserMenu,
|
|
1132
|
+
headerClassName,
|
|
1133
|
+
showUserMenu = true,
|
|
1134
|
+
enforcePermissions = false,
|
|
1135
|
+
defaultPermission = "read",
|
|
1136
|
+
routePermissions = EMPTY_ROUTE_PERMISSIONS,
|
|
1137
|
+
permissionFallback,
|
|
1138
|
+
filterNavigationByPermissions = false,
|
|
1139
|
+
pageIdMapping = EMPTY_PAGE_ID_MAPPING,
|
|
1140
|
+
// NEW: Phase 1 - Enhanced Security Features
|
|
1141
|
+
strictMode = true,
|
|
1142
|
+
enforcePagePermissions = false,
|
|
1143
|
+
pagePermissionFallback,
|
|
1144
|
+
auditLog = true,
|
|
1145
|
+
onPageAccessDenied,
|
|
1146
|
+
onStrictModeViolation,
|
|
1147
|
+
// NEW: Phase 2 - Enhanced Routing Features
|
|
1148
|
+
roleBasedRouting = false,
|
|
1149
|
+
routeConfig = [],
|
|
1150
|
+
fallbackRoute = "/unauthorized",
|
|
1151
|
+
onRouteAccessDenied,
|
|
1152
|
+
onRouteStrictModeViolation
|
|
1153
|
+
}) {
|
|
1154
|
+
const { user, signOut, updatePassword } = useUnifiedAuth();
|
|
1155
|
+
const navigate = useNavigate();
|
|
1156
|
+
const location = useLocation();
|
|
1157
|
+
const checkPermission = useCallback3(async (permission, pageId) => {
|
|
1158
|
+
try {
|
|
1159
|
+
if (!user?.id) return false;
|
|
1160
|
+
const scope = {
|
|
1161
|
+
organisationId: user.user_metadata?.organisationId || user.app_metadata?.organisationId,
|
|
1162
|
+
eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,
|
|
1163
|
+
appId: user.user_metadata?.appId || user.app_metadata?.appId
|
|
1164
|
+
};
|
|
1165
|
+
const { isSuperAdmin } = await import("./api-ETQ6YJ3C.js");
|
|
1166
|
+
const isSuper = await isSuperAdmin(user.id);
|
|
1167
|
+
if (isSuper) {
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
if (!scope.organisationId) {
|
|
1171
|
+
console.warn("No organisation context available for permission check, denying access");
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
return await isPermitted({
|
|
1175
|
+
userId: user.id,
|
|
1176
|
+
scope,
|
|
1177
|
+
permission,
|
|
1178
|
+
pageId
|
|
1179
|
+
});
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
console.error("Permission check failed:", error);
|
|
1182
|
+
throw error;
|
|
1183
|
+
}
|
|
1184
|
+
}, [user?.id]);
|
|
1185
|
+
const [hasPermission, setHasPermission] = useState5(null);
|
|
1186
|
+
const [isCheckingPermission, setIsCheckingPermission] = useState5(false);
|
|
1187
|
+
const [permissionError, setPermissionError] = useState5(null);
|
|
1188
|
+
const defaultNavItems = useMemo5(() => [
|
|
1189
|
+
{ id: "home", label: "Home", href: "/", icon: "Home" },
|
|
1190
|
+
{ id: "dashboard", label: "Dashboard", href: "/dashboard", icon: "LayoutDashboard" },
|
|
1191
|
+
{ id: "settings", label: "Settings", href: "/settings", icon: "Settings" },
|
|
1192
|
+
{ id: "ui-showcase", label: "UI Showcase", href: "/ui-showcase", icon: "Component" },
|
|
1193
|
+
{ id: "data-table-showcase", label: "DataTable Showcase", href: "/data-table-showcase", icon: "Table" }
|
|
1194
|
+
], []);
|
|
1195
|
+
const baseMenuItems = useMemo5(() => navItems || defaultNavItems, [navItems]);
|
|
1196
|
+
const currentRoutePermission = useMemo5(() => {
|
|
1197
|
+
const currentPath = location.pathname;
|
|
1198
|
+
return routePermissions[currentPath] || defaultPermission;
|
|
1199
|
+
}, [location.pathname, routePermissions, defaultPermission]);
|
|
1200
|
+
const currentPageId = useMemo5(() => {
|
|
1201
|
+
const currentPath = location.pathname;
|
|
1202
|
+
return pageIdMapping[currentPath] || currentPath.slice(1) || "home";
|
|
1203
|
+
}, [location.pathname, pageIdMapping]);
|
|
1204
|
+
useEffect3(() => {
|
|
1205
|
+
if (!enforcePermissions) {
|
|
1206
|
+
setHasPermission(true);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
let isMounted = true;
|
|
1210
|
+
const checkRoutePermission = async () => {
|
|
1211
|
+
if (!isMounted) return;
|
|
1212
|
+
setIsCheckingPermission(true);
|
|
1213
|
+
setPermissionError(null);
|
|
1214
|
+
try {
|
|
1215
|
+
const hasAccess = await checkPermission(currentRoutePermission, currentPageId);
|
|
1216
|
+
if (!isMounted) return;
|
|
1217
|
+
setHasPermission(hasAccess);
|
|
1218
|
+
if (auditLog) {
|
|
1219
|
+
console.log(`[PaceAppLayout] Page access attempt:`, {
|
|
1220
|
+
pageName: currentPageId,
|
|
1221
|
+
operation: currentRoutePermission,
|
|
1222
|
+
userId: user?.id,
|
|
1223
|
+
allowed: hasAccess,
|
|
1224
|
+
strictMode,
|
|
1225
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (strictMode && !hasAccess) {
|
|
1229
|
+
console.error(`[PaceAppLayout] STRICT MODE VIOLATION: User attempted to access protected page without permission`, {
|
|
1230
|
+
pageName: currentPageId,
|
|
1231
|
+
operation: currentRoutePermission,
|
|
1232
|
+
userId: user?.id,
|
|
1233
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1234
|
+
});
|
|
1235
|
+
if (onStrictModeViolation) {
|
|
1236
|
+
onStrictModeViolation(currentPageId, currentRoutePermission);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (!hasAccess && onPageAccessDenied) {
|
|
1240
|
+
onPageAccessDenied(currentPageId, currentRoutePermission);
|
|
1241
|
+
}
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
if (!isMounted) return;
|
|
1244
|
+
console.error(`[PaceAppLayout] Permission check failed for ${currentPageId}:`, error);
|
|
1245
|
+
setPermissionError(error instanceof Error ? error : new Error("Permission check failed"));
|
|
1246
|
+
setHasPermission(false);
|
|
1247
|
+
} finally {
|
|
1248
|
+
if (isMounted) {
|
|
1249
|
+
setIsCheckingPermission(false);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
checkRoutePermission();
|
|
1254
|
+
return () => {
|
|
1255
|
+
isMounted = false;
|
|
1256
|
+
};
|
|
1257
|
+
}, [enforcePermissions, currentRoutePermission, currentPageId, strictMode, user?.id]);
|
|
1258
|
+
const [filteredMenuItems, setFilteredMenuItems] = useState5(baseMenuItems);
|
|
1259
|
+
useEffect3(() => {
|
|
1260
|
+
if (!filterNavigationByPermissions || !enforcePermissions) {
|
|
1261
|
+
setFilteredMenuItems(baseMenuItems);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
let isMounted = true;
|
|
1265
|
+
const filterItems = async () => {
|
|
1266
|
+
const filtered = await Promise.all(
|
|
1267
|
+
baseMenuItems.map(async (item) => {
|
|
1268
|
+
if (!item.href) return { item, hasAccess: true };
|
|
1269
|
+
const pageId = pageIdMapping[item.href] || item.href.slice(1) || "home";
|
|
1270
|
+
const permission = routePermissions[item.href] || defaultPermission;
|
|
1271
|
+
try {
|
|
1272
|
+
const hasAccess = await checkPermission(permission, pageId);
|
|
1273
|
+
return { item, hasAccess };
|
|
1274
|
+
} catch {
|
|
1275
|
+
return { item, hasAccess: false };
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1278
|
+
);
|
|
1279
|
+
if (!isMounted) return;
|
|
1280
|
+
const accessibleItems = filtered.filter(({ hasAccess }) => hasAccess).map(({ item }) => item);
|
|
1281
|
+
setFilteredMenuItems(accessibleItems);
|
|
1282
|
+
};
|
|
1283
|
+
filterItems();
|
|
1284
|
+
return () => {
|
|
1285
|
+
isMounted = false;
|
|
1286
|
+
};
|
|
1287
|
+
}, [baseMenuItems, filterNavigationByPermissions, enforcePermissions, pageIdMapping, routePermissions, defaultPermission]);
|
|
1288
|
+
useEffect3(() => {
|
|
1289
|
+
if (!roleBasedRouting || routeConfig.length === 0) return;
|
|
1290
|
+
const currentPath = location.pathname;
|
|
1291
|
+
const currentRoute = routeConfig.find((route) => route.path === currentPath);
|
|
1292
|
+
if (!currentRoute) {
|
|
1293
|
+
if (strictMode) {
|
|
1294
|
+
console.error(`[PaceAppLayout] STRICT MODE VIOLATION: Route not found in configuration`, {
|
|
1295
|
+
route: currentPath,
|
|
1296
|
+
userId: user?.id,
|
|
1297
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1298
|
+
});
|
|
1299
|
+
if (onRouteStrictModeViolation) {
|
|
1300
|
+
onRouteStrictModeViolation(currentPath, "Route not found in configuration");
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const hasAccess = true;
|
|
1306
|
+
if (!hasAccess) {
|
|
1307
|
+
if (onRouteAccessDenied) {
|
|
1308
|
+
onRouteAccessDenied(currentPath, "Insufficient permissions");
|
|
1309
|
+
}
|
|
1310
|
+
if (strictMode) {
|
|
1311
|
+
console.error(`[PaceAppLayout] STRICT MODE VIOLATION: User attempted to access protected route without permission`, {
|
|
1312
|
+
route: currentPath,
|
|
1313
|
+
userId: user?.id,
|
|
1314
|
+
permissions: currentRoute.permissions,
|
|
1315
|
+
roles: currentRoute.roles,
|
|
1316
|
+
accessLevel: currentRoute.accessLevel,
|
|
1317
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1318
|
+
});
|
|
1319
|
+
if (onRouteStrictModeViolation) {
|
|
1320
|
+
onRouteStrictModeViolation(currentPath, "Insufficient permissions");
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
navigate(fallbackRoute, { replace: true });
|
|
1324
|
+
}
|
|
1325
|
+
if (auditLog) {
|
|
1326
|
+
console.log(`[PaceAppLayout] Route access attempt:`, {
|
|
1327
|
+
route: currentPath,
|
|
1328
|
+
userId: user?.id,
|
|
1329
|
+
allowed: hasAccess,
|
|
1330
|
+
permissions: currentRoute.permissions,
|
|
1331
|
+
roles: currentRoute.roles,
|
|
1332
|
+
accessLevel: currentRoute.accessLevel,
|
|
1333
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
}, [roleBasedRouting, routeConfig, location.pathname, strictMode, user?.id, fallbackRoute]);
|
|
1337
|
+
const handleSignOut = async () => {
|
|
1338
|
+
await signOut();
|
|
1339
|
+
};
|
|
1340
|
+
const handleChangePassword = async (newPassword) => {
|
|
1341
|
+
const result = await updatePassword(newPassword);
|
|
1342
|
+
if (result?.error) {
|
|
1343
|
+
console.error("Failed to change password:", result.error.message);
|
|
1344
|
+
}
|
|
1345
|
+
return result || { error: null };
|
|
1346
|
+
};
|
|
1347
|
+
if (enforcePermissions && isCheckingPermission) {
|
|
1348
|
+
return /* @__PURE__ */ jsx13("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs9("div", { className: "text-center", children: [
|
|
1349
|
+
/* @__PURE__ */ jsx13("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-sec-900 mx-auto mb-4" }),
|
|
1350
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sec-600", children: "Checking permissions..." })
|
|
1351
|
+
] }) });
|
|
1352
|
+
}
|
|
1353
|
+
if (enforcePermissions && permissionError) {
|
|
1354
|
+
return /* @__PURE__ */ jsx13("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs9("div", { className: "text-center", children: [
|
|
1355
|
+
/* @__PURE__ */ jsx13("h2", { className: "text-xl font-semibold text-acc-600 mb-2", children: "Permission Error" }),
|
|
1356
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sec-600 mb-4", children: permissionError.message }),
|
|
1357
|
+
/* @__PURE__ */ jsx13(Button, { onClick: () => navigate("/"), children: "Go Home" })
|
|
1358
|
+
] }) });
|
|
1359
|
+
}
|
|
1360
|
+
if (enforcePermissions && hasPermission === false) {
|
|
1361
|
+
if (enforcePagePermissions && pagePermissionFallback) {
|
|
1362
|
+
return /* @__PURE__ */ jsx13(Fragment4, { children: pagePermissionFallback });
|
|
1363
|
+
}
|
|
1364
|
+
if (permissionFallback) {
|
|
1365
|
+
return /* @__PURE__ */ jsx13(Fragment4, { children: permissionFallback });
|
|
1366
|
+
}
|
|
1367
|
+
return /* @__PURE__ */ jsx13("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsxs9("div", { className: "text-center", children: [
|
|
1368
|
+
/* @__PURE__ */ jsx13("h2", { className: "text-xl font-semibold text-acc-600 mb-2", children: "Access Denied" }),
|
|
1369
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sec-600 mb-4", children: "You don't have permission to access this page." }),
|
|
1370
|
+
/* @__PURE__ */ jsx13(Button, { onClick: () => navigate("/"), children: "Go Home" })
|
|
1371
|
+
] }) });
|
|
1372
|
+
}
|
|
1373
|
+
return /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
1374
|
+
/* @__PURE__ */ jsx13(
|
|
1375
|
+
Header,
|
|
1376
|
+
{
|
|
1377
|
+
logo: customLogo || void 0,
|
|
1378
|
+
logoUrl: !customLogo ? `/${appName.toLowerCase()}_logo_wide.svg` : void 0,
|
|
1379
|
+
logoAlt: `${appName} Logo`,
|
|
1380
|
+
navItems: filteredMenuItems,
|
|
1381
|
+
actions: headerActions,
|
|
1382
|
+
userMenu: customUserMenu,
|
|
1383
|
+
user,
|
|
1384
|
+
onSignOut: handleSignOut,
|
|
1385
|
+
onChangePassword: handleChangePassword,
|
|
1386
|
+
currentPath: window.location.pathname,
|
|
1387
|
+
onNavigate: (item) => {
|
|
1388
|
+
if (item.href) {
|
|
1389
|
+
navigate(item.href);
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
showEventSelector,
|
|
1393
|
+
showUserMenu,
|
|
1394
|
+
className: headerClassName || "sticky top-0 z-[40] w-full"
|
|
1395
|
+
}
|
|
1396
|
+
),
|
|
1397
|
+
/* @__PURE__ */ jsx13("main", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto py-8", children: /* @__PURE__ */ jsx13(Outlet, {}) }),
|
|
1398
|
+
/* @__PURE__ */ jsx13(Footer, {})
|
|
1399
|
+
] });
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/components/PaceLoginPage/PaceLoginPage.tsx
|
|
1403
|
+
import { useEffect as useEffect4, useState as useState6 } from "react";
|
|
1404
|
+
import { useNavigate as useNavigate2 } from "react-router-dom";
|
|
1405
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1406
|
+
var PaceLoginPage = ({
|
|
1407
|
+
appName = "Pace",
|
|
1408
|
+
onSuccessRedirectPath = "/"
|
|
1409
|
+
}) => {
|
|
1410
|
+
const { signIn, isAuthenticated, isLoading, authError, hasRole } = useUnifiedAuth();
|
|
1411
|
+
const navigate = useNavigate2();
|
|
1412
|
+
const [isSigningIn, setIsSigningIn] = useState6(false);
|
|
1413
|
+
useEffect4(() => {
|
|
1414
|
+
if (isAuthenticated && !isLoading && hasRole("super_admin")) {
|
|
1415
|
+
try {
|
|
1416
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
console.error("Navigation error:", error);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
}, [isAuthenticated, isLoading, navigate, onSuccessRedirectPath, hasRole]);
|
|
1422
|
+
const handleSubmit = async (data) => {
|
|
1423
|
+
setIsSigningIn(true);
|
|
1424
|
+
try {
|
|
1425
|
+
const { error } = await signIn(data.email, data.password);
|
|
1426
|
+
if (!error) {
|
|
1427
|
+
try {
|
|
1428
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
1429
|
+
} catch (navError) {
|
|
1430
|
+
console.error("Navigation error after sign-in:", navError);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
} finally {
|
|
1434
|
+
setIsSigningIn(false);
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
return /* @__PURE__ */ jsxs10("main", { className: "min-h-screen grid mx-auto w-fit content-center justify-items-center gap-y-8", "aria-label": `${appName} Login Page`, children: [
|
|
1438
|
+
/* @__PURE__ */ jsx14(
|
|
1439
|
+
"img",
|
|
1440
|
+
{
|
|
1441
|
+
src: `/${appName.toLowerCase()}_logo_square.svg`,
|
|
1442
|
+
alt: `${appName} logo`,
|
|
1443
|
+
className: "h-48"
|
|
1444
|
+
}
|
|
1445
|
+
),
|
|
1446
|
+
/* @__PURE__ */ jsx14(
|
|
1447
|
+
LoginForm,
|
|
1448
|
+
{
|
|
1449
|
+
className: "w-md",
|
|
1450
|
+
onSignIn: handleSubmit,
|
|
1451
|
+
appName,
|
|
1452
|
+
isLoading: isSigningIn,
|
|
1453
|
+
onError: (error) => {
|
|
1454
|
+
console.error("Login error:", error);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
authError && /* @__PURE__ */ jsx14("em", { className: "mt-4 text-destructive text-center", children: authError.message })
|
|
1459
|
+
] });
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
1463
|
+
import { Component } from "react";
|
|
1464
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1465
|
+
var ErrorBoundary = class extends Component {
|
|
1466
|
+
constructor(props) {
|
|
1467
|
+
super(props);
|
|
1468
|
+
this.retryTimeoutId = null;
|
|
1469
|
+
this.reportError = (errorId, componentName) => {
|
|
1470
|
+
if (true) {
|
|
1471
|
+
console.warn("Error reporting would be triggered in production:", { errorId, componentName });
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
this.handleRetry = () => {
|
|
1475
|
+
const { maxRetries = 3 } = this.props;
|
|
1476
|
+
const { retryCount } = this.state;
|
|
1477
|
+
if (retryCount < maxRetries) {
|
|
1478
|
+
console.log(`Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1479
|
+
this.setState((prevState) => ({
|
|
1480
|
+
hasError: false,
|
|
1481
|
+
error: void 0,
|
|
1482
|
+
errorInfo: void 0,
|
|
1483
|
+
errorId: void 0,
|
|
1484
|
+
retryCount: prevState.retryCount + 1
|
|
1485
|
+
}));
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
this.state = {
|
|
1489
|
+
hasError: false,
|
|
1490
|
+
retryCount: 0
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
static getDerivedStateFromError(error) {
|
|
1494
|
+
const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1495
|
+
return {
|
|
1496
|
+
hasError: true,
|
|
1497
|
+
error,
|
|
1498
|
+
errorId
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
componentDidCatch(error, errorInfo) {
|
|
1502
|
+
const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
|
|
1503
|
+
const errorId = this.state.errorId;
|
|
1504
|
+
this.setState({ errorInfo });
|
|
1505
|
+
console.error(`ErrorBoundary [${componentName}] caught error ${errorId}:`, error, errorInfo);
|
|
1506
|
+
performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
|
|
1507
|
+
componentName,
|
|
1508
|
+
errorId,
|
|
1509
|
+
errorMessage: error.message,
|
|
1510
|
+
stack: error.stack?.substring(0, 200)
|
|
1511
|
+
// Truncated stack trace
|
|
1512
|
+
});
|
|
1513
|
+
if (enableReporting) {
|
|
1514
|
+
this.reportError(errorId, componentName);
|
|
1515
|
+
}
|
|
1516
|
+
if (onError) {
|
|
1517
|
+
onError(error, errorInfo, errorId);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
componentWillUnmount() {
|
|
1521
|
+
if (this.retryTimeoutId) {
|
|
1522
|
+
clearTimeout(this.retryTimeoutId);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
render() {
|
|
1526
|
+
if (this.state.hasError) {
|
|
1527
|
+
const {
|
|
1528
|
+
componentName = "Component",
|
|
1529
|
+
fallback,
|
|
1530
|
+
enableRetry = true,
|
|
1531
|
+
maxRetries = 3
|
|
1532
|
+
} = this.props;
|
|
1533
|
+
const { retryCount, errorId } = this.state;
|
|
1534
|
+
if (fallback) {
|
|
1535
|
+
return fallback;
|
|
1536
|
+
}
|
|
1537
|
+
return /* @__PURE__ */ jsx15(
|
|
1538
|
+
"div",
|
|
1539
|
+
{
|
|
1540
|
+
role: "alert",
|
|
1541
|
+
className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
|
|
1542
|
+
"data-error-boundary": errorId,
|
|
1543
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-3", children: [
|
|
1544
|
+
/* @__PURE__ */ jsx15("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx15("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx15("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
|
|
1545
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex-1 min-w-0", children: [
|
|
1546
|
+
/* @__PURE__ */ jsxs11("h3", { className: "text-destructive", children: [
|
|
1547
|
+
"Error in ",
|
|
1548
|
+
componentName
|
|
1549
|
+
] }),
|
|
1550
|
+
/* @__PURE__ */ jsx15("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
|
|
1551
|
+
enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs11("div", { className: "flex gap-3 mb-4", children: [
|
|
1552
|
+
/* @__PURE__ */ jsxs11(
|
|
1553
|
+
"button",
|
|
1554
|
+
{
|
|
1555
|
+
onClick: this.handleRetry,
|
|
1556
|
+
className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
|
|
1557
|
+
children: [
|
|
1558
|
+
"Retry (",
|
|
1559
|
+
retryCount + 1,
|
|
1560
|
+
"/",
|
|
1561
|
+
maxRetries,
|
|
1562
|
+
")"
|
|
1563
|
+
]
|
|
1564
|
+
}
|
|
1565
|
+
),
|
|
1566
|
+
/* @__PURE__ */ jsx15(
|
|
1567
|
+
"button",
|
|
1568
|
+
{
|
|
1569
|
+
onClick: () => window.location.reload(),
|
|
1570
|
+
className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
|
|
1571
|
+
children: "Reload Page"
|
|
1572
|
+
}
|
|
1573
|
+
)
|
|
1574
|
+
] }),
|
|
1575
|
+
retryCount >= maxRetries && /* @__PURE__ */ jsxs11("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
|
|
1576
|
+
/* @__PURE__ */ jsx15("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
|
|
1577
|
+
/* @__PURE__ */ jsx15(
|
|
1578
|
+
"button",
|
|
1579
|
+
{
|
|
1580
|
+
onClick: () => window.location.reload(),
|
|
1581
|
+
className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
|
|
1582
|
+
children: "Reload Page"
|
|
1583
|
+
}
|
|
1584
|
+
)
|
|
1585
|
+
] }),
|
|
1586
|
+
false
|
|
1587
|
+
] })
|
|
1588
|
+
] })
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
return this.props.children;
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
// src/components/OrganisationSelector/OrganisationSelector.tsx
|
|
1597
|
+
import { useState as useState7, useCallback as useCallback4 } from "react";
|
|
1598
|
+
init_Button();
|
|
1599
|
+
init_OrganisationProvider();
|
|
1600
|
+
import { RefreshCw as RefreshCw2, AlertCircle as AlertCircle2, Building2, Shield } from "lucide-react";
|
|
1601
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1602
|
+
function OrganisationSelector({
|
|
1603
|
+
placeholder = "Select organisation",
|
|
1604
|
+
className,
|
|
1605
|
+
onOrganisationChange,
|
|
1606
|
+
showNoOrganisationsMessage = true,
|
|
1607
|
+
showRetryButton = true,
|
|
1608
|
+
showRole = false,
|
|
1609
|
+
compact = false,
|
|
1610
|
+
disabled = false
|
|
1611
|
+
}) {
|
|
1612
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
1613
|
+
const [switchError, setSwitchError] = useState7(null);
|
|
1614
|
+
const {
|
|
1615
|
+
organisations,
|
|
1616
|
+
selectedOrganisation,
|
|
1617
|
+
isLoading: orgLoading,
|
|
1618
|
+
error: orgError,
|
|
1619
|
+
switchOrganisation,
|
|
1620
|
+
getUserRole,
|
|
1621
|
+
validateOrganisationAccess,
|
|
1622
|
+
refreshOrganisations
|
|
1623
|
+
} = useOrganisations();
|
|
1624
|
+
const handleOrganisationChange = useCallback4(async (orgId) => {
|
|
1625
|
+
if (disabled || isLoading) return;
|
|
1626
|
+
setSwitchError(null);
|
|
1627
|
+
setIsLoading(true);
|
|
1628
|
+
try {
|
|
1629
|
+
if (!validateOrganisationAccess(orgId)) {
|
|
1630
|
+
throw new Error("You do not have access to this organisation");
|
|
1631
|
+
}
|
|
1632
|
+
await switchOrganisation(orgId);
|
|
1633
|
+
const newOrganisation = organisations.find((org) => org.id === orgId);
|
|
1634
|
+
if (newOrganisation && onOrganisationChange) {
|
|
1635
|
+
onOrganisationChange(newOrganisation);
|
|
1636
|
+
}
|
|
1637
|
+
console.log("[OrganisationSelector] Successfully switched to organisation:", orgId);
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
console.error("[OrganisationSelector] Failed to switch organisation:", error);
|
|
1640
|
+
setSwitchError(error instanceof Error ? error.message : "Failed to switch organisation");
|
|
1641
|
+
} finally {
|
|
1642
|
+
setIsLoading(false);
|
|
1643
|
+
}
|
|
1644
|
+
}, [
|
|
1645
|
+
disabled,
|
|
1646
|
+
isLoading,
|
|
1647
|
+
validateOrganisationAccess,
|
|
1648
|
+
switchOrganisation,
|
|
1649
|
+
organisations,
|
|
1650
|
+
onOrganisationChange
|
|
1651
|
+
]);
|
|
1652
|
+
const handleRetry = useCallback4(async () => {
|
|
1653
|
+
setIsLoading(true);
|
|
1654
|
+
setSwitchError(null);
|
|
1655
|
+
try {
|
|
1656
|
+
await refreshOrganisations();
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
console.error("[OrganisationSelector] Failed to refresh organisations:", error);
|
|
1659
|
+
setSwitchError("Failed to refresh organisations");
|
|
1660
|
+
} finally {
|
|
1661
|
+
setIsLoading(false);
|
|
1662
|
+
}
|
|
1663
|
+
}, [refreshOrganisations]);
|
|
1664
|
+
if (orgLoading) {
|
|
1665
|
+
return /* @__PURE__ */ jsxs12("div", { className: `flex items-center gap-2 ${className}`, children: [
|
|
1666
|
+
/* @__PURE__ */ jsx16(LoadingSpinner, { size: "sm" }),
|
|
1667
|
+
/* @__PURE__ */ jsx16("span", { className: "text-sm text-muted-foreground", children: compact ? "Loading..." : "Loading organisations..." })
|
|
1668
|
+
] });
|
|
1669
|
+
}
|
|
1670
|
+
if (orgError) {
|
|
1671
|
+
return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className}`, children: [
|
|
1672
|
+
/* @__PURE__ */ jsxs12(Alert, { variant: "destructive", children: [
|
|
1673
|
+
/* @__PURE__ */ jsx16(AlertCircle2, { className: "h-4 w-4" }),
|
|
1674
|
+
/* @__PURE__ */ jsxs12(AlertDescription, { children: [
|
|
1675
|
+
"Failed to load organisations: ",
|
|
1676
|
+
orgError.message
|
|
1677
|
+
] })
|
|
1678
|
+
] }),
|
|
1679
|
+
showRetryButton && /* @__PURE__ */ jsxs12(
|
|
1680
|
+
Button,
|
|
1681
|
+
{
|
|
1682
|
+
variant: "outline",
|
|
1683
|
+
size: "sm",
|
|
1684
|
+
onClick: handleRetry,
|
|
1685
|
+
disabled: isLoading,
|
|
1686
|
+
className: "w-full",
|
|
1687
|
+
children: [
|
|
1688
|
+
/* @__PURE__ */ jsx16(RefreshCw2, { className: `h-4 w-4 mr-2 ${isLoading ? "animate-spin" : ""}` }),
|
|
1689
|
+
"Retry"
|
|
1690
|
+
]
|
|
1691
|
+
}
|
|
1692
|
+
)
|
|
1693
|
+
] });
|
|
1694
|
+
}
|
|
1695
|
+
if (organisations.length === 0) {
|
|
1696
|
+
if (showNoOrganisationsMessage) {
|
|
1697
|
+
return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className}`, children: [
|
|
1698
|
+
/* @__PURE__ */ jsxs12(Alert, { children: [
|
|
1699
|
+
/* @__PURE__ */ jsx16(Building2, { className: "h-4 w-4" }),
|
|
1700
|
+
/* @__PURE__ */ jsx16(AlertDescription, { children: "No organisations available. Please contact your administrator to be added to an organisation." })
|
|
1701
|
+
] }),
|
|
1702
|
+
showRetryButton && /* @__PURE__ */ jsxs12(
|
|
1703
|
+
Button,
|
|
1704
|
+
{
|
|
1705
|
+
variant: "outline",
|
|
1706
|
+
size: "sm",
|
|
1707
|
+
onClick: handleRetry,
|
|
1708
|
+
disabled: isLoading,
|
|
1709
|
+
className: "w-full",
|
|
1710
|
+
children: [
|
|
1711
|
+
/* @__PURE__ */ jsx16(RefreshCw2, { className: `h-4 w-4 mr-2 ${isLoading ? "animate-spin" : ""}` }),
|
|
1712
|
+
"Check Again"
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
)
|
|
1716
|
+
] });
|
|
1717
|
+
}
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
const switchErrorDisplay = switchError && /* @__PURE__ */ jsxs12(Alert, { variant: "destructive", className: "mt-2", children: [
|
|
1721
|
+
/* @__PURE__ */ jsx16(AlertCircle2, { className: "h-4 w-4" }),
|
|
1722
|
+
/* @__PURE__ */ jsx16(AlertDescription, { children: switchError })
|
|
1723
|
+
] });
|
|
1724
|
+
return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className}`, children: [
|
|
1725
|
+
/* @__PURE__ */ jsxs12(
|
|
1726
|
+
Select,
|
|
1727
|
+
{
|
|
1728
|
+
value: selectedOrganisation.id,
|
|
1729
|
+
onValueChange: handleOrganisationChange,
|
|
1730
|
+
disabled: disabled || isLoading,
|
|
1731
|
+
children: [
|
|
1732
|
+
/* @__PURE__ */ jsx16(SelectTrigger, { className: `${isLoading ? "opacity-50" : ""}`, children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
1733
|
+
isLoading ? /* @__PURE__ */ jsx16(LoadingSpinner, { size: "sm" }) : /* @__PURE__ */ jsx16(Building2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1734
|
+
/* @__PURE__ */ jsx16(SelectValue, { placeholder })
|
|
1735
|
+
] }) }),
|
|
1736
|
+
/* @__PURE__ */ jsx16(SelectContent, { children: organisations.map((org) => {
|
|
1737
|
+
const userRole = getUserRole(org.id);
|
|
1738
|
+
const hasAccess = validateOrganisationAccess(org.id);
|
|
1739
|
+
return /* @__PURE__ */ jsx16(
|
|
1740
|
+
SelectItem,
|
|
1741
|
+
{
|
|
1742
|
+
value: org.id,
|
|
1743
|
+
disabled: !hasAccess,
|
|
1744
|
+
className: !hasAccess ? "opacity-50" : "",
|
|
1745
|
+
children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between w-full", children: [
|
|
1746
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
1747
|
+
/* @__PURE__ */ jsx16(Building2, { className: "h-4 w-4" }),
|
|
1748
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-col", children: [
|
|
1749
|
+
/* @__PURE__ */ jsx16("span", { className: "font-medium", children: org.display_name }),
|
|
1750
|
+
!compact && org.description && /* @__PURE__ */ jsx16("span", { className: "text-xs text-muted-foreground truncate max-w-40", children: org.description })
|
|
1751
|
+
] })
|
|
1752
|
+
] }),
|
|
1753
|
+
showRole && /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1 ml-4", children: [
|
|
1754
|
+
/* @__PURE__ */ jsx16(Shield, { className: "h-3 w-3 text-muted-foreground" }),
|
|
1755
|
+
/* @__PURE__ */ jsx16("span", { className: "text-xs text-muted-foreground capitalize", children: userRole.replace("_", " ") })
|
|
1756
|
+
] })
|
|
1757
|
+
] })
|
|
1758
|
+
},
|
|
1759
|
+
org.id
|
|
1760
|
+
);
|
|
1761
|
+
}) })
|
|
1762
|
+
]
|
|
1763
|
+
}
|
|
1764
|
+
),
|
|
1765
|
+
switchErrorDisplay
|
|
1766
|
+
] });
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/components/PasswordReset/PasswordResetForm.tsx
|
|
1770
|
+
init_Button();
|
|
1771
|
+
import { useState as useState8 } from "react";
|
|
1772
|
+
init_cn();
|
|
1773
|
+
init_UnifiedAuthProvider();
|
|
1774
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1775
|
+
function PasswordResetForm({
|
|
1776
|
+
onSuccess,
|
|
1777
|
+
onError,
|
|
1778
|
+
className
|
|
1779
|
+
}) {
|
|
1780
|
+
const { resetPassword } = useUnifiedAuth();
|
|
1781
|
+
const [email, setEmail] = useState8("");
|
|
1782
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
1783
|
+
const [isSuccess, setIsSuccess] = useState8(false);
|
|
1784
|
+
const [error, setError] = useState8(null);
|
|
1785
|
+
const handleSubmit = async (e) => {
|
|
1786
|
+
e.preventDefault();
|
|
1787
|
+
if (!email.trim()) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
setIsLoading(true);
|
|
1791
|
+
setError(null);
|
|
1792
|
+
try {
|
|
1793
|
+
const { error: resetError } = await resetPassword(email);
|
|
1794
|
+
if (resetError) {
|
|
1795
|
+
const errorObj = new Error(resetError.message || "Failed to send reset email");
|
|
1796
|
+
setError(errorObj.message);
|
|
1797
|
+
onError?.(errorObj);
|
|
1798
|
+
} else {
|
|
1799
|
+
setIsSuccess(true);
|
|
1800
|
+
onSuccess?.();
|
|
1801
|
+
}
|
|
1802
|
+
} catch (err) {
|
|
1803
|
+
const errorObj = err instanceof Error ? err : new Error("An unexpected error occurred");
|
|
1804
|
+
setError(errorObj.message);
|
|
1805
|
+
onError?.(errorObj);
|
|
1806
|
+
} finally {
|
|
1807
|
+
setIsLoading(false);
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
const handleSendAnother = () => {
|
|
1811
|
+
setIsSuccess(false);
|
|
1812
|
+
setError(null);
|
|
1813
|
+
};
|
|
1814
|
+
if (isSuccess) {
|
|
1815
|
+
return /* @__PURE__ */ jsx17("div", { className: cn("", className), role: "form", children: /* @__PURE__ */ jsxs13("div", { className: "space-y-4 text-center", children: [
|
|
1816
|
+
/* @__PURE__ */ jsx17("h2", { children: "Check your email" }),
|
|
1817
|
+
/* @__PURE__ */ jsxs13("p", { className: "text-sec-600", children: [
|
|
1818
|
+
"We have sent a password reset link to ",
|
|
1819
|
+
email
|
|
1820
|
+
] }),
|
|
1821
|
+
/* @__PURE__ */ jsx17(
|
|
1822
|
+
Button,
|
|
1823
|
+
{
|
|
1824
|
+
variant: "outline",
|
|
1825
|
+
onClick: handleSendAnother,
|
|
1826
|
+
className: "w-full",
|
|
1827
|
+
children: "Send another email"
|
|
1828
|
+
}
|
|
1829
|
+
)
|
|
1830
|
+
] }) });
|
|
1831
|
+
}
|
|
1832
|
+
return /* @__PURE__ */ jsx17("div", { className: cn("", className), role: "form", children: /* @__PURE__ */ jsxs13("div", { className: "space-y-4", children: [
|
|
1833
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1834
|
+
/* @__PURE__ */ jsx17("h2", { children: "Reset Password" }),
|
|
1835
|
+
/* @__PURE__ */ jsx17("p", { className: "text-sec-600", children: "Enter your email address and we'll send you a reset link." })
|
|
1836
|
+
] }),
|
|
1837
|
+
/* @__PURE__ */ jsxs13("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
1838
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-2", children: [
|
|
1839
|
+
/* @__PURE__ */ jsx17(Label, { htmlFor: "email", children: "Email Address" }),
|
|
1840
|
+
/* @__PURE__ */ jsx17(
|
|
1841
|
+
Input,
|
|
1842
|
+
{
|
|
1843
|
+
id: "email",
|
|
1844
|
+
type: "email",
|
|
1845
|
+
value: email,
|
|
1846
|
+
onChange: (e) => setEmail(e.target.value),
|
|
1847
|
+
placeholder: "Enter your email",
|
|
1848
|
+
required: true,
|
|
1849
|
+
disabled: isLoading
|
|
1850
|
+
}
|
|
1851
|
+
)
|
|
1852
|
+
] }),
|
|
1853
|
+
error && /* @__PURE__ */ jsx17("div", { className: "text-sm text-destructive", role: "alert", children: error }),
|
|
1854
|
+
/* @__PURE__ */ jsx17(
|
|
1855
|
+
Button,
|
|
1856
|
+
{
|
|
1857
|
+
type: "submit",
|
|
1858
|
+
className: "w-full",
|
|
1859
|
+
disabled: !email.trim() || isLoading,
|
|
1860
|
+
children: isLoading ? "Sending..." : "Send Reset Link"
|
|
1861
|
+
}
|
|
1862
|
+
)
|
|
1863
|
+
] })
|
|
1864
|
+
] }) });
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// src/utils/storage/config.ts
|
|
1868
|
+
var FILE_SIZE_LIMITS = {
|
|
1869
|
+
// Images
|
|
1870
|
+
"image/jpeg": 5 * 1024 * 1024,
|
|
1871
|
+
// 5MB
|
|
1872
|
+
"image/png": 5 * 1024 * 1024,
|
|
1873
|
+
// 5MB
|
|
1874
|
+
"image/gif": 10 * 1024 * 1024,
|
|
1875
|
+
// 10MB (for animations)
|
|
1876
|
+
"image/webp": 5 * 1024 * 1024,
|
|
1877
|
+
// 5MB
|
|
1878
|
+
"image/svg+xml": 1 * 1024 * 1024,
|
|
1879
|
+
// 1MB (vector graphics)
|
|
1880
|
+
// Documents
|
|
1881
|
+
"application/pdf": 50 * 1024 * 1024,
|
|
1882
|
+
// 50MB
|
|
1883
|
+
"application/msword": 25 * 1024 * 1024,
|
|
1884
|
+
// 25MB
|
|
1885
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": 25 * 1024 * 1024,
|
|
1886
|
+
// 25MB
|
|
1887
|
+
"application/vnd.ms-excel": 25 * 1024 * 1024,
|
|
1888
|
+
// 25MB
|
|
1889
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": 25 * 1024 * 1024,
|
|
1890
|
+
// 25MB
|
|
1891
|
+
// Archives
|
|
1892
|
+
"application/zip": 100 * 1024 * 1024,
|
|
1893
|
+
// 100MB
|
|
1894
|
+
"application/x-rar-compressed": 100 * 1024 * 1024,
|
|
1895
|
+
// 100MB
|
|
1896
|
+
// Text files
|
|
1897
|
+
"text/plain": 1 * 1024 * 1024,
|
|
1898
|
+
// 1MB
|
|
1899
|
+
"text/csv": 10 * 1024 * 1024,
|
|
1900
|
+
// 10MB
|
|
1901
|
+
"application/json": 10 * 1024 * 1024
|
|
1902
|
+
// 10MB
|
|
1903
|
+
};
|
|
1904
|
+
var DEFAULT_FILE_SIZE_LIMIT = 10 * 1024 * 1024;
|
|
1905
|
+
var APP_PATH_MAPPING = {
|
|
1906
|
+
"PACE": "event_logos",
|
|
1907
|
+
"TRAC": "trac_accommodation",
|
|
1908
|
+
// Default category for TRAC files
|
|
1909
|
+
"MEDI": "documents",
|
|
1910
|
+
"CAKE": "documents"
|
|
1911
|
+
};
|
|
1912
|
+
var STORAGE_CONFIG = {
|
|
1913
|
+
bucketName: "files",
|
|
1914
|
+
fileSizeLimits: FILE_SIZE_LIMITS,
|
|
1915
|
+
defaultFileSizeLimit: DEFAULT_FILE_SIZE_LIMIT
|
|
1916
|
+
};
|
|
1917
|
+
function getFileSizeLimit(mimeType) {
|
|
1918
|
+
return STORAGE_CONFIG.fileSizeLimits[mimeType] || STORAGE_CONFIG.defaultFileSizeLimit;
|
|
1919
|
+
}
|
|
1920
|
+
function validateFileSize(file) {
|
|
1921
|
+
const limit = getFileSizeLimit(file.type);
|
|
1922
|
+
if (file.size > limit) {
|
|
1923
|
+
const limitMB = Math.round(limit / (1024 * 1024));
|
|
1924
|
+
const fileMB = Math.round(file.size / (1024 * 1024));
|
|
1925
|
+
return {
|
|
1926
|
+
isValid: false,
|
|
1927
|
+
error: `File size (${fileMB}MB) exceeds limit (${limitMB}MB) for ${file.type}`
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
return { isValid: true };
|
|
1931
|
+
}
|
|
1932
|
+
function formatFileSize(bytes) {
|
|
1933
|
+
if (bytes === 0) return "0 Bytes";
|
|
1934
|
+
if (bytes < 0) return `${bytes} Bytes`;
|
|
1935
|
+
const k = 1024;
|
|
1936
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
1937
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1938
|
+
const sizeIndex = Math.min(Math.max(i, 0), sizes.length - 1);
|
|
1939
|
+
return parseFloat((bytes / Math.pow(k, sizeIndex)).toFixed(2)) + " " + sizes[sizeIndex];
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/utils/storage/helpers.ts
|
|
1943
|
+
function generateFilePath(options, fileName) {
|
|
1944
|
+
const { appName, orgId, isPublic = false, customPath } = options;
|
|
1945
|
+
if (isPublic) {
|
|
1946
|
+
if (customPath) {
|
|
1947
|
+
return `public/${customPath}/${fileName}`;
|
|
1948
|
+
}
|
|
1949
|
+
return `public/assets/${appName}/${fileName}`;
|
|
1950
|
+
}
|
|
1951
|
+
const categoryMap = {
|
|
1952
|
+
"accommodation": "trac_accommodation",
|
|
1953
|
+
"activity": "trac_activity",
|
|
1954
|
+
"transport": "trac_transport",
|
|
1955
|
+
"journal": "trac_journal",
|
|
1956
|
+
"logos": "event_logos",
|
|
1957
|
+
"documents": "files"
|
|
1958
|
+
};
|
|
1959
|
+
if (customPath) {
|
|
1960
|
+
const mappedCategory = categoryMap[customPath] || customPath;
|
|
1961
|
+
return `apps/${appName}/${orgId}/${mappedCategory}/${fileName}`;
|
|
1962
|
+
}
|
|
1963
|
+
return `apps/${appName}/${orgId}/files/${fileName}`;
|
|
1964
|
+
}
|
|
1965
|
+
function generateUniqueFileName(originalName) {
|
|
1966
|
+
const timestamp = Date.now();
|
|
1967
|
+
const uuid = crypto.randomUUID();
|
|
1968
|
+
const extension = originalName.split(".").pop() || "";
|
|
1969
|
+
const baseName = originalName.replace(/\.[^/.]+$/, "");
|
|
1970
|
+
return `${timestamp}-${uuid}-${baseName}.${extension}`;
|
|
1971
|
+
}
|
|
1972
|
+
async function extractFileMetadata(file, options, uploadedBy) {
|
|
1973
|
+
const metadata = {
|
|
1974
|
+
mimeType: file.type,
|
|
1975
|
+
size: file.size,
|
|
1976
|
+
orgId: options.orgId,
|
|
1977
|
+
appName: options.appName,
|
|
1978
|
+
uploadedBy,
|
|
1979
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1980
|
+
tags: options.tags || [],
|
|
1981
|
+
isPublic: options.isPublic || false,
|
|
1982
|
+
customMetadata: options.metadata || {}
|
|
1983
|
+
};
|
|
1984
|
+
if (file.type.startsWith("image/")) {
|
|
1985
|
+
try {
|
|
1986
|
+
const dimensions = await getImageDimensions(file);
|
|
1987
|
+
metadata.width = dimensions.width;
|
|
1988
|
+
metadata.height = dimensions.height;
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
console.warn("Could not extract image dimensions:", error);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
try {
|
|
1994
|
+
metadata.hash = await generateFileHash(file);
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
console.warn("Could not generate file hash:", error);
|
|
1997
|
+
}
|
|
1998
|
+
return metadata;
|
|
1999
|
+
}
|
|
2000
|
+
async function getImageDimensions(file) {
|
|
2001
|
+
return new Promise((resolve, reject) => {
|
|
2002
|
+
const img = new Image();
|
|
2003
|
+
const url = URL.createObjectURL(file);
|
|
2004
|
+
img.onload = () => {
|
|
2005
|
+
URL.revokeObjectURL(url);
|
|
2006
|
+
resolve({ width: img.width, height: img.height });
|
|
2007
|
+
};
|
|
2008
|
+
img.onerror = () => {
|
|
2009
|
+
URL.revokeObjectURL(url);
|
|
2010
|
+
reject(new Error("Could not load image"));
|
|
2011
|
+
};
|
|
2012
|
+
img.src = url;
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
async function generateFileHash(file) {
|
|
2016
|
+
const buffer = await file.arrayBuffer();
|
|
2017
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
|
|
2018
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
2019
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2020
|
+
return `sha256:${hashHex}`;
|
|
2021
|
+
}
|
|
2022
|
+
async function uploadFile(supabase, file, options) {
|
|
2023
|
+
try {
|
|
2024
|
+
const sizeValidation = validateFileSize(file);
|
|
2025
|
+
if (!sizeValidation.isValid) {
|
|
2026
|
+
return {
|
|
2027
|
+
success: false,
|
|
2028
|
+
error: sizeValidation.error
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
const uniqueFileName = generateUniqueFileName(file.name);
|
|
2032
|
+
const filePath = generateFilePath(options, uniqueFileName);
|
|
2033
|
+
const metadata = await extractFileMetadata(file, options, "current-user");
|
|
2034
|
+
const { data, error } = await supabase.storage.from(STORAGE_CONFIG.bucketName).upload(filePath, file, {
|
|
2035
|
+
cacheControl: "3600",
|
|
2036
|
+
upsert: false,
|
|
2037
|
+
contentType: file.type
|
|
2038
|
+
});
|
|
2039
|
+
if (error) {
|
|
2040
|
+
return {
|
|
2041
|
+
success: false,
|
|
2042
|
+
error: `Upload failed: ${error.message}`
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
let publicUrl;
|
|
2046
|
+
if (options.isPublic) {
|
|
2047
|
+
const { data: urlData } = supabase.storage.from(STORAGE_CONFIG.bucketName).getPublicUrl(filePath);
|
|
2048
|
+
publicUrl = urlData.publicUrl;
|
|
2049
|
+
}
|
|
2050
|
+
return {
|
|
2051
|
+
success: true,
|
|
2052
|
+
path: filePath,
|
|
2053
|
+
publicUrl,
|
|
2054
|
+
metadata
|
|
2055
|
+
};
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
return {
|
|
2058
|
+
success: false,
|
|
2059
|
+
error: `Upload failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
function getPublicUrl(supabase, path) {
|
|
2064
|
+
const { data } = supabase.storage.from(STORAGE_CONFIG.bucketName).getPublicUrl(path);
|
|
2065
|
+
return data.publicUrl;
|
|
2066
|
+
}
|
|
2067
|
+
async function getSignedUrl(supabase, path, options) {
|
|
2068
|
+
try {
|
|
2069
|
+
const { data, error } = await supabase.storage.from(STORAGE_CONFIG.bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
2070
|
+
if (error) {
|
|
2071
|
+
console.error("Failed to create signed URL:", error);
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
return {
|
|
2075
|
+
url: data.signedUrl,
|
|
2076
|
+
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
2077
|
+
};
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
console.error("Failed to create signed URL:", error);
|
|
2080
|
+
return null;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
async function deleteFile(supabase, path) {
|
|
2084
|
+
try {
|
|
2085
|
+
const { error } = await supabase.storage.from(STORAGE_CONFIG.bucketName).remove([path]);
|
|
2086
|
+
if (error) {
|
|
2087
|
+
return {
|
|
2088
|
+
success: false,
|
|
2089
|
+
error: `Delete failed: ${error.message}`
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
return { success: true };
|
|
2093
|
+
} catch (error) {
|
|
2094
|
+
return {
|
|
2095
|
+
success: false,
|
|
2096
|
+
error: `Delete failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
async function listFiles(supabase, options) {
|
|
2101
|
+
try {
|
|
2102
|
+
const pathPrefix = `apps/${options.appName}/${options.orgId}/`;
|
|
2103
|
+
const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
|
|
2104
|
+
const { data, error } = await supabase.storage.from(STORAGE_CONFIG.bucketName).list(searchPath, {
|
|
2105
|
+
limit: options.limit || 100,
|
|
2106
|
+
offset: options.offset || 0,
|
|
2107
|
+
sortBy: { column: "created_at", order: "desc" }
|
|
2108
|
+
});
|
|
2109
|
+
if (error) {
|
|
2110
|
+
console.error("Failed to list files:", error);
|
|
2111
|
+
return { files: [], totalCount: 0, hasMore: false };
|
|
2112
|
+
}
|
|
2113
|
+
const files = (data || []).map((item) => ({
|
|
2114
|
+
name: item.name,
|
|
2115
|
+
path: `${searchPath}${item.name}`,
|
|
2116
|
+
size: item.metadata?.size || 0,
|
|
2117
|
+
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
2118
|
+
lastModified: item.updated_at || item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2119
|
+
metadata: {
|
|
2120
|
+
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
2121
|
+
size: item.metadata?.size || 0,
|
|
2122
|
+
orgId: options.orgId,
|
|
2123
|
+
appName: options.appName,
|
|
2124
|
+
uploadedBy: "unknown",
|
|
2125
|
+
uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2126
|
+
isPublic: false
|
|
2127
|
+
}
|
|
2128
|
+
}));
|
|
2129
|
+
return {
|
|
2130
|
+
files,
|
|
2131
|
+
totalCount: files.length,
|
|
2132
|
+
hasMore: files.length >= (options.limit || 100)
|
|
2133
|
+
};
|
|
2134
|
+
} catch (error) {
|
|
2135
|
+
console.error("Failed to list files:", error);
|
|
2136
|
+
return { files: [], totalCount: 0, hasMore: false };
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
async function archiveFile(supabase, path, options) {
|
|
2140
|
+
try {
|
|
2141
|
+
const archivedPath = path.replace(`apps/${options.appName}/${options.orgId}/`, `archived/${options.appName}/${options.orgId}/`);
|
|
2142
|
+
const { error: copyError } = await supabase.storage.from(STORAGE_CONFIG.bucketName).copy(path, archivedPath);
|
|
2143
|
+
if (copyError) {
|
|
2144
|
+
return {
|
|
2145
|
+
success: false,
|
|
2146
|
+
error: `Archive failed: ${copyError.message}`
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
const deleteResult = await deleteFile(supabase, path);
|
|
2150
|
+
if (!deleteResult.success) {
|
|
2151
|
+
return deleteResult;
|
|
2152
|
+
}
|
|
2153
|
+
return { success: true };
|
|
2154
|
+
} catch (error) {
|
|
2155
|
+
return {
|
|
2156
|
+
success: false,
|
|
2157
|
+
error: `Archive failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/hooks/useStorage.ts
|
|
2163
|
+
import { useState as useState9, useCallback as useCallback5 } from "react";
|
|
2164
|
+
function useStorage({ supabase, appName, orgId }) {
|
|
2165
|
+
const [isUploading, setIsUploading] = useState9(false);
|
|
2166
|
+
const [uploadError, setUploadError] = useState9(null);
|
|
2167
|
+
const [isListing, setIsListing] = useState9(false);
|
|
2168
|
+
const [listError, setListError] = useState9(null);
|
|
2169
|
+
const [files, setFiles] = useState9([]);
|
|
2170
|
+
const handleUploadFile = useCallback5(async (file, options = {}) => {
|
|
2171
|
+
setIsUploading(true);
|
|
2172
|
+
setUploadError(null);
|
|
2173
|
+
try {
|
|
2174
|
+
const uploadOptions = {
|
|
2175
|
+
appName,
|
|
2176
|
+
orgId,
|
|
2177
|
+
isPublic: false,
|
|
2178
|
+
...options
|
|
2179
|
+
};
|
|
2180
|
+
const result = await uploadFile(supabase, file, uploadOptions);
|
|
2181
|
+
if (result.success) {
|
|
2182
|
+
await refreshFiles();
|
|
2183
|
+
} else {
|
|
2184
|
+
setUploadError(result.error || "Upload failed");
|
|
2185
|
+
}
|
|
2186
|
+
return result;
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
const errorMessage = error instanceof Error ? error.message : "Upload failed";
|
|
2189
|
+
setUploadError(errorMessage);
|
|
2190
|
+
return {
|
|
2191
|
+
success: false,
|
|
2192
|
+
error: errorMessage
|
|
2193
|
+
};
|
|
2194
|
+
} finally {
|
|
2195
|
+
setIsUploading(false);
|
|
2196
|
+
}
|
|
2197
|
+
}, [supabase, appName, orgId]);
|
|
2198
|
+
const handleGetPublicUrl = useCallback5((path) => {
|
|
2199
|
+
return getPublicUrl(supabase, path);
|
|
2200
|
+
}, [supabase]);
|
|
2201
|
+
const handleGetSignedUrl = useCallback5(async (path, expiresIn) => {
|
|
2202
|
+
try {
|
|
2203
|
+
const result = await getSignedUrl(supabase, path, {
|
|
2204
|
+
appName,
|
|
2205
|
+
orgId,
|
|
2206
|
+
expiresIn
|
|
2207
|
+
});
|
|
2208
|
+
return result?.url || null;
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
console.error("Failed to get signed URL:", error);
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
}, [supabase, appName, orgId]);
|
|
2214
|
+
const handleDeleteFile = useCallback5(async (path) => {
|
|
2215
|
+
try {
|
|
2216
|
+
const result = await deleteFile(supabase, path);
|
|
2217
|
+
if (result.success) {
|
|
2218
|
+
await refreshFiles();
|
|
2219
|
+
}
|
|
2220
|
+
return result;
|
|
2221
|
+
} catch (error) {
|
|
2222
|
+
return {
|
|
2223
|
+
success: false,
|
|
2224
|
+
error: error instanceof Error ? error.message : "Delete failed"
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
}, [supabase]);
|
|
2228
|
+
const handleArchiveFile = useCallback5(async (path) => {
|
|
2229
|
+
try {
|
|
2230
|
+
const result = await archiveFile(supabase, path, { appName, orgId });
|
|
2231
|
+
if (result.success) {
|
|
2232
|
+
await refreshFiles();
|
|
2233
|
+
}
|
|
2234
|
+
return result;
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
return {
|
|
2237
|
+
success: false,
|
|
2238
|
+
error: error instanceof Error ? error.message : "Archive failed"
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
}, [supabase, appName, orgId]);
|
|
2242
|
+
const handleListFiles = useCallback5(async (options = {}) => {
|
|
2243
|
+
setIsListing(true);
|
|
2244
|
+
setListError(null);
|
|
2245
|
+
try {
|
|
2246
|
+
const listOptions = {
|
|
2247
|
+
appName,
|
|
2248
|
+
orgId,
|
|
2249
|
+
...options
|
|
2250
|
+
};
|
|
2251
|
+
const result = await listFiles(supabase, listOptions);
|
|
2252
|
+
setFiles(result.files);
|
|
2253
|
+
return result;
|
|
2254
|
+
} catch (error) {
|
|
2255
|
+
const errorMessage = error instanceof Error ? error.message : "List failed";
|
|
2256
|
+
setListError(errorMessage);
|
|
2257
|
+
return { files: [], totalCount: 0, hasMore: false };
|
|
2258
|
+
} finally {
|
|
2259
|
+
setIsListing(false);
|
|
2260
|
+
}
|
|
2261
|
+
}, [supabase, appName, orgId]);
|
|
2262
|
+
const refreshFiles = useCallback5(async () => {
|
|
2263
|
+
await handleListFiles();
|
|
2264
|
+
}, [handleListFiles]);
|
|
2265
|
+
return {
|
|
2266
|
+
// Upload
|
|
2267
|
+
uploadFile: handleUploadFile,
|
|
2268
|
+
isUploading,
|
|
2269
|
+
uploadError,
|
|
2270
|
+
// URLs
|
|
2271
|
+
getPublicUrl: handleGetPublicUrl,
|
|
2272
|
+
getSignedUrl: handleGetSignedUrl,
|
|
2273
|
+
getFileUrl: handleGetPublicUrl,
|
|
2274
|
+
// Alias for getPublicUrl
|
|
2275
|
+
// File management
|
|
2276
|
+
deleteFile: handleDeleteFile,
|
|
2277
|
+
archiveFile: handleArchiveFile,
|
|
2278
|
+
// Listing
|
|
2279
|
+
listFiles: handleListFiles,
|
|
2280
|
+
isListing,
|
|
2281
|
+
listError,
|
|
2282
|
+
isLoading: isListing,
|
|
2283
|
+
// Alias for isListing
|
|
2284
|
+
error: listError,
|
|
2285
|
+
// Alias for listError
|
|
2286
|
+
// State
|
|
2287
|
+
files,
|
|
2288
|
+
refreshFiles
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
function useFileUpload({ supabase, appName, orgId }) {
|
|
2292
|
+
const [uploadProgress, setUploadProgress] = useState9(0);
|
|
2293
|
+
const [isUploading, setIsUploading] = useState9(false);
|
|
2294
|
+
const [uploadError, setUploadError] = useState9(null);
|
|
2295
|
+
const uploadWithProgress = useCallback5(async (file, options = {}) => {
|
|
2296
|
+
setIsUploading(true);
|
|
2297
|
+
setUploadProgress(0);
|
|
2298
|
+
setUploadError(null);
|
|
2299
|
+
try {
|
|
2300
|
+
const progressInterval = setInterval(() => {
|
|
2301
|
+
setUploadProgress((prev) => Math.min(prev + 10, 90));
|
|
2302
|
+
}, 100);
|
|
2303
|
+
const uploadOptions = {
|
|
2304
|
+
appName,
|
|
2305
|
+
orgId,
|
|
2306
|
+
isPublic: false,
|
|
2307
|
+
...options
|
|
2308
|
+
};
|
|
2309
|
+
const result = await uploadFile(supabase, file, uploadOptions);
|
|
2310
|
+
clearInterval(progressInterval);
|
|
2311
|
+
setUploadProgress(100);
|
|
2312
|
+
if (!result.success) {
|
|
2313
|
+
setUploadError(result.error || "Upload failed");
|
|
2314
|
+
}
|
|
2315
|
+
return result;
|
|
2316
|
+
} catch (error) {
|
|
2317
|
+
const errorMessage = error instanceof Error ? error.message : "Upload failed";
|
|
2318
|
+
setUploadError(errorMessage);
|
|
2319
|
+
return {
|
|
2320
|
+
success: false,
|
|
2321
|
+
error: errorMessage
|
|
2322
|
+
};
|
|
2323
|
+
} finally {
|
|
2324
|
+
setIsUploading(false);
|
|
2325
|
+
setTimeout(() => setUploadProgress(0), 1e3);
|
|
2326
|
+
}
|
|
2327
|
+
}, [supabase, appName, orgId]);
|
|
2328
|
+
return {
|
|
2329
|
+
uploadWithProgress,
|
|
2330
|
+
uploadProgress,
|
|
2331
|
+
isUploading,
|
|
2332
|
+
uploadError
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/components/FileUpload/FileUpload.tsx
|
|
2337
|
+
import { useCallback as useCallback6, useState as useState10 } from "react";
|
|
2338
|
+
init_Button();
|
|
2339
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2340
|
+
function FileUpload({
|
|
2341
|
+
supabase,
|
|
2342
|
+
appName,
|
|
2343
|
+
orgId,
|
|
2344
|
+
onUploadComplete,
|
|
2345
|
+
onUploadStart,
|
|
2346
|
+
accept = "*/*",
|
|
2347
|
+
maxSize,
|
|
2348
|
+
multiple = false,
|
|
2349
|
+
disabled = false,
|
|
2350
|
+
className = "",
|
|
2351
|
+
children
|
|
2352
|
+
}) {
|
|
2353
|
+
const [dragActive, setDragActive] = useState10(false);
|
|
2354
|
+
const [selectedFiles, setSelectedFiles] = useState10([]);
|
|
2355
|
+
const [validationErrors, setValidationErrors] = useState10([]);
|
|
2356
|
+
const { uploadWithProgress, uploadProgress, isUploading, uploadError } = useFileUpload({
|
|
2357
|
+
supabase,
|
|
2358
|
+
appName,
|
|
2359
|
+
orgId
|
|
2360
|
+
});
|
|
2361
|
+
const validateFiles = useCallback6((files) => {
|
|
2362
|
+
const errors = [];
|
|
2363
|
+
files.forEach((file, index) => {
|
|
2364
|
+
const sizeValidation = validateFileSize(file);
|
|
2365
|
+
if (!sizeValidation.isValid) {
|
|
2366
|
+
errors.push(`File ${index + 1}: ${sizeValidation.error}`);
|
|
2367
|
+
}
|
|
2368
|
+
if (maxSize && file.size > maxSize) {
|
|
2369
|
+
const fileMB = Math.round(file.size / (1024 * 1024));
|
|
2370
|
+
const maxMB = Math.round(maxSize / (1024 * 1024));
|
|
2371
|
+
errors.push(`File ${index + 1}: Size (${fileMB}MB) exceeds limit (${maxMB}MB)`);
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
return errors;
|
|
2375
|
+
}, [maxSize]);
|
|
2376
|
+
const handleFileSelect = useCallback6((files) => {
|
|
2377
|
+
if (!files) return;
|
|
2378
|
+
const fileArray = Array.from(files);
|
|
2379
|
+
const errors = validateFiles(fileArray);
|
|
2380
|
+
setValidationErrors(errors);
|
|
2381
|
+
setSelectedFiles(fileArray);
|
|
2382
|
+
}, [validateFiles]);
|
|
2383
|
+
const handleDrag = useCallback6((e) => {
|
|
2384
|
+
e.preventDefault();
|
|
2385
|
+
e.stopPropagation();
|
|
2386
|
+
if (e.type === "dragenter" || e.type === "dragover") {
|
|
2387
|
+
setDragActive(true);
|
|
2388
|
+
} else if (e.type === "dragleave") {
|
|
2389
|
+
setDragActive(false);
|
|
2390
|
+
}
|
|
2391
|
+
}, []);
|
|
2392
|
+
const handleDrop = useCallback6((e) => {
|
|
2393
|
+
e.preventDefault();
|
|
2394
|
+
e.stopPropagation();
|
|
2395
|
+
setDragActive(false);
|
|
2396
|
+
if (disabled) return;
|
|
2397
|
+
const files = e.dataTransfer.files;
|
|
2398
|
+
handleFileSelect(files);
|
|
2399
|
+
}, [disabled, handleFileSelect]);
|
|
2400
|
+
const handleFileInputChange = useCallback6((e) => {
|
|
2401
|
+
handleFileSelect(e.target.files);
|
|
2402
|
+
}, [handleFileSelect]);
|
|
2403
|
+
const handleUpload = useCallback6(async () => {
|
|
2404
|
+
if (selectedFiles.length === 0 || validationErrors.length > 0) return;
|
|
2405
|
+
onUploadStart?.();
|
|
2406
|
+
for (const file of selectedFiles) {
|
|
2407
|
+
const result = await uploadWithProgress(file);
|
|
2408
|
+
onUploadComplete?.(result);
|
|
2409
|
+
}
|
|
2410
|
+
setSelectedFiles([]);
|
|
2411
|
+
setValidationErrors([]);
|
|
2412
|
+
}, [selectedFiles, validationErrors, uploadWithProgress, onUploadComplete, onUploadStart]);
|
|
2413
|
+
const handleClear = useCallback6(() => {
|
|
2414
|
+
setSelectedFiles([]);
|
|
2415
|
+
setValidationErrors([]);
|
|
2416
|
+
}, []);
|
|
2417
|
+
const canUpload = selectedFiles.length > 0 && validationErrors.length === 0 && !isUploading && !disabled;
|
|
2418
|
+
return /* @__PURE__ */ jsxs14("div", { className: `file-upload ${className}`, children: [
|
|
2419
|
+
/* @__PURE__ */ jsxs14(
|
|
2420
|
+
"div",
|
|
2421
|
+
{
|
|
2422
|
+
className: `file-upload__dropzone ${dragActive ? "file-upload__dropzone--active" : ""} ${disabled ? "file-upload__dropzone--disabled" : ""}`,
|
|
2423
|
+
onDragEnter: handleDrag,
|
|
2424
|
+
onDragLeave: handleDrag,
|
|
2425
|
+
onDragOver: handleDrag,
|
|
2426
|
+
onDrop: handleDrop,
|
|
2427
|
+
children: [
|
|
2428
|
+
/* @__PURE__ */ jsx18(
|
|
2429
|
+
"input",
|
|
2430
|
+
{
|
|
2431
|
+
type: "file",
|
|
2432
|
+
accept,
|
|
2433
|
+
multiple,
|
|
2434
|
+
onChange: handleFileInputChange,
|
|
2435
|
+
disabled,
|
|
2436
|
+
className: "file-upload__input"
|
|
2437
|
+
}
|
|
2438
|
+
),
|
|
2439
|
+
children || /* @__PURE__ */ jsxs14("div", { className: "file-upload__content", children: [
|
|
2440
|
+
/* @__PURE__ */ jsx18("p", { className: "file-upload__text", children: dragActive ? "Drop files here" : "Drag and drop files here or click to select" }),
|
|
2441
|
+
/* @__PURE__ */ jsx18(
|
|
2442
|
+
Button,
|
|
2443
|
+
{
|
|
2444
|
+
type: "button",
|
|
2445
|
+
variant: "outline",
|
|
2446
|
+
disabled,
|
|
2447
|
+
onClick: () => document.querySelector(".file-upload__input")?.click(),
|
|
2448
|
+
children: "Select Files"
|
|
2449
|
+
}
|
|
2450
|
+
)
|
|
2451
|
+
] })
|
|
2452
|
+
]
|
|
2453
|
+
}
|
|
2454
|
+
),
|
|
2455
|
+
selectedFiles.length > 0 && /* @__PURE__ */ jsxs14("div", { className: "file-upload__files", children: [
|
|
2456
|
+
/* @__PURE__ */ jsx18("h4", { className: "file-upload__files-title", children: "Selected Files:" }),
|
|
2457
|
+
/* @__PURE__ */ jsx18("ul", { className: "file-upload__files-list", children: selectedFiles.map((file, index) => /* @__PURE__ */ jsxs14("li", { className: "file-upload__file-item", children: [
|
|
2458
|
+
/* @__PURE__ */ jsx18("span", { className: "file-upload__file-name", children: file.name }),
|
|
2459
|
+
/* @__PURE__ */ jsx18("span", { className: "file-upload__file-size", children: formatFileSize(file.size) })
|
|
2460
|
+
] }, index)) })
|
|
2461
|
+
] }),
|
|
2462
|
+
validationErrors.length > 0 && /* @__PURE__ */ jsx18(Alert, { variant: "destructive", className: "file-upload__errors", children: /* @__PURE__ */ jsx18("ul", { children: validationErrors.map((error, index) => /* @__PURE__ */ jsx18("li", { children: error }, index)) }) }),
|
|
2463
|
+
isUploading && /* @__PURE__ */ jsxs14("div", { className: "file-upload__progress", children: [
|
|
2464
|
+
/* @__PURE__ */ jsx18(Progress, { value: uploadProgress, max: 100 }),
|
|
2465
|
+
/* @__PURE__ */ jsxs14("p", { className: "file-upload__progress-text", children: [
|
|
2466
|
+
"Uploading... ",
|
|
2467
|
+
uploadProgress,
|
|
2468
|
+
"%"
|
|
2469
|
+
] })
|
|
2470
|
+
] }),
|
|
2471
|
+
uploadError && /* @__PURE__ */ jsx18(Alert, { variant: "destructive", className: "file-upload__error", children: uploadError }),
|
|
2472
|
+
selectedFiles.length > 0 && /* @__PURE__ */ jsxs14("div", { className: "file-upload__actions", children: [
|
|
2473
|
+
/* @__PURE__ */ jsx18(
|
|
2474
|
+
Button,
|
|
2475
|
+
{
|
|
2476
|
+
onClick: handleUpload,
|
|
2477
|
+
disabled: !canUpload,
|
|
2478
|
+
className: "file-upload__upload-btn",
|
|
2479
|
+
children: isUploading ? "Uploading..." : `Upload ${selectedFiles.length} file${selectedFiles.length > 1 ? "s" : ""}`
|
|
2480
|
+
}
|
|
2481
|
+
),
|
|
2482
|
+
/* @__PURE__ */ jsx18(
|
|
2483
|
+
Button,
|
|
2484
|
+
{
|
|
2485
|
+
variant: "outline",
|
|
2486
|
+
onClick: handleClear,
|
|
2487
|
+
disabled: isUploading,
|
|
2488
|
+
className: "file-upload__clear-btn",
|
|
2489
|
+
children: "Clear"
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
] })
|
|
2493
|
+
] });
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
// src/components/Table/Table.tsx
|
|
2497
|
+
init_cn();
|
|
2498
|
+
import * as React16 from "react";
|
|
2499
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
2500
|
+
var Table = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2501
|
+
"table",
|
|
2502
|
+
{
|
|
2503
|
+
ref,
|
|
2504
|
+
className: cn("w-full caption-bottom text-sm", className),
|
|
2505
|
+
...props
|
|
2506
|
+
}
|
|
2507
|
+
));
|
|
2508
|
+
Table.displayName = "Table";
|
|
2509
|
+
var TableHeader = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19("thead", { ref, className: cn("[&_tr]:border-b", className), ...props }));
|
|
2510
|
+
TableHeader.displayName = "TableHeader";
|
|
2511
|
+
var TableBody = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2512
|
+
"tbody",
|
|
2513
|
+
{
|
|
2514
|
+
ref,
|
|
2515
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
2516
|
+
...props
|
|
2517
|
+
}
|
|
2518
|
+
));
|
|
2519
|
+
TableBody.displayName = "TableBody";
|
|
2520
|
+
var TableFooter = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2521
|
+
"tfoot",
|
|
2522
|
+
{
|
|
2523
|
+
ref,
|
|
2524
|
+
className: cn(
|
|
2525
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
2526
|
+
className
|
|
2527
|
+
),
|
|
2528
|
+
...props
|
|
2529
|
+
}
|
|
2530
|
+
));
|
|
2531
|
+
TableFooter.displayName = "TableFooter";
|
|
2532
|
+
var TableRow = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2533
|
+
"tr",
|
|
2534
|
+
{
|
|
2535
|
+
ref,
|
|
2536
|
+
className: cn(
|
|
2537
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
2538
|
+
className
|
|
2539
|
+
),
|
|
2540
|
+
...props
|
|
2541
|
+
}
|
|
2542
|
+
));
|
|
2543
|
+
TableRow.displayName = "TableRow";
|
|
2544
|
+
var TableHead = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2545
|
+
"th",
|
|
2546
|
+
{
|
|
2547
|
+
ref,
|
|
2548
|
+
className: cn(
|
|
2549
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
2550
|
+
className
|
|
2551
|
+
),
|
|
2552
|
+
...props
|
|
2553
|
+
}
|
|
2554
|
+
));
|
|
2555
|
+
TableHead.displayName = "TableHead";
|
|
2556
|
+
var TableCell = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2557
|
+
"td",
|
|
2558
|
+
{
|
|
2559
|
+
ref,
|
|
2560
|
+
className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className),
|
|
2561
|
+
...props
|
|
2562
|
+
}
|
|
2563
|
+
));
|
|
2564
|
+
TableCell.displayName = "TableCell";
|
|
2565
|
+
var TableCaption = React16.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx19(
|
|
2566
|
+
"caption",
|
|
2567
|
+
{
|
|
2568
|
+
ref,
|
|
2569
|
+
className: cn("mt-4 text-sm text-muted-foreground", className),
|
|
2570
|
+
...props
|
|
2571
|
+
}
|
|
2572
|
+
));
|
|
2573
|
+
TableCaption.displayName = "TableCaption";
|
|
2574
|
+
|
|
2575
|
+
// src/components/PublicLayout/EventLogo.tsx
|
|
2576
|
+
import { useMemo as useMemo6 } from "react";
|
|
2577
|
+
import { jsx as jsx20 } from "react/jsx-runtime";
|
|
2578
|
+
var sizeClasses = {
|
|
2579
|
+
xs: "h-4 w-4 text-xs",
|
|
2580
|
+
sm: "h-6 w-6 text-sm",
|
|
2581
|
+
md: "h-8 w-8 text-base",
|
|
2582
|
+
lg: "h-12 w-12 text-lg",
|
|
2583
|
+
xl: "h-16 w-16 text-xl",
|
|
2584
|
+
"2xl": "h-20 w-20 text-2xl"
|
|
2585
|
+
};
|
|
2586
|
+
function defaultGenerateFallbackText(eventName) {
|
|
2587
|
+
if (!eventName) return "EV";
|
|
2588
|
+
return eventName.split(" ").map((word) => word.charAt(0).toUpperCase()).join("").substring(0, 3);
|
|
2589
|
+
}
|
|
2590
|
+
function EventLogo({
|
|
2591
|
+
eventId,
|
|
2592
|
+
eventName,
|
|
2593
|
+
organisationId,
|
|
2594
|
+
size = "md",
|
|
2595
|
+
className = "",
|
|
2596
|
+
showFallback = true,
|
|
2597
|
+
generateFallbackText = defaultGenerateFallbackText,
|
|
2598
|
+
validateImage = true,
|
|
2599
|
+
loadingComponent: LoadingComponent,
|
|
2600
|
+
errorComponent: ErrorComponent
|
|
2601
|
+
}) {
|
|
2602
|
+
let isPublicPage = false;
|
|
2603
|
+
try {
|
|
2604
|
+
const { isPublicPage: publicContext } = usePublicPageContext();
|
|
2605
|
+
isPublicPage = publicContext === true;
|
|
2606
|
+
} catch {
|
|
2607
|
+
isPublicPage = false;
|
|
2608
|
+
}
|
|
2609
|
+
const shouldAvoidAuthContext = useMemo6(() => {
|
|
2610
|
+
if (isPublicPage) return true;
|
|
2611
|
+
try {
|
|
2612
|
+
(init_UnifiedAuthProvider(), __toCommonJS(UnifiedAuthProvider_exports)).useUnifiedAuth();
|
|
2613
|
+
return false;
|
|
2614
|
+
} catch {
|
|
2615
|
+
return true;
|
|
2616
|
+
}
|
|
2617
|
+
}, [isPublicPage]);
|
|
2618
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2619
|
+
const hasValidOrganisationId = organisationId && uuidRegex.test(organisationId);
|
|
2620
|
+
const { supabase } = usePublicPageContext();
|
|
2621
|
+
if (!supabase) {
|
|
2622
|
+
return /* @__PURE__ */ jsx20("div", { className: `${sizeClasses[size]} ${className}`.trim(), title: `${eventName} logo (Supabase not configured)`, children: eventName ? defaultGenerateFallbackText(eventName) : "EV" });
|
|
2623
|
+
}
|
|
2624
|
+
const {
|
|
2625
|
+
logoUrl,
|
|
2626
|
+
fallbackText,
|
|
2627
|
+
isLoading,
|
|
2628
|
+
error
|
|
2629
|
+
} = usePublicEventLogo(
|
|
2630
|
+
eventId,
|
|
2631
|
+
eventName,
|
|
2632
|
+
organisationId,
|
|
2633
|
+
// Always pass organisationId, let the hook handle validation
|
|
2634
|
+
{
|
|
2635
|
+
validateImage,
|
|
2636
|
+
generateFallbackText,
|
|
2637
|
+
supabase
|
|
2638
|
+
}
|
|
2639
|
+
);
|
|
2640
|
+
const sizeClass = useMemo6(() => sizeClasses[size], [size]);
|
|
2641
|
+
const combinedClasses = useMemo6(() => {
|
|
2642
|
+
const baseClasses = "flex items-center justify-center bg-gray-100 text-gray-600 font-semibold rounded";
|
|
2643
|
+
return `${baseClasses} ${sizeClass} ${className}`.trim();
|
|
2644
|
+
}, [sizeClass, className]);
|
|
2645
|
+
if (!hasValidOrganisationId && !isLoading && !logoUrl && showFallback) {
|
|
2646
|
+
return /* @__PURE__ */ jsx20("div", { className: combinedClasses, title: `${eventName} logo (invalid organisation ID)`, children: fallbackText });
|
|
2647
|
+
}
|
|
2648
|
+
if (isLoading) {
|
|
2649
|
+
if (LoadingComponent) {
|
|
2650
|
+
return /* @__PURE__ */ jsx20(LoadingComponent, {});
|
|
2651
|
+
}
|
|
2652
|
+
return /* @__PURE__ */ jsx20("div", { className: `${combinedClasses} animate-pulse`, children: /* @__PURE__ */ jsx20("div", { className: "w-3/4 h-3/4 bg-gray-300 rounded" }) });
|
|
2653
|
+
}
|
|
2654
|
+
if (error) {
|
|
2655
|
+
if (ErrorComponent) {
|
|
2656
|
+
return /* @__PURE__ */ jsx20(ErrorComponent, { error });
|
|
2657
|
+
}
|
|
2658
|
+
if (showFallback) {
|
|
2659
|
+
return /* @__PURE__ */ jsx20("div", { className: combinedClasses, title: `${eventName} (logo unavailable)`, children: fallbackText });
|
|
2660
|
+
}
|
|
2661
|
+
return null;
|
|
2662
|
+
}
|
|
2663
|
+
if (!logoUrl) {
|
|
2664
|
+
if (showFallback) {
|
|
2665
|
+
return /* @__PURE__ */ jsx20("div", { className: combinedClasses, title: `${eventName} logo`, children: fallbackText });
|
|
2666
|
+
}
|
|
2667
|
+
return null;
|
|
2668
|
+
}
|
|
2669
|
+
return /* @__PURE__ */ jsx20(
|
|
2670
|
+
"img",
|
|
2671
|
+
{
|
|
2672
|
+
src: logoUrl,
|
|
2673
|
+
alt: `${eventName} logo`,
|
|
2674
|
+
className: `${sizeClass} ${className}`.trim(),
|
|
2675
|
+
onError: (e) => {
|
|
2676
|
+
const target = e.target;
|
|
2677
|
+
target.style.display = "none";
|
|
2678
|
+
const fallback = document.createElement("div");
|
|
2679
|
+
fallback.className = combinedClasses;
|
|
2680
|
+
fallback.textContent = fallbackText;
|
|
2681
|
+
fallback.title = `${eventName} logo`;
|
|
2682
|
+
target.parentNode?.insertBefore(fallback, target.nextSibling);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
function EventLogoCompact(props) {
|
|
2688
|
+
return /* @__PURE__ */ jsx20(
|
|
2689
|
+
EventLogo,
|
|
2690
|
+
{
|
|
2691
|
+
...props,
|
|
2692
|
+
size: "sm",
|
|
2693
|
+
className: `${props.className || ""} rounded-sm`
|
|
2694
|
+
}
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
function EventLogoLarge(props) {
|
|
2698
|
+
return /* @__PURE__ */ jsx20(
|
|
2699
|
+
EventLogo,
|
|
2700
|
+
{
|
|
2701
|
+
...props,
|
|
2702
|
+
size: "xl",
|
|
2703
|
+
className: `${props.className || ""} rounded-lg`
|
|
2704
|
+
}
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// src/components/PublicLayout/PublicPageHeader.tsx
|
|
2709
|
+
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2710
|
+
function PublicPageHeader({
|
|
2711
|
+
event,
|
|
2712
|
+
eventCode,
|
|
2713
|
+
title,
|
|
2714
|
+
description,
|
|
2715
|
+
showEventLogo = true,
|
|
2716
|
+
showAppLogo = true,
|
|
2717
|
+
className = "",
|
|
2718
|
+
children,
|
|
2719
|
+
customAppLogo,
|
|
2720
|
+
customEventLogo
|
|
2721
|
+
}) {
|
|
2722
|
+
const { appName } = useAppConfig();
|
|
2723
|
+
const headerClasses = `bg-white border-b border-gray-200 ${className}`.trim();
|
|
2724
|
+
return /* @__PURE__ */ jsx21("header", { className: headerClasses, children: /* @__PURE__ */ jsxs15("div", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto", children: [
|
|
2725
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-between py-4", children: [
|
|
2726
|
+
showAppLogo && /* @__PURE__ */ jsx21("div", { className: "flex-shrink-0", children: customAppLogo || /* @__PURE__ */ jsxs15("div", { className: "flex items-center", children: [
|
|
2727
|
+
/* @__PURE__ */ jsx21(
|
|
2728
|
+
"img",
|
|
2729
|
+
{
|
|
2730
|
+
className: "h-8 w-auto",
|
|
2731
|
+
src: `/${appName.toLowerCase()}_logo_wide.svg`,
|
|
2732
|
+
alt: appName
|
|
2733
|
+
}
|
|
2734
|
+
),
|
|
2735
|
+
/* @__PURE__ */ jsx21("span", { className: "ml-2 text-lg font-semibold text-gray-900", children: appName })
|
|
2736
|
+
] }) }),
|
|
2737
|
+
showEventLogo && /* @__PURE__ */ jsx21("div", { className: "flex-shrink-0", children: customEventLogo || /* @__PURE__ */ jsx21(
|
|
2738
|
+
EventLogo,
|
|
2739
|
+
{
|
|
2740
|
+
eventId: event.event_id,
|
|
2741
|
+
eventName: event.event_name,
|
|
2742
|
+
organisationId: event.organisation_id,
|
|
2743
|
+
size: "md",
|
|
2744
|
+
className: "h-12 w-12"
|
|
2745
|
+
}
|
|
2746
|
+
) })
|
|
2747
|
+
] }),
|
|
2748
|
+
/* @__PURE__ */ jsx21("div", { className: "pb-4", children: /* @__PURE__ */ jsxs15("div", { className: "text-center", children: [
|
|
2749
|
+
/* @__PURE__ */ jsx21("h1", { className: "text-3xl font-bold text-gray-900 mb-2", children: event.event_name }),
|
|
2750
|
+
event.event_venue && /* @__PURE__ */ jsx21("p", { className: "text-md text-gray-500 mb-4", children: event.event_venue }),
|
|
2751
|
+
title && /* @__PURE__ */ jsxs15("div", { className: "mt-6", children: [
|
|
2752
|
+
/* @__PURE__ */ jsx21("h2", { className: "text-2xl font-semibold text-gray-800 mb-2", children: title }),
|
|
2753
|
+
description && /* @__PURE__ */ jsx21("p", { className: "text-lg text-gray-600 max-w-3xl mx-auto", children: description })
|
|
2754
|
+
] }),
|
|
2755
|
+
children && /* @__PURE__ */ jsx21("div", { className: "mt-4", children })
|
|
2756
|
+
] }) })
|
|
2757
|
+
] }) });
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// src/components/PublicLayout/PublicPageFooter.tsx
|
|
2761
|
+
init_cn();
|
|
2762
|
+
import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2763
|
+
function PublicPageFooter({
|
|
2764
|
+
event,
|
|
2765
|
+
companyName = "Solvera Solutions Pty Ltd",
|
|
2766
|
+
year = (/* @__PURE__ */ new Date()).getFullYear(),
|
|
2767
|
+
links,
|
|
2768
|
+
className = "",
|
|
2769
|
+
logo,
|
|
2770
|
+
copyright,
|
|
2771
|
+
children
|
|
2772
|
+
}) {
|
|
2773
|
+
const copyrightText = copyright || `\xA9 Copyright 2022\u2013${year} all rights reserved, ${companyName}.`;
|
|
2774
|
+
return /* @__PURE__ */ jsx22("footer", { className: cn("mt-8 py-6 flex justify-center border-t border-border bg-main-100", className), children: /* @__PURE__ */ jsxs16("section", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto text-center", children: [
|
|
2775
|
+
logo && /* @__PURE__ */ jsx22("img", { src: logo, alt: "Logo", className: "h-8 w-auto" }),
|
|
2776
|
+
children && /* @__PURE__ */ jsx22(Fragment5, { children }),
|
|
2777
|
+
/* @__PURE__ */ jsx22("span", { className: "text-muted-foreground", children: copyrightText }),
|
|
2778
|
+
links && links.length > 0 && /* @__PURE__ */ jsx22("ul", { className: "flex gap-4 mt-2 md:mt-0", children: links.map((link, index) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22("a", { href: link.href, className: "text-muted-foreground hover:text-foreground", children: link.label }) }, index)) })
|
|
2779
|
+
] }) });
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
// src/components/PublicLayout/PublicLoadingSpinner.tsx
|
|
2783
|
+
import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2784
|
+
var sizeClasses2 = {
|
|
2785
|
+
sm: "h-4 w-4",
|
|
2786
|
+
md: "h-8 w-8",
|
|
2787
|
+
lg: "h-12 w-12",
|
|
2788
|
+
xl: "h-16 w-16"
|
|
2789
|
+
};
|
|
2790
|
+
function PublicLoadingSpinner({
|
|
2791
|
+
message = "Loading...",
|
|
2792
|
+
size = "md",
|
|
2793
|
+
className = "",
|
|
2794
|
+
showLogo = true,
|
|
2795
|
+
customMessage,
|
|
2796
|
+
centered = true
|
|
2797
|
+
}) {
|
|
2798
|
+
const sizeClass = sizeClasses2[size];
|
|
2799
|
+
const displayMessage = customMessage || message;
|
|
2800
|
+
const content = /* @__PURE__ */ jsxs17("div", { className: `flex flex-col items-center ${className}`, children: [
|
|
2801
|
+
showLogo && /* @__PURE__ */ jsx23("div", { className: "mb-4", children: /* @__PURE__ */ jsx23(
|
|
2802
|
+
"img",
|
|
2803
|
+
{
|
|
2804
|
+
className: "h-8 w-auto",
|
|
2805
|
+
src: "/pace_logo_wide.svg",
|
|
2806
|
+
alt: "PACE Core"
|
|
2807
|
+
}
|
|
2808
|
+
) }),
|
|
2809
|
+
/* @__PURE__ */ jsxs17("div", { className: "relative", children: [
|
|
2810
|
+
/* @__PURE__ */ jsx23(
|
|
2811
|
+
"div",
|
|
2812
|
+
{
|
|
2813
|
+
className: `${sizeClass} border-2 border-gray-200 border-t-blue-600 rounded-full animate-spin`,
|
|
2814
|
+
role: "status",
|
|
2815
|
+
"aria-label": "Loading"
|
|
2816
|
+
}
|
|
2817
|
+
),
|
|
2818
|
+
/* @__PURE__ */ jsx23("span", { className: "sr-only", children: displayMessage })
|
|
2819
|
+
] }),
|
|
2820
|
+
displayMessage && /* @__PURE__ */ jsx23("p", { className: "mt-4 text-sm text-gray-600 text-center", children: displayMessage })
|
|
2821
|
+
] });
|
|
2822
|
+
if (centered) {
|
|
2823
|
+
return /* @__PURE__ */ jsx23("div", { className: "min-h-screen bg-white flex items-center justify-center", children: /* @__PURE__ */ jsx23("div", { className: "max-w-md mx-auto px-4", children: content }) });
|
|
2824
|
+
}
|
|
2825
|
+
return content;
|
|
2826
|
+
}
|
|
2827
|
+
function PublicLoadingSpinnerFullPage({
|
|
2828
|
+
message = "Loading event details...",
|
|
2829
|
+
eventName,
|
|
2830
|
+
className = ""
|
|
2831
|
+
}) {
|
|
2832
|
+
return /* @__PURE__ */ jsx23("div", { className: `min-h-screen bg-white flex items-center justify-center ${className}`, children: /* @__PURE__ */ jsxs17("div", { className: "max-w-md mx-auto text-center px-4", children: [
|
|
2833
|
+
/* @__PURE__ */ jsx23("div", { className: "mb-8", children: /* @__PURE__ */ jsx23(
|
|
2834
|
+
"img",
|
|
2835
|
+
{
|
|
2836
|
+
className: "h-12 w-auto mx-auto",
|
|
2837
|
+
src: "/pace_logo_wide.svg",
|
|
2838
|
+
alt: "PACE Core"
|
|
2839
|
+
}
|
|
2840
|
+
) }),
|
|
2841
|
+
eventName && /* @__PURE__ */ jsx23("h1", { className: "text-2xl font-bold text-gray-900 mb-4", children: eventName }),
|
|
2842
|
+
/* @__PURE__ */ jsx23("div", { className: "relative mb-6", children: /* @__PURE__ */ jsx23(
|
|
2843
|
+
"div",
|
|
2844
|
+
{
|
|
2845
|
+
className: "h-12 w-12 border-4 border-gray-200 border-t-blue-600 rounded-full animate-spin mx-auto",
|
|
2846
|
+
role: "status",
|
|
2847
|
+
"aria-label": "Loading"
|
|
2848
|
+
}
|
|
2849
|
+
) }),
|
|
2850
|
+
/* @__PURE__ */ jsx23("p", { className: "text-lg text-gray-600", children: message }),
|
|
2851
|
+
/* @__PURE__ */ jsxs17("div", { className: "mt-4 flex justify-center space-x-1", children: [
|
|
2852
|
+
/* @__PURE__ */ jsx23("div", { className: "h-2 w-2 bg-blue-600 rounded-full animate-bounce", style: { animationDelay: "0ms" } }),
|
|
2853
|
+
/* @__PURE__ */ jsx23("div", { className: "h-2 w-2 bg-blue-600 rounded-full animate-bounce", style: { animationDelay: "150ms" } }),
|
|
2854
|
+
/* @__PURE__ */ jsx23("div", { className: "h-2 w-2 bg-blue-600 rounded-full animate-bounce", style: { animationDelay: "300ms" } })
|
|
2855
|
+
] })
|
|
2856
|
+
] }) });
|
|
2857
|
+
}
|
|
2858
|
+
function PublicLoadingSkeleton({
|
|
2859
|
+
lines = 3,
|
|
2860
|
+
className = ""
|
|
2861
|
+
}) {
|
|
2862
|
+
return /* @__PURE__ */ jsx23("div", { className: `animate-pulse ${className}`, children: Array.from({ length: lines }).map((_, index) => /* @__PURE__ */ jsx23(
|
|
2863
|
+
"div",
|
|
2864
|
+
{
|
|
2865
|
+
className: `h-4 bg-gray-200 rounded mb-2 ${index === lines - 1 ? "w-3/4" : "w-full"}`
|
|
2866
|
+
},
|
|
2867
|
+
index
|
|
2868
|
+
)) });
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// src/components/PublicLayout/PublicPageLayout.tsx
|
|
2872
|
+
import { useMemo as useMemo7 } from "react";
|
|
2873
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2874
|
+
function PublicPageLayout({
|
|
2875
|
+
eventCode,
|
|
2876
|
+
children,
|
|
2877
|
+
event = null,
|
|
2878
|
+
showFooter = true,
|
|
2879
|
+
className = "",
|
|
2880
|
+
errorFallback: ErrorFallback,
|
|
2881
|
+
loadingFallback: LoadingFallback = PublicLoadingSpinner,
|
|
2882
|
+
customHeader,
|
|
2883
|
+
customFooter,
|
|
2884
|
+
showValidationErrors = true
|
|
2885
|
+
}) {
|
|
2886
|
+
const isLoading = false;
|
|
2887
|
+
const error = null;
|
|
2888
|
+
const refetch = async () => {
|
|
2889
|
+
};
|
|
2890
|
+
const layoutClasses = useMemo7(() => {
|
|
2891
|
+
const baseClasses = "min-h-screen bg-white flex flex-col";
|
|
2892
|
+
return `${baseClasses} ${className}`.trim();
|
|
2893
|
+
}, [className]);
|
|
2894
|
+
if (isLoading) {
|
|
2895
|
+
return /* @__PURE__ */ jsx24("div", { className: layoutClasses, children: /* @__PURE__ */ jsx24("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsx24(LoadingFallback, {}) }) });
|
|
2896
|
+
}
|
|
2897
|
+
if (error && showValidationErrors) {
|
|
2898
|
+
return /* @__PURE__ */ jsx24("div", { className: layoutClasses, children: /* @__PURE__ */ jsx24("div", { className: "flex-1 flex items-center justify-center", children: ErrorFallback ? /* @__PURE__ */ jsx24(ErrorFallback, { error, retry: refetch }) : /* @__PURE__ */ jsxs18("div", { className: "text-center p-8", children: [
|
|
2899
|
+
/* @__PURE__ */ jsx24("h1", { className: "text-2xl font-bold text-gray-900 mb-4", children: "Event Not Found" }),
|
|
2900
|
+
/* @__PURE__ */ jsxs18("p", { className: "text-gray-600 mb-6", children: [
|
|
2901
|
+
'The event code "',
|
|
2902
|
+
eventCode,
|
|
2903
|
+
'" is invalid or the event is not available for public viewing.'
|
|
2904
|
+
] }),
|
|
2905
|
+
/* @__PURE__ */ jsx24(
|
|
2906
|
+
"button",
|
|
2907
|
+
{
|
|
2908
|
+
onClick: refetch,
|
|
2909
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
|
2910
|
+
children: "Try Again"
|
|
2911
|
+
}
|
|
2912
|
+
)
|
|
2913
|
+
] }) }) });
|
|
2914
|
+
}
|
|
2915
|
+
if (!event) {
|
|
2916
|
+
return /* @__PURE__ */ jsx24("div", { className: layoutClasses, children: /* @__PURE__ */ jsx24("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ jsxs18("div", { className: "text-center p-8", children: [
|
|
2917
|
+
/* @__PURE__ */ jsx24("h1", { className: "text-2xl font-bold text-gray-900 mb-4", children: "Event Not Available" }),
|
|
2918
|
+
/* @__PURE__ */ jsx24("p", { className: "text-gray-600 mb-6", children: "This event is not available for public viewing." }),
|
|
2919
|
+
/* @__PURE__ */ jsx24(
|
|
2920
|
+
"button",
|
|
2921
|
+
{
|
|
2922
|
+
onClick: refetch,
|
|
2923
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors",
|
|
2924
|
+
children: "Try Again"
|
|
2925
|
+
}
|
|
2926
|
+
)
|
|
2927
|
+
] }) }) });
|
|
2928
|
+
}
|
|
2929
|
+
return /* @__PURE__ */ jsx24(PublicErrorBoundary, { children: /* @__PURE__ */ jsxs18("div", { className: layoutClasses, children: [
|
|
2930
|
+
customHeader || /* @__PURE__ */ jsx24(
|
|
2931
|
+
PublicPageHeader,
|
|
2932
|
+
{
|
|
2933
|
+
event,
|
|
2934
|
+
eventCode
|
|
2935
|
+
}
|
|
2936
|
+
),
|
|
2937
|
+
/* @__PURE__ */ jsx24("main", { className: "px-4 w-[min(var(--app-width),100%)] mx-auto py-8", children }),
|
|
2938
|
+
showFooter && (customFooter || /* @__PURE__ */ jsx24(PublicPageFooter, { event }))
|
|
2939
|
+
] }) });
|
|
2940
|
+
}
|
|
2941
|
+
function usePublicPageContext2() {
|
|
2942
|
+
return {
|
|
2943
|
+
eventCode: null,
|
|
2944
|
+
eventId: null,
|
|
2945
|
+
event: null,
|
|
2946
|
+
isLoading: false,
|
|
2947
|
+
error: null,
|
|
2948
|
+
refetch: async () => {
|
|
2949
|
+
},
|
|
2950
|
+
isPublicPage: true
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// src/components/PublicLayout/PublicPageDebugger.tsx
|
|
2955
|
+
import { useEffect as useEffect5 } from "react";
|
|
2956
|
+
import { jsx as jsx25, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2957
|
+
function PublicPageDebugger({ enabled = true, label = "PublicPage" }) {
|
|
2958
|
+
useEffect5(() => {
|
|
2959
|
+
if (!enabled) return;
|
|
2960
|
+
console.log(`[${label}] Component mounted`);
|
|
2961
|
+
try {
|
|
2962
|
+
const { isPublicPage } = usePublicPageContext();
|
|
2963
|
+
console.log(`[${label}] Public page context detected:`, isPublicPage);
|
|
2964
|
+
} catch (error) {
|
|
2965
|
+
console.warn(`[${label}] Not in PublicPageProvider context:`, error instanceof Error ? error.message : String(error));
|
|
2966
|
+
}
|
|
2967
|
+
try {
|
|
2968
|
+
const { isAuthenticated } = (init_UnifiedAuthProvider(), __toCommonJS(UnifiedAuthProvider_exports)).useUnifiedAuth();
|
|
2969
|
+
console.warn(`[${label}] AUTHENTICATION CONTEXT DETECTED! This should not happen in public pages.`);
|
|
2970
|
+
console.warn(`[${label}] isAuthenticated:`, isAuthenticated);
|
|
2971
|
+
} catch (error) {
|
|
2972
|
+
console.log(`[${label}] No authentication context detected (good for public pages)`);
|
|
2973
|
+
}
|
|
2974
|
+
try {
|
|
2975
|
+
const { selectedOrganisation } = (init_OrganisationProvider(), __toCommonJS(OrganisationProvider_exports)).useOrganisations();
|
|
2976
|
+
console.warn(`[${label}] ORGANISATION CONTEXT DETECTED! This should not happen in public pages.`);
|
|
2977
|
+
console.warn(`[${label}] selectedOrganisation:`, selectedOrganisation);
|
|
2978
|
+
} catch (error) {
|
|
2979
|
+
console.log(`[${label}] No organisation context detected (good for public pages)`);
|
|
2980
|
+
}
|
|
2981
|
+
try {
|
|
2982
|
+
const { events } = (init_EventProvider(), __toCommonJS(EventProvider_exports)).useEvents();
|
|
2983
|
+
console.warn(`[${label}] EVENT CONTEXT DETECTED! This should not happen in public pages.`);
|
|
2984
|
+
console.warn(`[${label}] events:`, events);
|
|
2985
|
+
} catch (error) {
|
|
2986
|
+
console.log(`[${label}] No event context detected (good for public pages)`);
|
|
2987
|
+
}
|
|
2988
|
+
const supabaseUrl = import.meta.env?.VITE_SUPABASE_URL || process.env.VITE_SUPABASE_URL;
|
|
2989
|
+
const supabaseKey = import.meta.env?.VITE_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
|
|
2990
|
+
console.log(`[${label}] Environment variables:`, {
|
|
2991
|
+
supabaseUrl: supabaseUrl ? "Present" : "Missing",
|
|
2992
|
+
supabaseKey: supabaseKey ? "Present" : "Missing"
|
|
2993
|
+
});
|
|
2994
|
+
return () => {
|
|
2995
|
+
console.log(`[${label}] Component unmounted`);
|
|
2996
|
+
};
|
|
2997
|
+
}, [enabled, label]);
|
|
2998
|
+
if (!enabled) return null;
|
|
2999
|
+
return /* @__PURE__ */ jsxs19("div", { style: {
|
|
3000
|
+
position: "fixed",
|
|
3001
|
+
top: 0,
|
|
3002
|
+
right: 0,
|
|
3003
|
+
background: "rgba(0,0,0,0.8)",
|
|
3004
|
+
color: "white",
|
|
3005
|
+
padding: "8px",
|
|
3006
|
+
fontSize: "12px",
|
|
3007
|
+
zIndex: 9999,
|
|
3008
|
+
fontFamily: "monospace"
|
|
3009
|
+
}, children: [
|
|
3010
|
+
/* @__PURE__ */ jsx25("div", { children: "Public Page Debugger" }),
|
|
3011
|
+
/* @__PURE__ */ jsx25("div", { children: "Check console for context analysis" })
|
|
3012
|
+
] });
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
// src/components/PublicLayout/PublicPageDiagnostic.tsx
|
|
3016
|
+
import { useEffect as useEffect6, useState as useState11 } from "react";
|
|
3017
|
+
import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3018
|
+
function PublicPageDiagnostic({ enabled = true, label = "PublicPage" }) {
|
|
3019
|
+
const [diagnostics, setDiagnostics] = useState11({
|
|
3020
|
+
hasPublicPageContext: false,
|
|
3021
|
+
hasAuthContext: false,
|
|
3022
|
+
hasOrgContext: false,
|
|
3023
|
+
hasEventContext: false,
|
|
3024
|
+
hasEnvironmentVars: false,
|
|
3025
|
+
routeParams: null
|
|
3026
|
+
});
|
|
3027
|
+
useEffect6(() => {
|
|
3028
|
+
if (!enabled) return;
|
|
3029
|
+
const runDiagnostics = () => {
|
|
3030
|
+
const newDiagnostics = {
|
|
3031
|
+
hasPublicPageContext: false,
|
|
3032
|
+
hasAuthContext: false,
|
|
3033
|
+
hasOrgContext: false,
|
|
3034
|
+
hasEventContext: false,
|
|
3035
|
+
hasEnvironmentVars: false,
|
|
3036
|
+
routeParams: null
|
|
3037
|
+
};
|
|
3038
|
+
try {
|
|
3039
|
+
const { isPublicPage } = usePublicPageContext();
|
|
3040
|
+
newDiagnostics.hasPublicPageContext = isPublicPage === true;
|
|
3041
|
+
} catch (error) {
|
|
3042
|
+
newDiagnostics.hasPublicPageContext = false;
|
|
3043
|
+
}
|
|
3044
|
+
try {
|
|
3045
|
+
const { isAuthenticated } = (init_UnifiedAuthProvider(), __toCommonJS(UnifiedAuthProvider_exports)).useUnifiedAuth();
|
|
3046
|
+
newDiagnostics.hasAuthContext = true;
|
|
3047
|
+
} catch (error) {
|
|
3048
|
+
newDiagnostics.hasAuthContext = false;
|
|
3049
|
+
}
|
|
3050
|
+
try {
|
|
3051
|
+
const { selectedOrganisation } = (init_OrganisationProvider(), __toCommonJS(OrganisationProvider_exports)).useOrganisations();
|
|
3052
|
+
newDiagnostics.hasOrgContext = true;
|
|
3053
|
+
} catch (error) {
|
|
3054
|
+
newDiagnostics.hasOrgContext = false;
|
|
3055
|
+
}
|
|
3056
|
+
try {
|
|
3057
|
+
const { events } = (init_EventProvider(), __toCommonJS(EventProvider_exports)).useEvents();
|
|
3058
|
+
newDiagnostics.hasEventContext = true;
|
|
3059
|
+
} catch (error) {
|
|
3060
|
+
newDiagnostics.hasEventContext = false;
|
|
3061
|
+
}
|
|
3062
|
+
const supabaseUrl = import.meta.env?.VITE_SUPABASE_URL || process.env.VITE_SUPABASE_URL;
|
|
3063
|
+
const supabaseKey = import.meta.env?.VITE_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
|
|
3064
|
+
newDiagnostics.hasEnvironmentVars = !!(supabaseUrl && supabaseKey);
|
|
3065
|
+
try {
|
|
3066
|
+
const { useParams } = __require("react-router-dom");
|
|
3067
|
+
const params = useParams();
|
|
3068
|
+
newDiagnostics.routeParams = params;
|
|
3069
|
+
} catch (error) {
|
|
3070
|
+
newDiagnostics.routeParams = null;
|
|
3071
|
+
}
|
|
3072
|
+
setDiagnostics(newDiagnostics);
|
|
3073
|
+
console.group(`\u{1F50D} [${label}] Public Page Diagnostics`);
|
|
3074
|
+
console.log("\u2705 Public Page Context:", newDiagnostics.hasPublicPageContext ? "PRESENT" : "MISSING");
|
|
3075
|
+
console.log("\u274C Auth Context:", newDiagnostics.hasAuthContext ? "PRESENT (BAD!)" : "ABSENT (GOOD!)");
|
|
3076
|
+
console.log("\u274C Org Context:", newDiagnostics.hasOrgContext ? "PRESENT (BAD!)" : "ABSENT (GOOD!)");
|
|
3077
|
+
console.log("\u274C Event Context:", newDiagnostics.hasEventContext ? "PRESENT (BAD!)" : "ABSENT (GOOD!)");
|
|
3078
|
+
console.log("\u2705 Environment Vars:", newDiagnostics.hasEnvironmentVars ? "PRESENT" : "MISSING");
|
|
3079
|
+
console.log("\u2705 Route Params:", newDiagnostics.routeParams);
|
|
3080
|
+
console.groupEnd();
|
|
3081
|
+
if (newDiagnostics.hasAuthContext || newDiagnostics.hasOrgContext || newDiagnostics.hasEventContext) {
|
|
3082
|
+
console.error(`\u{1F6A8} [${label}] PROBLEM DETECTED: Public page is inside authentication context!`);
|
|
3083
|
+
console.error("\u{1F527} SOLUTION: Move public routes outside of UnifiedAuthProvider, OrganisationProvider, and EventProvider");
|
|
3084
|
+
console.error("\u{1F4D6} See: packages/core/docs/emergency-public-pages-fix.md");
|
|
3085
|
+
} else if (!newDiagnostics.hasPublicPageContext) {
|
|
3086
|
+
console.warn(`\u26A0\uFE0F [${label}] WARNING: Not in PublicPageProvider context`);
|
|
3087
|
+
console.warn("\u{1F527} SOLUTION: Wrap your public page component in <PublicPageProvider>");
|
|
3088
|
+
} else if (!newDiagnostics.hasEnvironmentVars) {
|
|
3089
|
+
console.warn(`\u26A0\uFE0F [${label}] WARNING: Missing environment variables`);
|
|
3090
|
+
console.warn("\u{1F527} SOLUTION: Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY in your environment");
|
|
3091
|
+
} else {
|
|
3092
|
+
console.log(`\u2705 [${label}] All diagnostics passed! Public page should work correctly.`);
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
runDiagnostics();
|
|
3096
|
+
}, [enabled, label]);
|
|
3097
|
+
if (!enabled) return null;
|
|
3098
|
+
return /* @__PURE__ */ jsxs20("div", { style: {
|
|
3099
|
+
position: "fixed",
|
|
3100
|
+
top: 0,
|
|
3101
|
+
left: 0,
|
|
3102
|
+
background: "rgba(0,0,0,0.9)",
|
|
3103
|
+
color: "white",
|
|
3104
|
+
padding: "12px",
|
|
3105
|
+
fontSize: "11px",
|
|
3106
|
+
zIndex: 9999,
|
|
3107
|
+
fontFamily: "monospace",
|
|
3108
|
+
maxWidth: "300px",
|
|
3109
|
+
borderRadius: "0 0 8px 0"
|
|
3110
|
+
}, children: [
|
|
3111
|
+
/* @__PURE__ */ jsx26("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "\u{1F50D} Public Page Diagnostics" }),
|
|
3112
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3113
|
+
"Public Context: ",
|
|
3114
|
+
diagnostics.hasPublicPageContext ? "\u2705" : "\u274C"
|
|
3115
|
+
] }),
|
|
3116
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3117
|
+
"Auth Context: ",
|
|
3118
|
+
diagnostics.hasAuthContext ? "\u274C BAD" : "\u2705 GOOD"
|
|
3119
|
+
] }),
|
|
3120
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3121
|
+
"Org Context: ",
|
|
3122
|
+
diagnostics.hasOrgContext ? "\u274C BAD" : "\u2705 GOOD"
|
|
3123
|
+
] }),
|
|
3124
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3125
|
+
"Event Context: ",
|
|
3126
|
+
diagnostics.hasEventContext ? "\u274C BAD" : "\u2705 GOOD"
|
|
3127
|
+
] }),
|
|
3128
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3129
|
+
"Env Vars: ",
|
|
3130
|
+
diagnostics.hasEnvironmentVars ? "\u2705" : "\u274C"
|
|
3131
|
+
] }),
|
|
3132
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
3133
|
+
"Route Params: ",
|
|
3134
|
+
diagnostics.routeParams ? "\u2705" : "\u274C"
|
|
3135
|
+
] }),
|
|
3136
|
+
/* @__PURE__ */ jsx26("div", { style: { marginTop: "8px", fontSize: "10px", opacity: 0.8 }, children: "Check console for detailed analysis" })
|
|
3137
|
+
] });
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// src/components/PublicLayout/PublicPageContextChecker.tsx
|
|
3141
|
+
import { useEffect as useEffect7 } from "react";
|
|
3142
|
+
import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3143
|
+
function PublicPageContextChecker({ enabled = true, label = "PublicPage" }) {
|
|
3144
|
+
useEffect7(() => {
|
|
3145
|
+
if (!enabled) return;
|
|
3146
|
+
console.group(`\u{1F6A8} [${label}] PUBLIC PAGE CONTEXT CHECK`);
|
|
3147
|
+
try {
|
|
3148
|
+
const { isAuthenticated } = (init_UnifiedAuthProvider(), __toCommonJS(UnifiedAuthProvider_exports)).useUnifiedAuth();
|
|
3149
|
+
console.error(`\u274C [${label}] AUTHENTICATION CONTEXT DETECTED!`);
|
|
3150
|
+
console.error(`\u274C [${label}] isAuthenticated:`, isAuthenticated);
|
|
3151
|
+
console.error(`\u274C [${label}] This public page is inside UnifiedAuthProvider - THIS IS WRONG!`);
|
|
3152
|
+
console.error(`\u274C [${label}] SOLUTION: Move public routes outside of authentication providers`);
|
|
3153
|
+
} catch (error) {
|
|
3154
|
+
console.log(`\u2705 [${label}] No authentication context detected (GOOD!)`);
|
|
3155
|
+
}
|
|
3156
|
+
try {
|
|
3157
|
+
const { selectedOrganisation } = (init_OrganisationProvider(), __toCommonJS(OrganisationProvider_exports)).useOrganisations();
|
|
3158
|
+
console.error(`\u274C [${label}] ORGANISATION CONTEXT DETECTED!`);
|
|
3159
|
+
console.error(`\u274C [${label}] selectedOrganisation:`, selectedOrganisation);
|
|
3160
|
+
console.error(`\u274C [${label}] This public page is inside OrganisationProvider - THIS IS WRONG!`);
|
|
3161
|
+
console.error(`\u274C [${label}] SOLUTION: Move public routes outside of authentication providers`);
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
console.log(`\u2705 [${label}] No organisation context detected (GOOD!)`);
|
|
3164
|
+
}
|
|
3165
|
+
try {
|
|
3166
|
+
const { events } = (init_EventProvider(), __toCommonJS(EventProvider_exports)).useEvents();
|
|
3167
|
+
console.error(`\u274C [${label}] EVENT CONTEXT DETECTED!`);
|
|
3168
|
+
console.error(`\u274C [${label}] events:`, events);
|
|
3169
|
+
console.error(`\u274C [${label}] This public page is inside EventProvider - THIS IS WRONG!`);
|
|
3170
|
+
console.error(`\u274C [${label}] SOLUTION: Move public routes outside of authentication providers`);
|
|
3171
|
+
} catch (error) {
|
|
3172
|
+
console.log(`\u2705 [${label}] No event context detected (GOOD!)`);
|
|
3173
|
+
}
|
|
3174
|
+
try {
|
|
3175
|
+
const { isPublicPage } = usePublicPageContext();
|
|
3176
|
+
if (isPublicPage) {
|
|
3177
|
+
console.log(`\u2705 [${label}] Public page context detected (GOOD!)`);
|
|
3178
|
+
} else {
|
|
3179
|
+
console.warn(`\u26A0\uFE0F [${label}] Not in PublicPageProvider context`);
|
|
3180
|
+
console.warn(`\u26A0\uFE0F [${label}] SOLUTION: Wrap your public page in <PublicPageProvider>`);
|
|
3181
|
+
}
|
|
3182
|
+
} catch (error) {
|
|
3183
|
+
console.warn(`\u26A0\uFE0F [${label}] Not in PublicPageProvider context`);
|
|
3184
|
+
console.warn(`\u26A0\uFE0F [${label}] SOLUTION: Wrap your public page in <PublicPageProvider>`);
|
|
3185
|
+
}
|
|
3186
|
+
console.groupEnd();
|
|
3187
|
+
console.group(`\u{1F4D6} [${label}] IMMEDIATE ACTION REQUIRED`);
|
|
3188
|
+
console.log(`If you see any \u274C errors above, your public page is inside authentication context.`);
|
|
3189
|
+
console.log(`This will cause infinite loading loops and authentication errors.`);
|
|
3190
|
+
console.log(``);
|
|
3191
|
+
console.log(`\u{1F527} SOLUTION:`);
|
|
3192
|
+
console.log(`1. Check your main App.tsx file`);
|
|
3193
|
+
console.log(`2. Make sure public routes are completely separate from authentication providers`);
|
|
3194
|
+
console.log(`3. Follow the architecture in: packages/core/docs/public-pages-guide.md`);
|
|
3195
|
+
console.log(``);
|
|
3196
|
+
console.log(`\u2705 CORRECT ARCHITECTURE:`);
|
|
3197
|
+
console.log(`<BrowserRouter>`);
|
|
3198
|
+
console.log(` <Routes>`);
|
|
3199
|
+
console.log(` <Route path="/events/*" element={<PublicPageApp />} />`);
|
|
3200
|
+
console.log(` <Route path="/*" element={<AuthenticatedApp />} />`);
|
|
3201
|
+
console.log(` </Routes>`);
|
|
3202
|
+
console.log(`</BrowserRouter>`);
|
|
3203
|
+
console.groupEnd();
|
|
3204
|
+
}, [enabled, label]);
|
|
3205
|
+
if (!enabled) return null;
|
|
3206
|
+
return /* @__PURE__ */ jsxs21("div", { style: {
|
|
3207
|
+
position: "fixed",
|
|
3208
|
+
top: 0,
|
|
3209
|
+
left: 0,
|
|
3210
|
+
background: "rgba(220, 38, 38, 0.95)",
|
|
3211
|
+
color: "white",
|
|
3212
|
+
padding: "12px",
|
|
3213
|
+
fontSize: "12px",
|
|
3214
|
+
zIndex: 9999,
|
|
3215
|
+
fontFamily: "monospace",
|
|
3216
|
+
maxWidth: "400px",
|
|
3217
|
+
borderRadius: "0 0 8px 0",
|
|
3218
|
+
border: "2px solid #dc2626"
|
|
3219
|
+
}, children: [
|
|
3220
|
+
/* @__PURE__ */ jsx27("div", { style: { fontWeight: "bold", marginBottom: "8px" }, children: "\u{1F6A8} PUBLIC PAGE CONTEXT CHECK" }),
|
|
3221
|
+
/* @__PURE__ */ jsx27("div", { children: "Check console for authentication context analysis" }),
|
|
3222
|
+
/* @__PURE__ */ jsx27("div", { style: { marginTop: "8px", fontSize: "10px", opacity: 0.9 }, children: "If you see \u274C errors in console, your public page is inside auth context!" })
|
|
3223
|
+
] });
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
export {
|
|
3227
|
+
Label,
|
|
3228
|
+
Avatar,
|
|
3229
|
+
AvatarImage,
|
|
3230
|
+
AvatarFallback,
|
|
3231
|
+
Progress,
|
|
3232
|
+
ToastProvider,
|
|
3233
|
+
ToastViewport,
|
|
3234
|
+
Toast,
|
|
3235
|
+
ToastAction,
|
|
3236
|
+
ToastClose,
|
|
3237
|
+
ToastTitle,
|
|
3238
|
+
ToastDescription,
|
|
3239
|
+
useToast,
|
|
3240
|
+
Toaster,
|
|
3241
|
+
Form,
|
|
3242
|
+
LoginForm,
|
|
3243
|
+
EventSelector,
|
|
3244
|
+
PasswordChangeForm,
|
|
3245
|
+
UserMenu,
|
|
3246
|
+
NavigationMenu,
|
|
3247
|
+
Header,
|
|
3248
|
+
Footer,
|
|
3249
|
+
PaceAppLayout,
|
|
3250
|
+
PaceLoginPage,
|
|
3251
|
+
ErrorBoundary,
|
|
3252
|
+
OrganisationSelector,
|
|
3253
|
+
PasswordResetForm,
|
|
3254
|
+
FILE_SIZE_LIMITS,
|
|
3255
|
+
DEFAULT_FILE_SIZE_LIMIT,
|
|
3256
|
+
APP_PATH_MAPPING,
|
|
3257
|
+
STORAGE_CONFIG,
|
|
3258
|
+
getFileSizeLimit,
|
|
3259
|
+
validateFileSize,
|
|
3260
|
+
formatFileSize,
|
|
3261
|
+
generateFilePath,
|
|
3262
|
+
generateUniqueFileName,
|
|
3263
|
+
extractFileMetadata,
|
|
3264
|
+
uploadFile,
|
|
3265
|
+
getPublicUrl,
|
|
3266
|
+
getSignedUrl,
|
|
3267
|
+
deleteFile,
|
|
3268
|
+
listFiles,
|
|
3269
|
+
archiveFile,
|
|
3270
|
+
useStorage,
|
|
3271
|
+
useFileUpload,
|
|
3272
|
+
FileUpload,
|
|
3273
|
+
Table,
|
|
3274
|
+
TableHeader,
|
|
3275
|
+
TableBody,
|
|
3276
|
+
TableFooter,
|
|
3277
|
+
TableRow,
|
|
3278
|
+
TableHead,
|
|
3279
|
+
TableCell,
|
|
3280
|
+
TableCaption,
|
|
3281
|
+
EventLogo,
|
|
3282
|
+
EventLogoCompact,
|
|
3283
|
+
EventLogoLarge,
|
|
3284
|
+
PublicPageHeader,
|
|
3285
|
+
PublicPageFooter,
|
|
3286
|
+
PublicLoadingSpinner,
|
|
3287
|
+
PublicLoadingSpinnerFullPage,
|
|
3288
|
+
PublicLoadingSkeleton,
|
|
3289
|
+
PublicPageLayout,
|
|
3290
|
+
usePublicPageContext2 as usePublicPageContext,
|
|
3291
|
+
PublicPageDebugger,
|
|
3292
|
+
PublicPageDiagnostic,
|
|
3293
|
+
PublicPageContextChecker
|
|
3294
|
+
};
|
|
3295
|
+
//# sourceMappingURL=chunk-T3XIA4AJ.js.map
|