@jmruthers/pace-core 0.6.1 → 0.6.2
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 +43 -10
- package/cursor-rules/00-pace-core-compliance.mdc +18 -91
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +14 -11
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +8 -12
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +145 -114
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +14 -14
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- 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 +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.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 +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +1 -0
- package/package.json +2 -1
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +714 -687
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +61 -849
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +3 -0
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.tsx +21 -10
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +36 -0
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +157 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +200 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useUnifiedAuth } from "../../providers/services/UnifiedAuthProvider";
|
|
3
|
+
import { useRBAC } from "../../rbac/hooks/useRBAC";
|
|
4
|
+
import { useResolvedScope } from "../../rbac/hooks";
|
|
5
|
+
import { usePermissions } from "../../rbac/hooks/usePermissions";
|
|
6
|
+
import type {
|
|
7
|
+
Permission,
|
|
8
|
+
AccessLevel as RBACAccessLevel,
|
|
9
|
+
UUID,
|
|
10
|
+
PermissionMap,
|
|
11
|
+
} from "../../rbac/types";
|
|
12
|
+
import { logger } from "../../utils/core/logger";
|
|
13
|
+
import type { NavigationItem } from "./types";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for the useNavigationFiltering hook.
|
|
17
|
+
*/
|
|
18
|
+
interface UseNavigationFilteringOptions {
|
|
19
|
+
items: NavigationItem[];
|
|
20
|
+
itemsPreFiltered?: boolean;
|
|
21
|
+
auditLog?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Return value of the useNavigationFiltering hook.
|
|
26
|
+
*/
|
|
27
|
+
interface UseNavigationFilteringResult {
|
|
28
|
+
authContext: ReturnType<typeof useUnifiedAuth> | null;
|
|
29
|
+
rbacContext: ReturnType<typeof useRBAC> | null;
|
|
30
|
+
filteredItems: NavigationItem[];
|
|
31
|
+
permissionMap: PermissionMap;
|
|
32
|
+
hasAnyPermission: ((permissions: Permission[]) => boolean) | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hook for filtering navigation items based on RBAC permissions.
|
|
37
|
+
* Filters navigation items to show only those the user has access to.
|
|
38
|
+
*
|
|
39
|
+
* @param options - Navigation filtering configuration
|
|
40
|
+
* @returns Filtered navigation items and permission context
|
|
41
|
+
*/
|
|
42
|
+
export function useNavigationFiltering({
|
|
43
|
+
items,
|
|
44
|
+
itemsPreFiltered = false,
|
|
45
|
+
auditLog = true,
|
|
46
|
+
}: UseNavigationFilteringOptions): UseNavigationFilteringResult {
|
|
47
|
+
const [resolvedAppId, setResolvedAppId] = React.useState<string | undefined>(undefined);
|
|
48
|
+
const previousFilteredItemsRef = React.useRef<NavigationItem[]>([]);
|
|
49
|
+
|
|
50
|
+
let authContext = null;
|
|
51
|
+
try {
|
|
52
|
+
authContext = useUnifiedAuth();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
logger.warn(
|
|
55
|
+
"NavigationMenu",
|
|
56
|
+
"useUnifiedAuth not available, running in unauthenticated mode",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let rbacContext = null;
|
|
61
|
+
try {
|
|
62
|
+
rbacContext = useRBAC();
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.warn(
|
|
65
|
+
"NavigationMenu",
|
|
66
|
+
"useRBAC not available, permission filtering disabled",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const eventLoadingRaw = authContext?.eventLoading;
|
|
71
|
+
const eventLoading = eventLoadingRaw ?? false;
|
|
72
|
+
const selectedEvent = authContext?.selectedEvent || null;
|
|
73
|
+
const orgContextReady =
|
|
74
|
+
authContext?.isContextReady ?? (authContext?.selectedOrganisation?.id ? true : false);
|
|
75
|
+
|
|
76
|
+
const { supabase } = authContext || {};
|
|
77
|
+
const { selectedOrganisation } = authContext || {};
|
|
78
|
+
const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
79
|
+
supabase: itemsPreFiltered ? null : supabase || null,
|
|
80
|
+
selectedOrganisationId: itemsPreFiltered ? null : selectedOrganisation?.id || null,
|
|
81
|
+
selectedEventId: itemsPreFiltered ? null : selectedEvent?.event_id || null,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
if (
|
|
86
|
+
!scopeLoading &&
|
|
87
|
+
!resolvedScope?.appId &&
|
|
88
|
+
selectedOrganisation?.id &&
|
|
89
|
+
authContext?.appName &&
|
|
90
|
+
authContext?.user?.id &&
|
|
91
|
+
!resolvedAppId
|
|
92
|
+
) {
|
|
93
|
+
if (!authContext.user || !authContext.appName) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const userId = authContext.user.id;
|
|
97
|
+
const appName = authContext.appName;
|
|
98
|
+
import("../../rbac/api")
|
|
99
|
+
.then(({ resolveAppContext }) => {
|
|
100
|
+
resolveAppContext({
|
|
101
|
+
userId,
|
|
102
|
+
appName,
|
|
103
|
+
})
|
|
104
|
+
.then((result) => {
|
|
105
|
+
if (result?.appId) {
|
|
106
|
+
setResolvedAppId(result.appId);
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
.catch(() => {});
|
|
110
|
+
})
|
|
111
|
+
.catch(() => {});
|
|
112
|
+
}
|
|
113
|
+
}, [
|
|
114
|
+
scopeLoading,
|
|
115
|
+
resolvedScope?.appId,
|
|
116
|
+
selectedOrganisation?.id,
|
|
117
|
+
authContext?.appName,
|
|
118
|
+
authContext?.user?.id,
|
|
119
|
+
resolvedAppId,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const effectiveScope = React.useMemo(() => {
|
|
123
|
+
if (resolvedScope?.organisationId) {
|
|
124
|
+
return resolvedScope;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (selectedOrganisation?.id) {
|
|
128
|
+
const fallbackScope = {
|
|
129
|
+
organisationId: selectedOrganisation.id,
|
|
130
|
+
eventId: selectedEvent?.event_id || undefined,
|
|
131
|
+
appId: resolvedAppId,
|
|
132
|
+
};
|
|
133
|
+
return fallbackScope;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}, [resolvedScope, selectedOrganisation?.id, selectedEvent?.event_id, resolvedAppId]);
|
|
138
|
+
|
|
139
|
+
const scopeKey = effectiveScope
|
|
140
|
+
? `${effectiveScope.organisationId || ""}-${effectiveScope.eventId || ""}-${
|
|
141
|
+
effectiveScope.appId || ""
|
|
142
|
+
}`
|
|
143
|
+
: "empty";
|
|
144
|
+
|
|
145
|
+
const stableScope = React.useMemo(() => {
|
|
146
|
+
if (effectiveScope?.organisationId) {
|
|
147
|
+
return {
|
|
148
|
+
organisationId: effectiveScope.organisationId,
|
|
149
|
+
eventId: effectiveScope.eventId,
|
|
150
|
+
appId: effectiveScope.appId,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
organisationId: "",
|
|
155
|
+
eventId: undefined,
|
|
156
|
+
appId: undefined,
|
|
157
|
+
};
|
|
158
|
+
}, [scopeKey, effectiveScope]);
|
|
159
|
+
|
|
160
|
+
const userId = (authContext?.user?.id || "") as UUID;
|
|
161
|
+
const {
|
|
162
|
+
permissions: permissionMap,
|
|
163
|
+
hasAnyPermission,
|
|
164
|
+
isLoading: permissionsLoading,
|
|
165
|
+
error: permissionsError,
|
|
166
|
+
} = usePermissions(
|
|
167
|
+
itemsPreFiltered ? (null as any) : userId,
|
|
168
|
+
itemsPreFiltered ? undefined : stableScope.organisationId,
|
|
169
|
+
itemsPreFiltered ? undefined : stableScope.eventId,
|
|
170
|
+
itemsPreFiltered ? undefined : stableScope.appId,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const filteredItems = React.useMemo(() => {
|
|
174
|
+
if (itemsPreFiltered && items && items.length > 0) {
|
|
175
|
+
const visibleItems = (items || []).filter((item) => !item.meta?.hidden);
|
|
176
|
+
previousFilteredItemsRef.current = visibleItems;
|
|
177
|
+
return visibleItems;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const isOrgContextReady = orgContextReady && selectedOrganisation?.id;
|
|
181
|
+
const isEventContextReady = eventLoadingRaw === undefined ? true : !eventLoading;
|
|
182
|
+
const hasValidContext = isOrgContextReady && isEventContextReady;
|
|
183
|
+
const shouldWaitForScope = scopeLoading || !hasValidContext;
|
|
184
|
+
const shouldRetryAfterError = scopeError && hasValidContext && !scopeLoading;
|
|
185
|
+
|
|
186
|
+
if (items && items.length > 0 && selectedOrganisation?.id) {
|
|
187
|
+
const visibleItems = (items || []).filter((item) => !item.meta?.hidden);
|
|
188
|
+
previousFilteredItemsRef.current = visibleItems;
|
|
189
|
+
return visibleItems;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!authContext || !rbacContext || (shouldWaitForScope && !shouldRetryAfterError)) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (permissionsLoading) {
|
|
197
|
+
if (previousFilteredItemsRef.current.length > 0) {
|
|
198
|
+
return previousFilteredItemsRef.current;
|
|
199
|
+
}
|
|
200
|
+
if (items && items.length > 0 && stableScope.organisationId) {
|
|
201
|
+
return (items || []).filter((item) => !item.meta?.hidden);
|
|
202
|
+
}
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (permissionsError) {
|
|
207
|
+
logger.warn(
|
|
208
|
+
"NavigationMenu",
|
|
209
|
+
"Permission check error - showing no items for security",
|
|
210
|
+
{
|
|
211
|
+
permissionsError: permissionsError?.message,
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!permissionMap || Object.keys(permissionMap).length === 0) {
|
|
218
|
+
if (stableScope.organisationId && items && items.length > 0) {
|
|
219
|
+
return (items || []).filter((item) => !item.meta?.hidden);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (stableScope.organisationId) {
|
|
223
|
+
logger.warn("NavigationMenu", "Permission map is empty and no items provided - showing nothing", {
|
|
224
|
+
permissionMapSize: 0,
|
|
225
|
+
organisationId: stableScope.organisationId,
|
|
226
|
+
eventId: stableScope.eventId,
|
|
227
|
+
appId: stableScope.appId,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const getPageIdFromHref = (href?: string): string | null => {
|
|
234
|
+
if (!href) return null;
|
|
235
|
+
const path = href.split("?")[0].split("#")[0].replace(/^\//, "");
|
|
236
|
+
return path || "home";
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const hasItemPermission = (item: NavigationItem): boolean => {
|
|
240
|
+
if (item.permissions && item.permissions.length > 0 && !item.href) {
|
|
241
|
+
const permissions = item.permissions
|
|
242
|
+
.filter((p): p is string => typeof p === "string")
|
|
243
|
+
.map((p) => p as Permission);
|
|
244
|
+
|
|
245
|
+
if (permissions.length > 0) {
|
|
246
|
+
const hasPermission = hasAnyPermission?.(permissions);
|
|
247
|
+
if (!hasPermission) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (item.roles && item.roles.length > 0) {
|
|
254
|
+
const hasRole = item.roles.some((role) => {
|
|
255
|
+
if (typeof role !== "string") return true;
|
|
256
|
+
|
|
257
|
+
switch (role.toLowerCase()) {
|
|
258
|
+
case "super_admin":
|
|
259
|
+
case "super admin":
|
|
260
|
+
return rbacContext.isSuperAdmin;
|
|
261
|
+
case "org_admin":
|
|
262
|
+
case "org admin":
|
|
263
|
+
case "admin":
|
|
264
|
+
return rbacContext.isOrgAdmin || rbacContext.isSuperAdmin;
|
|
265
|
+
case "event_admin":
|
|
266
|
+
case "event admin":
|
|
267
|
+
return rbacContext.isEventAdmin || rbacContext.isSuperAdmin;
|
|
268
|
+
default:
|
|
269
|
+
return (
|
|
270
|
+
rbacContext.organisationRole === role ||
|
|
271
|
+
rbacContext.eventAppRole === role ||
|
|
272
|
+
rbacContext.isSuperAdmin
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
if (!hasRole) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (item.accessLevel) {
|
|
282
|
+
if (typeof item.accessLevel === "string") {
|
|
283
|
+
const accessLevel = item.accessLevel.toLowerCase() as RBACAccessLevel;
|
|
284
|
+
const userEventRole = rbacContext.eventAppRole;
|
|
285
|
+
|
|
286
|
+
if (!rbacContext.isSuperAdmin) {
|
|
287
|
+
const roleToAccessLevel: Record<string, RBACAccessLevel> = {
|
|
288
|
+
viewer: "viewer",
|
|
289
|
+
participant: "participant",
|
|
290
|
+
planner: "planner",
|
|
291
|
+
event_admin: "admin",
|
|
292
|
+
};
|
|
293
|
+
const userAccessLevel = userEventRole ? roleToAccessLevel[userEventRole] || "viewer" : null;
|
|
294
|
+
|
|
295
|
+
const levelHierarchy: Record<RBACAccessLevel, number> = {
|
|
296
|
+
viewer: 1,
|
|
297
|
+
participant: 2,
|
|
298
|
+
planner: 3,
|
|
299
|
+
admin: 4,
|
|
300
|
+
super: 5,
|
|
301
|
+
};
|
|
302
|
+
const requiredLevel = levelHierarchy[accessLevel] || 0;
|
|
303
|
+
const userLevel = userAccessLevel ? levelHierarchy[userAccessLevel] || 0 : 0;
|
|
304
|
+
|
|
305
|
+
if (userLevel < requiredLevel) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (item.href) {
|
|
313
|
+
const pageId = item.pageId || getPageIdFromHref(item.href);
|
|
314
|
+
if (pageId) {
|
|
315
|
+
const pagePermission: Permission = `read:page.${pageId}` as Permission;
|
|
316
|
+
|
|
317
|
+
const isSuperAdmin = permissionMap["*"] === true;
|
|
318
|
+
const hasPagePermission = permissionMap[pagePermission] === true;
|
|
319
|
+
const finalHasPermission = isSuperAdmin || hasPagePermission;
|
|
320
|
+
|
|
321
|
+
if (!finalHasPermission) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const filterItem = (item: NavigationItem): NavigationItem | null => {
|
|
331
|
+
if (item.meta?.hidden) return null;
|
|
332
|
+
|
|
333
|
+
if (!hasItemPermission(item)) return null;
|
|
334
|
+
|
|
335
|
+
let filteredChildren: NavigationItem[] | undefined;
|
|
336
|
+
if (item.children && item.children.length > 0) {
|
|
337
|
+
filteredChildren = item.children
|
|
338
|
+
.map((child) => filterItem(child))
|
|
339
|
+
.filter((child): child is NavigationItem => child !== null);
|
|
340
|
+
|
|
341
|
+
if (filteredChildren.length === 0 && !item.href) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
...item,
|
|
348
|
+
children: filteredChildren,
|
|
349
|
+
};
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const filtered = (items || [])
|
|
353
|
+
.map((item) => filterItem(item))
|
|
354
|
+
.filter((item): item is NavigationItem => item !== null);
|
|
355
|
+
|
|
356
|
+
previousFilteredItemsRef.current = filtered;
|
|
357
|
+
|
|
358
|
+
return filtered;
|
|
359
|
+
}, [
|
|
360
|
+
items,
|
|
361
|
+
itemsPreFiltered,
|
|
362
|
+
authContext,
|
|
363
|
+
rbacContext,
|
|
364
|
+
permissionMap,
|
|
365
|
+
hasAnyPermission,
|
|
366
|
+
scopeLoading,
|
|
367
|
+
scopeError,
|
|
368
|
+
permissionsLoading,
|
|
369
|
+
resolvedScope,
|
|
370
|
+
effectiveScope,
|
|
371
|
+
auditLog,
|
|
372
|
+
eventLoadingRaw,
|
|
373
|
+
eventLoading,
|
|
374
|
+
selectedEvent,
|
|
375
|
+
orgContextReady,
|
|
376
|
+
selectedOrganisation?.id,
|
|
377
|
+
permissionsError,
|
|
378
|
+
stableScope.organisationId,
|
|
379
|
+
stableScope.eventId,
|
|
380
|
+
stableScope.appId,
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
authContext,
|
|
385
|
+
rbacContext,
|
|
386
|
+
filteredItems,
|
|
387
|
+
permissionMap,
|
|
388
|
+
hasAnyPermission: hasAnyPermission || null,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
@@ -63,6 +63,9 @@ import { useOrganisations } from '../../hooks/useOrganisations';
|
|
|
63
63
|
import type { Organisation } from '../../types/organisation';
|
|
64
64
|
import { logger } from '../../utils/core/logger';
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Props for the OrganisationSelector component.
|
|
68
|
+
*/
|
|
66
69
|
export interface OrganisationSelectorProps {
|
|
67
70
|
/** Placeholder text for the dropdown */
|
|
68
71
|
placeholder?: string;
|
|
@@ -889,7 +889,7 @@ describe('PaceAppLayout Component', () => {
|
|
|
889
889
|
);
|
|
890
890
|
|
|
891
891
|
await waitFor(() => {
|
|
892
|
-
// useCan is called with userId, scope, permission, pageId, useCache, appName
|
|
892
|
+
// useCan is called with userId, scope, permission, pageId, useCache, precomputedSuperAdmin, appName
|
|
893
893
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
894
894
|
'user-123',
|
|
895
895
|
expect.objectContaining({
|
|
@@ -900,6 +900,7 @@ describe('PaceAppLayout Component', () => {
|
|
|
900
900
|
'update:page.dashboard-page',
|
|
901
901
|
'dashboard-page',
|
|
902
902
|
true,
|
|
903
|
+
expect.any(Boolean), // precomputedSuperAdmin - can be false, null, or undefined during checks
|
|
903
904
|
'Test App'
|
|
904
905
|
);
|
|
905
906
|
}, { timeout: 2000 });
|
|
@@ -921,7 +922,7 @@ describe('PaceAppLayout Component', () => {
|
|
|
921
922
|
);
|
|
922
923
|
|
|
923
924
|
await waitFor(() => {
|
|
924
|
-
// useCan is called with userId, scope, permission, pageId, useCache, appName
|
|
925
|
+
// useCan is called with userId, scope, permission, pageId, useCache, precomputedSuperAdmin, appName
|
|
925
926
|
// Uses defaultPermission "create" since /dashboard is not in routePermissions
|
|
926
927
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
927
928
|
'user-123',
|
|
@@ -933,6 +934,7 @@ describe('PaceAppLayout Component', () => {
|
|
|
933
934
|
'create:page.dashboard',
|
|
934
935
|
'dashboard',
|
|
935
936
|
true,
|
|
937
|
+
expect.any(Boolean), // precomputedSuperAdmin - can be false, null, or undefined during checks
|
|
936
938
|
'Test App'
|
|
937
939
|
);
|
|
938
940
|
}, { timeout: 2000 });
|
|
@@ -121,6 +121,10 @@ import type { PasswordChangeFormError } from '../PasswordChange/PasswordChangeFo
|
|
|
121
121
|
// Define Operation type locally since old RBAC types are removed
|
|
122
122
|
type Operation = 'read' | 'create' | 'update' | 'delete' | 'manage';
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Props for the PaceAppLayout component.
|
|
126
|
+
* Configures the application layout including navigation, header, and footer.
|
|
127
|
+
*/
|
|
124
128
|
export interface PaceAppLayoutProps {
|
|
125
129
|
/** The name of the application to be displayed in the header. */
|
|
126
130
|
appName: string;
|
|
@@ -524,12 +528,15 @@ export function PaceAppLayout({
|
|
|
524
528
|
// Pass appName to useCan so it can be passed to isPermitted for PORTAL/ADMIN special case
|
|
525
529
|
// Only check permissions if enforcePermissions is true and we have a valid permission string
|
|
526
530
|
const shouldCheckPermission = enforcePermissions && !!currentPermission && !!currentPageId;
|
|
531
|
+
// Pass super admin status to avoid duplicate check - use null if still checking, false/true if known
|
|
532
|
+
const superAdminStatus = isSuperAdminFromRBAC ? true : (isCheckingSuperAdminDirect ? null : isSuperAdminDirect);
|
|
527
533
|
const { can: canFromHook, isLoading: isCheckingPermission, error: permissionError } = useCan(
|
|
528
534
|
user?.id || '',
|
|
529
535
|
scope,
|
|
530
536
|
shouldCheckPermission ? currentPermission : ('' as Permission),
|
|
531
537
|
shouldCheckPermission ? currentPageId : '',
|
|
532
538
|
true, // useCache
|
|
539
|
+
superAdminStatus, // Pass super admin status to avoid duplicate check
|
|
533
540
|
appName // Pass appName for PORTAL/ADMIN special case
|
|
534
541
|
);
|
|
535
542
|
|
|
@@ -817,25 +824,39 @@ export function PaceAppLayout({
|
|
|
817
824
|
}, [roleBasedRouting, routeConfig, location.pathname, strictMode, user?.id, fallbackRoute, scope, navigate, auditLog, onRouteAccessDenied, onRouteStrictModeViolation]);
|
|
818
825
|
|
|
819
826
|
const handleSignOut = async () => {
|
|
820
|
-
|
|
827
|
+
try {
|
|
828
|
+
await signOut();
|
|
829
|
+
} catch (error) {
|
|
830
|
+
logger.error('PaceAppLayout', 'Failed to sign out', { error: error instanceof Error ? error.message : String(error) });
|
|
831
|
+
}
|
|
821
832
|
};
|
|
822
833
|
|
|
823
834
|
const handleChangePassword = async (newPassword: string, confirmPassword: string) => {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
835
|
+
try {
|
|
836
|
+
// The form component in UserMenu already checks for matching passwords
|
|
837
|
+
const result = await updatePassword(newPassword);
|
|
838
|
+
if (result?.error) {
|
|
839
|
+
// The form will display the error message
|
|
840
|
+
logger.error('PaceAppLayout', 'Failed to change password', { error: result.error.message });
|
|
841
|
+
// Convert AuthError to PasswordChangeFormError
|
|
842
|
+
return {
|
|
843
|
+
error: {
|
|
844
|
+
message: result.error.message,
|
|
845
|
+
code: result.error.name || 'PASSWORD_UPDATE_ERROR'
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
// The form will handle closing the modal on success
|
|
850
|
+
return {};
|
|
851
|
+
} catch (error) {
|
|
852
|
+
logger.error('PaceAppLayout', 'Failed to change password', { error: error instanceof Error ? error.message : String(error) });
|
|
830
853
|
return {
|
|
831
854
|
error: {
|
|
832
|
-
message:
|
|
833
|
-
code:
|
|
855
|
+
message: error instanceof Error ? error.message : 'An unexpected error occurred',
|
|
856
|
+
code: 'PASSWORD_UPDATE_ERROR'
|
|
834
857
|
}
|
|
835
858
|
};
|
|
836
859
|
}
|
|
837
|
-
// The form will handle closing the modal on success
|
|
838
|
-
return {};
|
|
839
860
|
};
|
|
840
861
|
|
|
841
862
|
// CRITICAL: Wait for organisation context to be ready before proceeding
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="vitest/globals" />
|
|
1
2
|
/**
|
|
2
3
|
* @file Shared Test Setup for PaceAppLayout Tests
|
|
3
4
|
* @package @jmruthers/pace-core
|
|
@@ -10,8 +11,6 @@
|
|
|
10
11
|
* provides the shared mock data and reset functions that can be imported and used
|
|
11
12
|
* in the vi.mock() factory functions.
|
|
12
13
|
*/
|
|
13
|
-
|
|
14
|
-
import { vi } from 'vitest';
|
|
15
14
|
import React from 'react';
|
|
16
15
|
|
|
17
16
|
// === MOCK DATA ===
|
|
@@ -131,6 +131,9 @@ import { clearPalette } from '../../theming/runtime';
|
|
|
131
131
|
import { EventServiceContext } from '../../providers/services/EventServiceProvider';
|
|
132
132
|
import { logger } from '../../utils/core/logger';
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Props for the PaceLoginPage component.
|
|
136
|
+
*/
|
|
134
137
|
export interface PaceLoginPageProps {
|
|
135
138
|
/** The name of the application to be displayed on the login form. */
|
|
136
139
|
appName: string;
|
|
@@ -103,16 +103,25 @@ import { Input } from '../Input/Input';
|
|
|
103
103
|
import { Label } from '../Label';
|
|
104
104
|
import { cn } from '../../utils/core/cn';
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Form values for password change.
|
|
108
|
+
*/
|
|
106
109
|
export interface PasswordChangeFormValues {
|
|
107
110
|
newPassword: string;
|
|
108
111
|
confirmPassword: string;
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Error structure for password change form.
|
|
116
|
+
*/
|
|
111
117
|
export interface PasswordChangeFormError {
|
|
112
118
|
message?: string;
|
|
113
119
|
code?: string;
|
|
114
120
|
}
|
|
115
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Props for the PasswordChangeForm component.
|
|
124
|
+
*/
|
|
116
125
|
export interface PasswordChangeFormProps {
|
|
117
126
|
onSubmit: (values: PasswordChangeFormValues) => Promise<{ error?: PasswordChangeFormError }>;
|
|
118
127
|
className?: string;
|
|
@@ -79,6 +79,9 @@ import { Alert, AlertDescription, AlertTitle } from '../Alert/Alert';
|
|
|
79
79
|
import { logger } from '../../utils/core/logger';
|
|
80
80
|
import { usePreventTabReload } from '../../hooks/usePreventTabReload';
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Props for the ProtectedRoute component.
|
|
84
|
+
*/
|
|
82
85
|
export interface ProtectedRouteProps {
|
|
83
86
|
/**
|
|
84
87
|
* Whether an event is required for routes inside this component.
|
|
@@ -88,14 +91,6 @@ export interface ProtectedRouteProps {
|
|
|
88
91
|
*/
|
|
89
92
|
requireEvent?: boolean;
|
|
90
93
|
|
|
91
|
-
/**
|
|
92
|
-
* Whether super admins can bypass event requirement.
|
|
93
|
-
* Note: This feature requires additional RBAC setup. For simple bypass, set requireEvent={false} instead.
|
|
94
|
-
* @default false
|
|
95
|
-
* @deprecated Use requireEvent={false} for routes that don't need events
|
|
96
|
-
*/
|
|
97
|
-
allowSuperAdminBypass?: boolean;
|
|
98
|
-
|
|
99
94
|
/**
|
|
100
95
|
* Custom component to render when no events are available.
|
|
101
96
|
* If not provided, a default message is shown.
|
|
@@ -133,7 +128,6 @@ export interface ProtectedRouteProps {
|
|
|
133
128
|
*/
|
|
134
129
|
export function ProtectedRoute({
|
|
135
130
|
requireEvent = false,
|
|
136
|
-
allowSuperAdminBypass = false,
|
|
137
131
|
noEventsFallback,
|
|
138
132
|
loadingFallback,
|
|
139
133
|
loginPath = '/login'
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
* refetch={refetch}
|
|
36
36
|
* >
|
|
37
37
|
* <h1>Event Details</h1>
|
|
38
|
-
* <
|
|
38
|
+
* <main className="content">
|
|
39
39
|
* Your public page content
|
|
40
|
-
* </
|
|
40
|
+
* </main>
|
|
41
41
|
* </PublicPageLayout>
|
|
42
42
|
* );
|
|
43
43
|
* }
|
|
@@ -90,8 +90,6 @@ export interface PublicPageLayoutProps {
|
|
|
90
90
|
refetch?: () => Promise<void> | void;
|
|
91
91
|
/** Whether to show the footer (default: true) */
|
|
92
92
|
showFooter?: boolean;
|
|
93
|
-
/** @deprecated Custom CSS classes for the layout - no longer used as wrapper div was removed */
|
|
94
|
-
className?: string;
|
|
95
93
|
/** Custom error fallback component */
|
|
96
94
|
errorFallback?: React.ComponentType<{ error: Error; retry: () => void }>;
|
|
97
95
|
/** Custom loading fallback component */
|
|
@@ -294,7 +292,6 @@ export function PublicPageLayout({
|
|
|
294
292
|
error = null,
|
|
295
293
|
refetch,
|
|
296
294
|
showFooter = true,
|
|
297
|
-
className = '',
|
|
298
295
|
errorFallback: ErrorFallback,
|
|
299
296
|
loadingFallback: LoadingFallback,
|
|
300
297
|
customHeader,
|
|
@@ -50,6 +50,10 @@ interface PublicPageContextType {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Context for public pages.
|
|
55
|
+
* Provides isolated context for public pages without authentication requirements.
|
|
56
|
+
*/
|
|
53
57
|
export const PublicPageContext = createContext<PublicPageContextType | undefined>(undefined);
|
|
54
58
|
|
|
55
59
|
export interface PublicPageProviderProps {
|