@jmruthers/pace-core 0.5.76 → 0.5.78
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 +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
- package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
- package/dist/chunk-2OGV6IRV.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
- package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
- package/dist/chunk-LRQ6RBJC.js.map +1 -0
- package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
- package/dist/chunk-MNJXXD6C.js.map +1 -0
- package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
- package/dist/chunk-PTR5PMPE.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
- package/dist/chunk-YVVGHRGI.js.map +1 -0
- package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
- package/dist/chunk-ZMNXIJP4.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +14 -18
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +21 -29
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +71 -221
- package/dist/rbac/index.js +15 -16
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +251 -269
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +6 -0
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +6 -0
- package/docs/best-practices/accessibility.md +6 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +6 -0
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +6 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +70 -13
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +309 -337
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/index.ts +7 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
- package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +12 -3
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +114 -16
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +6 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/Select/Select.tsx +1 -0
- package/src/components/examples/PermissionExample.tsx +173 -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/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +12 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +43 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/services/base/BaseService.ts +8 -0
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-K34IM5CT.js.map +0 -1
- package/dist/chunk-KHJS6VIA.js.map +0 -1
- package/dist/chunk-KK73ZB4E.js.map +0 -1
- package/dist/chunk-M5IWZRBT.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-Y6TXWPJO.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/TERMINOLOGY.md +0 -231
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/best-practices/performance-expansion.md +0 -473
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-templates.md +0 -539
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -376
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -964
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
- /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
- /package/docs/{app.css.example → styles/app.css.example} +0 -0
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
import React, { useEffect, useState } from 'react';
|
|
125
125
|
import { useNavigate } from 'react-router-dom';
|
|
126
126
|
import { useUnifiedAuth } from '../../providers';
|
|
127
|
+
import { isSuperAdmin } from '../../rbac/api';
|
|
127
128
|
import { LoginForm } from '../LoginForm';
|
|
128
129
|
import { Button, Input, Label } from '..';
|
|
129
130
|
|
|
@@ -132,6 +133,8 @@ export interface PaceLoginPageProps {
|
|
|
132
133
|
appName: string;
|
|
133
134
|
/** The path to redirect to upon successful login. Defaults to `/`. */
|
|
134
135
|
onSuccessRedirectPath?: string;
|
|
136
|
+
/** Whether to check app access using RBAC. Defaults to false. */
|
|
137
|
+
requireAppAccess?: boolean;
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
/**
|
|
@@ -141,6 +144,7 @@ export interface PaceLoginPageProps {
|
|
|
141
144
|
*
|
|
142
145
|
* Recent enhancements:
|
|
143
146
|
* - Role-based redirection: Admin users are automatically redirected
|
|
147
|
+
* - RBAC-based app access control: Checks if user has permission to access the app
|
|
144
148
|
* - Enhanced error handling with navigation error recovery
|
|
145
149
|
* - Dual loading states: auth loading + form submission loading
|
|
146
150
|
* - Error persistence: Auth errors are displayed below the form
|
|
@@ -159,33 +163,144 @@ export interface PaceLoginPageProps {
|
|
|
159
163
|
*/
|
|
160
164
|
export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
161
165
|
appName = 'Pace',
|
|
162
|
-
onSuccessRedirectPath = '/'
|
|
166
|
+
onSuccessRedirectPath = '/',
|
|
167
|
+
requireAppAccess = false
|
|
163
168
|
}) => {
|
|
164
|
-
const { signIn, isAuthenticated, isLoading, authError,
|
|
169
|
+
const { signIn, isAuthenticated, isLoading, authError, user, supabase } = useUnifiedAuth();
|
|
170
|
+
|
|
165
171
|
const navigate = useNavigate();
|
|
166
172
|
const [isSigningIn, setIsSigningIn] = useState(false);
|
|
173
|
+
const [accessError, setAccessError] = useState<string | null>(null);
|
|
174
|
+
const [isCheckingAccess, setIsCheckingAccess] = useState(false);
|
|
167
175
|
|
|
176
|
+
// Check app access after authentication using RBAC
|
|
168
177
|
useEffect(() => {
|
|
169
|
-
|
|
170
|
-
|
|
178
|
+
if (!requireAppAccess || !isAuthenticated || isLoading || !user || !supabase) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const checkAccess = async () => {
|
|
183
|
+
setIsCheckingAccess(true);
|
|
184
|
+
setAccessError(null);
|
|
185
|
+
|
|
171
186
|
try {
|
|
172
|
-
|
|
187
|
+
const userId = user.id;
|
|
188
|
+
console.log('[PaceLoginPage] Checking app access using RBAC:', { appName, userId });
|
|
189
|
+
|
|
190
|
+
// Step 1: Check if user is super admin (they have unrestricted access)
|
|
191
|
+
const superAdminCheck = await isSuperAdmin(userId);
|
|
192
|
+
|
|
193
|
+
if (superAdminCheck) {
|
|
194
|
+
console.log('[PaceLoginPage] User is super admin, granting access');
|
|
195
|
+
setIsCheckingAccess(false);
|
|
196
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 2: Get the app ID
|
|
201
|
+
const { data: appData, error: appError } = await supabase
|
|
202
|
+
.from('rbac_apps')
|
|
203
|
+
.select('id, name, is_active')
|
|
204
|
+
.eq('name', appName)
|
|
205
|
+
.eq('is_active', true)
|
|
206
|
+
.single();
|
|
207
|
+
|
|
208
|
+
if (appError || !appData) {
|
|
209
|
+
console.error('[PaceLoginPage] App not found:', appName, appError);
|
|
210
|
+
setAccessError(`Application "${appName}" is not configured. Please contact your administrator.`);
|
|
211
|
+
setIsCheckingAccess(false);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Step 3: Get all pages for this app
|
|
216
|
+
const { data: pagesData, error: pagesError } = await supabase
|
|
217
|
+
.from('rbac_app_pages')
|
|
218
|
+
.select('id, page_name')
|
|
219
|
+
.eq('app_id', appData.id);
|
|
220
|
+
|
|
221
|
+
if (pagesError || !pagesData || pagesData.length === 0) {
|
|
222
|
+
console.log('[PaceLoginPage] No pages configured for app:', appName);
|
|
223
|
+
setAccessError(`You do not have permission to access ${appName}. This application is currently unavailable. Please contact your administrator if you believe you should have access.`);
|
|
224
|
+
setIsCheckingAccess(false);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Step 4: Get user's first organisation
|
|
229
|
+
const { data: orgData } = await supabase
|
|
230
|
+
.from('rbac_organisation_roles')
|
|
231
|
+
.select('organisation_id')
|
|
232
|
+
.eq('user_id', userId)
|
|
233
|
+
.eq('status', 'active')
|
|
234
|
+
.is('revoked_at', null)
|
|
235
|
+
.limit(1)
|
|
236
|
+
.single();
|
|
237
|
+
|
|
238
|
+
if (!orgData) {
|
|
239
|
+
console.log('[PaceLoginPage] User has no organisation access');
|
|
240
|
+
setAccessError(`You do not have permission to access ${appName}. You are not assigned to any organisation. Please contact your administrator.`);
|
|
241
|
+
setIsCheckingAccess(false);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Step 5: Check if user has ANY read permission on ANY page in this app
|
|
246
|
+
// We check each page and see if user has the 'read' operation allowed
|
|
247
|
+
let hasAnyAccess = false;
|
|
248
|
+
for (const page of pagesData) {
|
|
249
|
+
const { data: hasPermission, error: permError } = await supabase
|
|
250
|
+
.rpc('rbac_check_permission_simplified', {
|
|
251
|
+
p_user_id: userId,
|
|
252
|
+
p_permission: `read:page.${page.page_name}`, // Permission format: operation:resource
|
|
253
|
+
p_organisation_id: orgData.organisation_id,
|
|
254
|
+
p_event_id: null,
|
|
255
|
+
p_app_id: appData.id,
|
|
256
|
+
p_page_id: page.page_name // Page name to resolve to UUID
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
console.log('[PaceLoginPage] Permission check for page:', page.page_name, { hasPermission, error: permError });
|
|
260
|
+
|
|
261
|
+
if (!permError && hasPermission === true) {
|
|
262
|
+
hasAnyAccess = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (hasAnyAccess) {
|
|
268
|
+
console.log('[PaceLoginPage] User has access to app');
|
|
269
|
+
setIsCheckingAccess(false);
|
|
270
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// No access - deny
|
|
275
|
+
console.log('[PaceLoginPage] Access denied - no permissions');
|
|
276
|
+
setAccessError(`You do not have permission to access ${appName}. This application is restricted to authorized users only. Please contact your administrator if you believe you should have access.`);
|
|
277
|
+
setIsCheckingAccess(false);
|
|
173
278
|
} catch (error) {
|
|
174
|
-
console.error('
|
|
279
|
+
console.error('[PaceLoginPage] Error checking app access:', error);
|
|
280
|
+
setAccessError('An error occurred while checking your permissions. Please try again or contact support.');
|
|
281
|
+
setIsCheckingAccess(false);
|
|
175
282
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
checkAccess();
|
|
286
|
+
}, [isAuthenticated, isLoading, user, supabase, appName, requireAppAccess, navigate, onSuccessRedirectPath]);
|
|
178
287
|
|
|
179
288
|
const handleSubmit = async (data: { email: string; password: string }) => {
|
|
180
289
|
setIsSigningIn(true);
|
|
290
|
+
setAccessError(null); // Clear previous access errors
|
|
291
|
+
|
|
181
292
|
try {
|
|
182
293
|
const { error } = await signIn(data.email, data.password);
|
|
183
294
|
|
|
184
295
|
if (!error) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
296
|
+
// Navigation will be handled by the useEffect that checks app access
|
|
297
|
+
// Don't navigate here if requireAppAccess is true
|
|
298
|
+
if (!requireAppAccess) {
|
|
299
|
+
try {
|
|
300
|
+
navigate(onSuccessRedirectPath, { replace: true });
|
|
301
|
+
} catch (navError) {
|
|
302
|
+
console.error('Navigation error after sign-in:', navError);
|
|
303
|
+
}
|
|
189
304
|
}
|
|
190
305
|
}
|
|
191
306
|
} finally {
|
|
@@ -215,7 +330,16 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
215
330
|
{authError.message}
|
|
216
331
|
</em>
|
|
217
332
|
)}
|
|
218
|
-
|
|
333
|
+
{accessError && (
|
|
334
|
+
<em className="mt-4 text-destructive text-center">
|
|
335
|
+
{accessError}
|
|
336
|
+
</em>
|
|
337
|
+
)}
|
|
338
|
+
{isCheckingAccess && (
|
|
339
|
+
<em className="mt-4 text-muted-foreground text-center">
|
|
340
|
+
Checking permissions...
|
|
341
|
+
</em>
|
|
342
|
+
)}
|
|
219
343
|
</main>
|
|
220
344
|
);
|
|
221
345
|
}
|
|
@@ -271,7 +271,7 @@ describe('[component] PublicPageHeader', () => {
|
|
|
271
271
|
<PublicPageHeader event={mockEvent} eventCode="EVENT123" />
|
|
272
272
|
);
|
|
273
273
|
|
|
274
|
-
const eventInfo = screen.getByRole('banner').querySelector('div
|
|
274
|
+
const eventInfo = screen.getByRole('banner').querySelector('div.pb-4');
|
|
275
275
|
expect(eventInfo).toHaveClass('pb-4');
|
|
276
276
|
});
|
|
277
277
|
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Permission System Example
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Components/Examples
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Example component demonstrating the hybrid permission system
|
|
8
|
+
* that supports both direct app access and event-based permissions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { useUnifiedAuth } from '../../providers/UnifiedAuthProvider';
|
|
13
|
+
import { useAppConfig } from '../../hooks/useAppConfig';
|
|
14
|
+
import { usePermissions, useAccessLevel } from '../../rbac/hooks';
|
|
15
|
+
import { AccessLevel } from '../../types/unified';
|
|
16
|
+
|
|
17
|
+
export function PermissionExample() {
|
|
18
|
+
const {
|
|
19
|
+
user,
|
|
20
|
+
isAuthenticated,
|
|
21
|
+
selectedEvent,
|
|
22
|
+
selectedOrganisation
|
|
23
|
+
} = useUnifiedAuth();
|
|
24
|
+
|
|
25
|
+
const { hasPermission } = usePermissions(user?.id || '', {
|
|
26
|
+
organisationId: selectedOrganisation?.id || '',
|
|
27
|
+
appId: undefined
|
|
28
|
+
});
|
|
29
|
+
const { accessLevel } = useAccessLevel(user?.id || '', {
|
|
30
|
+
organisationId: selectedOrganisation?.id || '',
|
|
31
|
+
appId: undefined
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Helper function to check if user has required access level
|
|
35
|
+
const hasAccessLevel = (requiredLevel: AccessLevel): boolean => {
|
|
36
|
+
const levelOrder = {
|
|
37
|
+
[AccessLevel.NONE]: 0,
|
|
38
|
+
[AccessLevel.VIEWER]: 1,
|
|
39
|
+
[AccessLevel.PARTICIPANT]: 2,
|
|
40
|
+
[AccessLevel.EDITOR]: 3,
|
|
41
|
+
[AccessLevel.PLANNER]: 4,
|
|
42
|
+
[AccessLevel.ADMIN]: 5,
|
|
43
|
+
[AccessLevel.SUPER_ADMIN]: 6,
|
|
44
|
+
[AccessLevel.SUPER]: 7
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return levelOrder[accessLevel] >= levelOrder[requiredLevel];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const { supportsDirectAccess, requiresEvent, isLoading, appName } = useAppConfig();
|
|
51
|
+
|
|
52
|
+
if (!isAuthenticated) {
|
|
53
|
+
return <div>Please log in to see permissions</div>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isLoading) {
|
|
57
|
+
return <div>Loading app configuration...</div>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className="p-6 max-w-2xl">
|
|
62
|
+
<h2>Permission System Example</h2>
|
|
63
|
+
|
|
64
|
+
{/* App Configuration Info */}
|
|
65
|
+
<div className="mb-6 p-4 bg-sec-100 rounded-lg">
|
|
66
|
+
<h3>App Configuration: {appName}</h3>
|
|
67
|
+
<div className="space-y-1">
|
|
68
|
+
<div>Supports Direct Access: {supportsDirectAccess ? '✅' : '❌'}</div>
|
|
69
|
+
<div>Requires Event: {requiresEvent ? '✅' : '❌'}</div>
|
|
70
|
+
<div>Current Access Level: {accessLevel}</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Event Selection */}
|
|
75
|
+
{requiresEvent && (
|
|
76
|
+
<div className="mb-6 p-4 border rounded-lg">
|
|
77
|
+
<h3>Event Context</h3>
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
<div>Selected Event: {selectedEvent?.event_id || 'None'}</div>
|
|
80
|
+
<div className="space-x-2">
|
|
81
|
+
<button
|
|
82
|
+
onClick={() => {/* Event selection logic would go here */}}
|
|
83
|
+
className="px-3 py-1 bg-main-500 text-main-50 rounded"
|
|
84
|
+
>
|
|
85
|
+
Select Event 123
|
|
86
|
+
</button>
|
|
87
|
+
<button
|
|
88
|
+
onClick={() => {/* Clear event logic would go here */}}
|
|
89
|
+
className="px-3 py-1 bg-sec-500 text-main-50 rounded"
|
|
90
|
+
>
|
|
91
|
+
Clear Event
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{/* Permission Examples */}
|
|
99
|
+
<div className="space-y-4">
|
|
100
|
+
<h3>Permission Examples</h3>
|
|
101
|
+
|
|
102
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
103
|
+
{/* Basic Permissions */}
|
|
104
|
+
<div className="p-4 border rounded-lg">
|
|
105
|
+
<h4>Basic Permissions</h4>
|
|
106
|
+
<div className="space-y-1 text-sm">
|
|
107
|
+
<div>Can Read: {hasPermission('read:app') ? '✅' : '❌'}</div>
|
|
108
|
+
<div>Can Write: {hasPermission('create:app') ? '✅' : '❌'}</div>
|
|
109
|
+
<div>Can Delete: {hasPermission('delete:app') ? '✅' : '❌'}</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Access Level Checks */}
|
|
114
|
+
<div className="p-4 border rounded-lg">
|
|
115
|
+
<h4>Access Level Checks</h4>
|
|
116
|
+
<div className="space-y-1 text-sm">
|
|
117
|
+
<div>Is Editor+: {hasAccessLevel(AccessLevel.EDITOR) ? '✅' : '❌'}</div>
|
|
118
|
+
<div>Is Planner+: {hasAccessLevel(AccessLevel.PLANNER) ? '✅' : '❌'}</div>
|
|
119
|
+
<div>Is Admin: {hasAccessLevel(AccessLevel.ADMIN) ? '✅' : '❌'}</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{/* Conditional Rendering Examples */}
|
|
125
|
+
<div className="p-4 border rounded-lg">
|
|
126
|
+
<h4>Conditional UI Elements</h4>
|
|
127
|
+
<div className="space-y-2">
|
|
128
|
+
{hasPermission('read:app') && (
|
|
129
|
+
<div className="p-2 bg-main-100 text-main-800 rounded">
|
|
130
|
+
✅ You can see this because you have read permission
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{hasPermission('create:app') && (
|
|
135
|
+
<button className="px-4 py-2 bg-main-500 text-main-50 rounded">
|
|
136
|
+
Edit (Available with write permission)
|
|
137
|
+
</button>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{hasAccessLevel(AccessLevel.ADMIN) && (
|
|
141
|
+
<div className="p-2 bg-acc-100 text-acc-800 rounded">
|
|
142
|
+
🔒 Admin-only content visible
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{!hasPermission('delete:app') && (
|
|
147
|
+
<div className="p-2 bg-acc-100 text-acc-800 rounded">
|
|
148
|
+
⚠️ Delete functionality not available for your access level
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{/* Status Summary */}
|
|
155
|
+
<div className="p-4 bg-main-50 rounded-lg">
|
|
156
|
+
<h4>Current Status</h4>
|
|
157
|
+
<div className="text-sm space-y-1">
|
|
158
|
+
<div>User: {user?.email}</div>
|
|
159
|
+
<div>App: {appName}</div>
|
|
160
|
+
<div>Event: {selectedEvent?.event_id || 'Direct Access'}</div>
|
|
161
|
+
<div>
|
|
162
|
+
Permission Model: {
|
|
163
|
+
selectedEvent?.event_id ? 'Event-based' :
|
|
164
|
+
supportsDirectAccess ? 'Direct Access' :
|
|
165
|
+
'No Access (requires event)'
|
|
166
|
+
}
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Correct Public Page Implementation
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Examples/PublicPages
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* This example shows the CORRECT way to implement public pages
|
|
8
|
+
* that are completely isolated from the authentication context.
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL: Public pages MUST be completely separate from the main app's
|
|
11
|
+
* authentication context. They cannot be rendered inside the same component
|
|
12
|
+
* tree as UnifiedAuthProvider, OrganisationProvider, or EventProvider.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
17
|
+
import {
|
|
18
|
+
PublicPageProvider,
|
|
19
|
+
PublicPageLayout,
|
|
20
|
+
PublicPageHeader,
|
|
21
|
+
PublicPageFooter,
|
|
22
|
+
EventLogo,
|
|
23
|
+
usePublicEvent,
|
|
24
|
+
usePublicRouteParams,
|
|
25
|
+
PublicLoadingSpinner
|
|
26
|
+
} from '../index';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* MAIN APPLICATION - This is your main app with authentication
|
|
30
|
+
*
|
|
31
|
+
* This should be completely separate from public pages.
|
|
32
|
+
* All authenticated routes go here.
|
|
33
|
+
*/
|
|
34
|
+
function MainApp() {
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<h1>Main Application (Requires Authentication)</h1>
|
|
38
|
+
<p>This is your main app with all the authenticated features.</p>
|
|
39
|
+
{/* Your main app content here */}
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* PUBLIC PAGE APPLICATION - This is completely separate
|
|
46
|
+
*
|
|
47
|
+
* This handles ALL public pages and is completely isolated
|
|
48
|
+
* from the main application's authentication context.
|
|
49
|
+
*/
|
|
50
|
+
function PublicPageApp() {
|
|
51
|
+
return (
|
|
52
|
+
<PublicPageProvider>
|
|
53
|
+
<Routes>
|
|
54
|
+
{/* All public routes go here */}
|
|
55
|
+
<Route path="/events/:eventCode/recipe-grid-report" element={<PublicRecipeGridReportPage />} />
|
|
56
|
+
<Route path="/events/:eventCode" element={<PublicEventPage />} />
|
|
57
|
+
<Route path="/public-info/:pageId" element={<PublicInfoPage />} />
|
|
58
|
+
</Routes>
|
|
59
|
+
</PublicPageProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* ROOT APPLICATION - This decides which app to render
|
|
65
|
+
*
|
|
66
|
+
* This is the key: public pages and authenticated pages
|
|
67
|
+
* are completely separate applications.
|
|
68
|
+
*/
|
|
69
|
+
function RootApp() {
|
|
70
|
+
return (
|
|
71
|
+
<BrowserRouter>
|
|
72
|
+
<Routes>
|
|
73
|
+
{/* Public routes - NO authentication context */}
|
|
74
|
+
<Route path="/events/*" element={<PublicPageApp />} />
|
|
75
|
+
<Route path="/public-info/*" element={<PublicPageApp />} />
|
|
76
|
+
|
|
77
|
+
{/* Authenticated routes - WITH authentication context */}
|
|
78
|
+
<Route path="/*" element={<MainApp />} />
|
|
79
|
+
</Routes>
|
|
80
|
+
</BrowserRouter>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Public Recipe Grid Report Page
|
|
86
|
+
*
|
|
87
|
+
* This demonstrates the correct usage pattern.
|
|
88
|
+
* Notice: NO authentication context is triggered.
|
|
89
|
+
*/
|
|
90
|
+
function PublicRecipeGridReportPage() {
|
|
91
|
+
// Step 1: Extract event code from URL
|
|
92
|
+
const { eventCode } = usePublicRouteParams({ fetchEventData: false });
|
|
93
|
+
|
|
94
|
+
// Step 2: Fetch event data
|
|
95
|
+
const { event, isLoading, error, refetch } = usePublicEvent(eventCode || '');
|
|
96
|
+
|
|
97
|
+
// Step 3: Handle loading state
|
|
98
|
+
if (isLoading) {
|
|
99
|
+
return (
|
|
100
|
+
<PublicLoadingSpinner
|
|
101
|
+
message="Loading recipe grid report..."
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 4: Handle error state
|
|
107
|
+
if (error) {
|
|
108
|
+
return (
|
|
109
|
+
<div className="min-h-screen bg-white flex items-center justify-center">
|
|
110
|
+
<div className="max-w-md mx-auto text-center px-4">
|
|
111
|
+
<div className="mb-6">
|
|
112
|
+
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
|
|
113
|
+
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
114
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
115
|
+
</svg>
|
|
116
|
+
</div>
|
|
117
|
+
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
|
118
|
+
Recipe Grid Report Not Found
|
|
119
|
+
</h1>
|
|
120
|
+
<p className="text-gray-600 mb-6">
|
|
121
|
+
The event code "{eventCode}" is invalid or the recipe grid report is not available for public viewing.
|
|
122
|
+
</p>
|
|
123
|
+
<button
|
|
124
|
+
onClick={refetch}
|
|
125
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
|
126
|
+
>
|
|
127
|
+
Try Again
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Step 5: Handle missing event
|
|
136
|
+
if (!event) {
|
|
137
|
+
return (
|
|
138
|
+
<div className="min-h-screen bg-white flex items-center justify-center">
|
|
139
|
+
<div className="max-w-md mx-auto text-center px-4">
|
|
140
|
+
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
|
141
|
+
Recipe Grid Report Not Available
|
|
142
|
+
</h1>
|
|
143
|
+
<p className="text-gray-600 mb-6">
|
|
144
|
+
This recipe grid report is not available for public viewing.
|
|
145
|
+
</p>
|
|
146
|
+
<button
|
|
147
|
+
onClick={refetch}
|
|
148
|
+
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
|
149
|
+
>
|
|
150
|
+
Try Again
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Step 6: Render the public page
|
|
158
|
+
return (
|
|
159
|
+
<PublicPageLayout eventCode={eventCode || ''} event={event}>
|
|
160
|
+
<PublicPageHeader
|
|
161
|
+
event={event}
|
|
162
|
+
eventCode={eventCode || ''}
|
|
163
|
+
title="Recipe Grid Report"
|
|
164
|
+
description="Public recipe grid report for this event"
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<main className="max-w-6xl mx-auto px-4 py-8">
|
|
168
|
+
{/* Recipe Grid Report Content */}
|
|
169
|
+
<div className="mb-12">
|
|
170
|
+
<div className="bg-green-50 border border-green-200 rounded-lg p-6">
|
|
171
|
+
<h3 className="font-semibold text-green-900 mb-2">Recipe Grid Report</h3>
|
|
172
|
+
<p className="text-green-800">
|
|
173
|
+
This is where your recipe grid report content would go.
|
|
174
|
+
The public page is now working correctly without authentication context conflicts.
|
|
175
|
+
</p>
|
|
176
|
+
<div className="mt-4 text-sm text-green-700">
|
|
177
|
+
<p><strong>Event Code:</strong> {eventCode}</p>
|
|
178
|
+
<p><strong>Event ID:</strong> {event.event_id}</p>
|
|
179
|
+
<p><strong>Event Name:</strong> {event.event_name}</p>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{/* Event Information */}
|
|
185
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
|
|
186
|
+
<div className="lg:col-span-2 space-y-6">
|
|
187
|
+
<div>
|
|
188
|
+
<h2 className="text-2xl font-bold text-gray-900 mb-4">Event Information</h2>
|
|
189
|
+
<div className="bg-gray-50 rounded-lg p-6 space-y-4">
|
|
190
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
191
|
+
<div>
|
|
192
|
+
<h3 className="font-semibold text-gray-700">Date</h3>
|
|
193
|
+
<p className="text-gray-900">
|
|
194
|
+
{event.event_date ? new Date(event.event_date).toLocaleDateString('en-AU', {
|
|
195
|
+
weekday: 'long',
|
|
196
|
+
year: 'numeric',
|
|
197
|
+
month: 'long',
|
|
198
|
+
day: 'numeric'
|
|
199
|
+
}) : 'TBA'}
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
<div>
|
|
203
|
+
<h3 className="font-semibold text-gray-700">Venue</h3>
|
|
204
|
+
<p className="text-gray-900">{event.event_venue || 'TBA'}</p>
|
|
205
|
+
</div>
|
|
206
|
+
<div>
|
|
207
|
+
<h3 className="font-semibold text-gray-700">Participants</h3>
|
|
208
|
+
<p className="text-gray-900">{event.event_participants || 'TBA'}</p>
|
|
209
|
+
</div>
|
|
210
|
+
<div>
|
|
211
|
+
<h3 className="font-semibold text-gray-700">Event Code</h3>
|
|
212
|
+
<p className="text-gray-900 font-mono">{event.event_code}</p>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Event Logo */}
|
|
220
|
+
<div className="flex justify-center lg:justify-start">
|
|
221
|
+
<div className="text-center">
|
|
222
|
+
<EventLogo
|
|
223
|
+
eventId={event.event_id}
|
|
224
|
+
eventName={event.event_name}
|
|
225
|
+
organisationId={event.organisation_id}
|
|
226
|
+
size="2xl"
|
|
227
|
+
className="rounded-lg shadow-lg"
|
|
228
|
+
/>
|
|
229
|
+
<p className="mt-4 text-sm text-gray-600">Event Logo</p>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
</main>
|
|
234
|
+
|
|
235
|
+
<PublicPageFooter event={event} />
|
|
236
|
+
</PublicPageLayout>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Generic public event page
|
|
242
|
+
*/
|
|
243
|
+
function PublicEventPage() {
|
|
244
|
+
const { eventCode } = usePublicRouteParams({ fetchEventData: false });
|
|
245
|
+
const { event, isLoading, error, refetch } = usePublicEvent(eventCode || '');
|
|
246
|
+
|
|
247
|
+
if (isLoading) return <PublicLoadingSpinner message="Loading event..." />;
|
|
248
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
249
|
+
if (!event) return <div>Event not found</div>;
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<PublicPageLayout eventCode={eventCode || ''} event={event}>
|
|
253
|
+
<PublicPageHeader
|
|
254
|
+
event={event}
|
|
255
|
+
eventCode={eventCode || ''}
|
|
256
|
+
title="Event Details"
|
|
257
|
+
description="Public information about this event"
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
<main className="max-w-4xl mx-auto px-4 py-6">
|
|
261
|
+
<div className="text-center mb-6">
|
|
262
|
+
<EventLogo
|
|
263
|
+
eventId={event.event_id}
|
|
264
|
+
eventName={event.event_name}
|
|
265
|
+
organisationId={event.organisation_id}
|
|
266
|
+
size="xl"
|
|
267
|
+
className="mx-auto mb-4"
|
|
268
|
+
/>
|
|
269
|
+
<h1 className="text-2xl font-bold text-gray-900">{event.event_name}</h1>
|
|
270
|
+
{event.event_date && (
|
|
271
|
+
<p className="text-gray-600 mt-2">
|
|
272
|
+
{new Date(event.event_date).toLocaleDateString('en-AU')}
|
|
273
|
+
</p>
|
|
274
|
+
)}
|
|
275
|
+
</div>
|
|
276
|
+
</main>
|
|
277
|
+
|
|
278
|
+
<PublicPageFooter event={event} />
|
|
279
|
+
</PublicPageLayout>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Generic public info page
|
|
285
|
+
*/
|
|
286
|
+
function PublicInfoPage() {
|
|
287
|
+
return (
|
|
288
|
+
<div className="min-h-screen bg-white flex items-center justify-center">
|
|
289
|
+
<div className="max-w-md mx-auto text-center px-4">
|
|
290
|
+
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
|
291
|
+
Public Information Page
|
|
292
|
+
</h1>
|
|
293
|
+
<p className="text-gray-600">
|
|
294
|
+
This is a public information page that doesn't require authentication.
|
|
295
|
+
</p>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export default RootApp;
|