@jmruthers/pace-core 0.6.2 → 0.6.4
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 +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/chunk-36LVWXB2.js +227 -0
- package/dist/chunk-36LVWXB2.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
- package/dist/chunk-3LPHPB62.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
- package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
- package/dist/chunk-7JPAB3T5.js.map +1 -0
- package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
- package/dist/chunk-ATKZM7RX.js.map +1 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
- package/dist/chunk-AVMLPIM7.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
- package/dist/chunk-I6DAQMWX.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
- package/dist/chunk-NN6WWZ5U.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
- package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
- package/dist/chunk-YKRAFF5K.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-OOPCLPZW.js +9 -0
- package/dist/contextValidator-OOPCLPZW.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +10 -5
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +568 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +285 -56
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Fix for Auto-Selection Issue in pace-mint AppLayout
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
|
|
5
|
+
When a user switches from an event to an organisation in the context selector, pace-mint's AppLayout component is auto-selecting the event again, even though the user explicitly cleared it. This causes the selector to show the event instead of the organisation.
|
|
6
|
+
|
|
7
|
+
## Root Cause
|
|
8
|
+
|
|
9
|
+
pace-mint's AppLayout has its own auto-selection logic that runs whenever:
|
|
10
|
+
- Events are loaded/refreshed
|
|
11
|
+
- `selectedEvent` becomes `null`
|
|
12
|
+
- There's only one event available
|
|
13
|
+
|
|
14
|
+
This logic doesn't distinguish between:
|
|
15
|
+
- **Initial load** (should auto-select)
|
|
16
|
+
- **User explicitly cleared event** (should NOT auto-select)
|
|
17
|
+
|
|
18
|
+
## Solution
|
|
19
|
+
|
|
20
|
+
Update pace-mint's AppLayout component to track when the user explicitly clears the event selection, and skip auto-selection in those cases.
|
|
21
|
+
|
|
22
|
+
## Implementation Steps
|
|
23
|
+
|
|
24
|
+
### Step 1: Track Previous Event Selection
|
|
25
|
+
|
|
26
|
+
Add a ref to track the previous `selectedEvent` value to detect when the user explicitly clears it:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { useRef, useEffect } from 'react';
|
|
30
|
+
import { useEvents } from '@jmruthers/pace-core';
|
|
31
|
+
|
|
32
|
+
function AppLayout() {
|
|
33
|
+
const { events, selectedEvent, setSelectedEvent } = useEvents();
|
|
34
|
+
|
|
35
|
+
// Track previous selectedEvent to detect explicit clears
|
|
36
|
+
const previousSelectedEventRef = useRef<Event | null>(null);
|
|
37
|
+
const userExplicitlyClearedRef = useRef<boolean>(false);
|
|
38
|
+
|
|
39
|
+
// Detect when user explicitly clears event (was set, now null)
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const wasSet = previousSelectedEventRef.current !== null;
|
|
42
|
+
const isNowNull = selectedEvent === null;
|
|
43
|
+
|
|
44
|
+
if (wasSet && isNowNull) {
|
|
45
|
+
// User explicitly cleared the event
|
|
46
|
+
userExplicitlyClearedRef.current = true;
|
|
47
|
+
} else if (selectedEvent !== null) {
|
|
48
|
+
// Event is selected, reset the flag
|
|
49
|
+
userExplicitlyClearedRef.current = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
previousSelectedEventRef.current = selectedEvent;
|
|
53
|
+
}, [selectedEvent]);
|
|
54
|
+
|
|
55
|
+
// ... rest of component
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Step 2: Update Auto-Selection Logic
|
|
60
|
+
|
|
61
|
+
Modify your auto-selection logic to check the flag before auto-selecting:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// OLD CODE (problematic):
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!selectedEvent && events.length === 1 && !isLoading) {
|
|
67
|
+
// Auto-select single event
|
|
68
|
+
setSelectedEvent(events[0]);
|
|
69
|
+
}
|
|
70
|
+
}, [selectedEvent, events, isLoading, setSelectedEvent]);
|
|
71
|
+
|
|
72
|
+
// NEW CODE (fixed):
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
// Only auto-select if:
|
|
75
|
+
// 1. No event is currently selected
|
|
76
|
+
// 2. There's exactly one event available
|
|
77
|
+
// 3. Events are not loading
|
|
78
|
+
// 4. User did NOT explicitly clear the event
|
|
79
|
+
if (
|
|
80
|
+
!selectedEvent &&
|
|
81
|
+
events.length === 1 &&
|
|
82
|
+
!isLoading &&
|
|
83
|
+
!userExplicitlyClearedRef.current
|
|
84
|
+
) {
|
|
85
|
+
// Auto-select single event
|
|
86
|
+
setSelectedEvent(events[0]);
|
|
87
|
+
}
|
|
88
|
+
}, [selectedEvent, events, isLoading, setSelectedEvent]);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Step 3: Reset Flag on Initial Load (Optional)
|
|
92
|
+
|
|
93
|
+
If you want to allow auto-selection on initial page load but prevent it after user actions, you can track whether this is the initial load:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
const isInitialLoadRef = useRef<boolean>(true);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
// After first render, mark initial load as complete
|
|
100
|
+
if (isInitialLoadRef.current && events.length > 0) {
|
|
101
|
+
isInitialLoadRef.current = false;
|
|
102
|
+
}
|
|
103
|
+
}, [events]);
|
|
104
|
+
|
|
105
|
+
// Then in auto-selection logic:
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (
|
|
108
|
+
!selectedEvent &&
|
|
109
|
+
events.length === 1 &&
|
|
110
|
+
!isLoading &&
|
|
111
|
+
(!userExplicitlyClearedRef.current || isInitialLoadRef.current)
|
|
112
|
+
) {
|
|
113
|
+
setSelectedEvent(events[0]);
|
|
114
|
+
}
|
|
115
|
+
}, [selectedEvent, events, isLoading, setSelectedEvent]);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Complete Example
|
|
119
|
+
|
|
120
|
+
Here's a complete example of the fix:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useRef, useEffect } from 'react';
|
|
124
|
+
import { useEvents } from '@jmruthers/pace-core';
|
|
125
|
+
import type { Event } from '@jmruthers/pace-core';
|
|
126
|
+
|
|
127
|
+
function AppLayout() {
|
|
128
|
+
const { events, selectedEvent, setSelectedEvent, isLoading } = useEvents();
|
|
129
|
+
|
|
130
|
+
// Track previous selectedEvent to detect explicit clears
|
|
131
|
+
const previousSelectedEventRef = useRef<Event | null>(null);
|
|
132
|
+
const userExplicitlyClearedRef = useRef<boolean>(false);
|
|
133
|
+
const isInitialLoadRef = useRef<boolean>(true);
|
|
134
|
+
|
|
135
|
+
// Detect when user explicitly clears event
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const wasSet = previousSelectedEventRef.current !== null;
|
|
138
|
+
const isNowNull = selectedEvent === null;
|
|
139
|
+
|
|
140
|
+
if (wasSet && isNowNull) {
|
|
141
|
+
// User explicitly cleared the event (e.g., switched to organisation)
|
|
142
|
+
userExplicitlyClearedRef.current = true;
|
|
143
|
+
console.log('[AppLayout] User explicitly cleared event - preventing auto-selection');
|
|
144
|
+
} else if (selectedEvent !== null) {
|
|
145
|
+
// Event is selected, reset the flag
|
|
146
|
+
userExplicitlyClearedRef.current = false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
previousSelectedEventRef.current = selectedEvent;
|
|
150
|
+
}, [selectedEvent]);
|
|
151
|
+
|
|
152
|
+
// Mark initial load as complete after events are loaded
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (isInitialLoadRef.current && events.length > 0 && !isLoading) {
|
|
155
|
+
isInitialLoadRef.current = false;
|
|
156
|
+
}
|
|
157
|
+
}, [events, isLoading]);
|
|
158
|
+
|
|
159
|
+
// Auto-select single event ONLY if:
|
|
160
|
+
// - No event is selected
|
|
161
|
+
// - Exactly one event available
|
|
162
|
+
// - Not loading
|
|
163
|
+
// - User didn't explicitly clear it (or it's initial load)
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
const shouldAutoSelect =
|
|
166
|
+
!selectedEvent &&
|
|
167
|
+
events.length === 1 &&
|
|
168
|
+
!isLoading &&
|
|
169
|
+
(!userExplicitlyClearedRef.current || isInitialLoadRef.current);
|
|
170
|
+
|
|
171
|
+
if (shouldAutoSelect) {
|
|
172
|
+
console.log('[AppLayout] Auto-selecting single available event');
|
|
173
|
+
setSelectedEvent(events[0]);
|
|
174
|
+
} else if (!selectedEvent && events.length === 1 && userExplicitlyClearedRef.current) {
|
|
175
|
+
console.log('[AppLayout] Auto-select skipped - user explicitly cleared event');
|
|
176
|
+
}
|
|
177
|
+
}, [selectedEvent, events, isLoading, setSelectedEvent]);
|
|
178
|
+
|
|
179
|
+
// ... rest of your component
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Testing
|
|
184
|
+
|
|
185
|
+
After implementing the fix, test the following scenarios:
|
|
186
|
+
|
|
187
|
+
1. **Initial Load**:
|
|
188
|
+
- ✅ Should auto-select if only one event is available
|
|
189
|
+
|
|
190
|
+
2. **Switch Event to Organisation**:
|
|
191
|
+
- ✅ Should NOT auto-select event after switching to organisation
|
|
192
|
+
- ✅ Selector should show the organisation, not the event
|
|
193
|
+
|
|
194
|
+
3. **Switch Organisation to Event**:
|
|
195
|
+
- ✅ Should select the event when user explicitly selects it
|
|
196
|
+
- ✅ Selector should show the event
|
|
197
|
+
|
|
198
|
+
4. **Manual Event Selection**:
|
|
199
|
+
- ✅ Should select the event when user clicks on it
|
|
200
|
+
- ✅ Should not interfere with manual selections
|
|
201
|
+
|
|
202
|
+
## Additional Notes
|
|
203
|
+
|
|
204
|
+
- pace-core's EventService already has internal logic to prevent auto-selection when `userClearedEventRef` is set
|
|
205
|
+
- This fix adds an additional layer of protection at the application level
|
|
206
|
+
- The ref-based approach ensures the flag persists across re-renders
|
|
207
|
+
- The initial load flag allows auto-selection on first page load while preventing it after user actions
|
|
208
|
+
|
|
209
|
+
## Related pace-core Changes
|
|
210
|
+
|
|
211
|
+
The following changes were made in pace-core to support this fix:
|
|
212
|
+
|
|
213
|
+
1. **ContextSelector** - Now prioritizes event selection over organisation when both are selected
|
|
214
|
+
2. **Header Component** - Clears event selection when switching to organisation
|
|
215
|
+
3. **EventService** - Preserves `userClearedEventRef` flag when organisation changes
|
|
216
|
+
|
|
217
|
+
These changes ensure that pace-core respects user intent, but consuming apps (like pace-mint) should also implement the above fix to prevent their own auto-selection logic from interfering.
|
|
218
|
+
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
---
|
|
2
|
+
lastUpdated: 2025-12-31T00:00:00+11:00
|
|
3
|
+
version: 0.6.4
|
|
4
|
+
reviewedBy: pace-mint-implementation-guide
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# pace-mint RBAC Setup Guide
|
|
8
|
+
|
|
9
|
+
This guide explains how to configure pace-mint to support both organisation-based and event-based pages using pace-core's RBAC system.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
pace-mint is a **hybrid app** that supports both:
|
|
14
|
+
- **Organisation-based pages**: Financial management at the organisation level
|
|
15
|
+
- **Event-based pages**: Budget and budget variables for specific events
|
|
16
|
+
|
|
17
|
+
Users can have:
|
|
18
|
+
- Organisation roles (org_admin, leader, member, etc.) for organisation-based pages
|
|
19
|
+
- Event-app roles (planner, participant, etc.) for event-based pages
|
|
20
|
+
- Both types of roles simultaneously
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
### Two-Stream Navigation
|
|
25
|
+
|
|
26
|
+
pace-mint uses a **two-stream architecture**:
|
|
27
|
+
|
|
28
|
+
1. **Landing/Dashboard Page**: User selects their mode
|
|
29
|
+
- "Organisation Financial Management" → Organisation stream
|
|
30
|
+
- "Event Financial Management" → Event stream
|
|
31
|
+
|
|
32
|
+
2. **Each Stream**: Has its own navigation showing only relevant pages
|
|
33
|
+
- Organisation stream: Shows only organisation-scoped pages
|
|
34
|
+
- Event stream: Shows only event-scoped pages
|
|
35
|
+
|
|
36
|
+
### Page Scoping
|
|
37
|
+
|
|
38
|
+
Each page in pace-mint is marked with a `scope_type`:
|
|
39
|
+
|
|
40
|
+
- `'event'`: Page requires event context (e.g., "budget", "budget-variables")
|
|
41
|
+
- `'organisation'`: Page requires organisation context (e.g., "dashboard", "reports")
|
|
42
|
+
- `'both'`: Page can be accessed in either context (rare, but possible)
|
|
43
|
+
|
|
44
|
+
**Note**: All pages must have `scope_type` set explicitly. There is no inheritance from app-level configuration.
|
|
45
|
+
|
|
46
|
+
## Database Setup
|
|
47
|
+
|
|
48
|
+
### 1. Ensure pace-mint App Configuration
|
|
49
|
+
|
|
50
|
+
```sql
|
|
51
|
+
-- Verify pace-mint app exists and is configured correctly
|
|
52
|
+
SELECT id, name, display_name, is_active
|
|
53
|
+
FROM rbac_apps
|
|
54
|
+
WHERE name = 'MINT';
|
|
55
|
+
|
|
56
|
+
-- Note: App-level scope configuration (requires_event) has been removed.
|
|
57
|
+
-- All scope is now configured at the page level via scope_type.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Create/Update Pages with scope_type
|
|
61
|
+
|
|
62
|
+
For each page in pace-mint, set the `scope_type`:
|
|
63
|
+
|
|
64
|
+
```sql
|
|
65
|
+
-- Example: Budget page is event-based
|
|
66
|
+
UPDATE rbac_app_pages
|
|
67
|
+
SET scope_type = 'event'
|
|
68
|
+
WHERE app_id = (SELECT id FROM rbac_apps WHERE name = 'MINT')
|
|
69
|
+
AND page_name = 'budget';
|
|
70
|
+
|
|
71
|
+
-- Example: Dashboard page is organisation-based (or NULL to inherit)
|
|
72
|
+
UPDATE rbac_app_pages
|
|
73
|
+
SET scope_type = 'organisation'
|
|
74
|
+
WHERE app_id = (SELECT id FROM rbac_apps WHERE name = 'MINT')
|
|
75
|
+
AND page_name = 'dashboard';
|
|
76
|
+
|
|
77
|
+
-- Example: Budget variables page is event-based
|
|
78
|
+
UPDATE rbac_app_pages
|
|
79
|
+
SET scope_type = 'event'
|
|
80
|
+
WHERE app_id = (SELECT id FROM rbac_apps WHERE name = 'MINT')
|
|
81
|
+
AND page_name = 'budget-variables';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Important**:
|
|
85
|
+
- All pages must have `scope_type` set explicitly (no NULL values allowed)
|
|
86
|
+
- Set `scope_type` to match the page's context requirements
|
|
87
|
+
- There is no inheritance - each page declares its own scope
|
|
88
|
+
|
|
89
|
+
### 3. Configure Page Permissions
|
|
90
|
+
|
|
91
|
+
Set up permissions for each page based on its scope:
|
|
92
|
+
|
|
93
|
+
#### Event-Based Pages (e.g., "budget")
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
-- Get the page ID
|
|
97
|
+
WITH mint_app AS (
|
|
98
|
+
SELECT id FROM rbac_apps WHERE name = 'MINT'
|
|
99
|
+
),
|
|
100
|
+
budget_page AS (
|
|
101
|
+
SELECT ap.id as page_id
|
|
102
|
+
FROM rbac_app_pages ap
|
|
103
|
+
JOIN mint_app ma ON ap.app_id = ma.id
|
|
104
|
+
WHERE ap.page_name = 'budget'
|
|
105
|
+
)
|
|
106
|
+
-- Insert permissions for event-based roles
|
|
107
|
+
INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed, organisation_id)
|
|
108
|
+
SELECT
|
|
109
|
+
bp.page_id,
|
|
110
|
+
op.operation,
|
|
111
|
+
role.role_name,
|
|
112
|
+
CASE
|
|
113
|
+
WHEN role.role_name = 'planner' THEN true
|
|
114
|
+
WHEN role.role_name = 'participant' AND op.operation = 'read' THEN true
|
|
115
|
+
ELSE false
|
|
116
|
+
END,
|
|
117
|
+
'00000000-0000-0000-0000-000000000000'::uuid -- ⚠️ REPLACE with actual organisation ID
|
|
118
|
+
FROM budget_page bp
|
|
119
|
+
CROSS JOIN (SELECT unnest(ARRAY['read', 'create', 'update', 'delete']) as operation) op
|
|
120
|
+
CROSS JOIN (SELECT unnest(ARRAY['planner', 'participant', 'viewer']) as role_name) role;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Organisation-Based Pages (e.g., "dashboard")
|
|
124
|
+
|
|
125
|
+
```sql
|
|
126
|
+
-- Get the page ID
|
|
127
|
+
WITH mint_app AS (
|
|
128
|
+
SELECT id FROM rbac_apps WHERE name = 'MINT'
|
|
129
|
+
),
|
|
130
|
+
dashboard_page AS (
|
|
131
|
+
SELECT ap.id as page_id
|
|
132
|
+
FROM rbac_app_pages ap
|
|
133
|
+
JOIN mint_app ma ON ap.app_id = ma.id
|
|
134
|
+
WHERE ap.page_name = 'dashboard'
|
|
135
|
+
)
|
|
136
|
+
-- Insert permissions for organisation-based roles
|
|
137
|
+
INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed, organisation_id)
|
|
138
|
+
SELECT
|
|
139
|
+
dp.page_id,
|
|
140
|
+
op.operation,
|
|
141
|
+
role.role_name,
|
|
142
|
+
CASE
|
|
143
|
+
WHEN role.role_name = 'org_admin' THEN true
|
|
144
|
+
WHEN role.role_name = 'leader' AND op.operation != 'delete' THEN true
|
|
145
|
+
WHEN role.role_name = 'member' AND op.operation = 'read' THEN true
|
|
146
|
+
ELSE false
|
|
147
|
+
END,
|
|
148
|
+
'00000000-0000-0000-0000-000000000000'::uuid -- ⚠️ REPLACE with actual organisation ID
|
|
149
|
+
FROM dashboard_page dp
|
|
150
|
+
CROSS JOIN (SELECT unnest(ARRAY['read', 'create', 'update', 'delete']) as operation) op
|
|
151
|
+
CROSS JOIN (SELECT unnest(ARRAY['org_admin', 'leader', 'member', 'supporter']) as role_name) role;
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## pace-core Components
|
|
155
|
+
|
|
156
|
+
### 1. Context Selector Component
|
|
157
|
+
|
|
158
|
+
pace-core provides a unified `ContextSelector` component that intelligently shows all organisations and events a user can access:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { ContextSelector } from '@jmruthers/pace-core';
|
|
162
|
+
|
|
163
|
+
<ContextSelector
|
|
164
|
+
onOrganisationSelect={(org) => {
|
|
165
|
+
// Switch to organisation stream
|
|
166
|
+
setSelectedOrganisation(org);
|
|
167
|
+
setSelectedEvent(null);
|
|
168
|
+
navigate('/dashboard'); // Navigate to org-based dashboard
|
|
169
|
+
}}
|
|
170
|
+
onEventSelect={(event) => {
|
|
171
|
+
// Switch to event stream
|
|
172
|
+
setSelectedEvent(event);
|
|
173
|
+
setSelectedOrganisation(null);
|
|
174
|
+
navigate('/budget'); // Navigate to event-based budget page
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The `ContextSelector` automatically shows:
|
|
180
|
+
- All organisations the user has access to (via organisation roles)
|
|
181
|
+
- All events the user has access to (via event-app roles or organisation membership)
|
|
182
|
+
- Everything for super admins
|
|
183
|
+
|
|
184
|
+
### 2. Navigation Filtering
|
|
185
|
+
|
|
186
|
+
pace-core's navigation automatically filters pages based on:
|
|
187
|
+
- Current context (event selected vs organisation selected)
|
|
188
|
+
- Page `scope_type`
|
|
189
|
+
- User's permissions
|
|
190
|
+
|
|
191
|
+
You don't need to manually filter - pace-core handles this automatically.
|
|
192
|
+
|
|
193
|
+
### 3. Permission Checking
|
|
194
|
+
|
|
195
|
+
pace-core automatically handles permission checking based on page scope:
|
|
196
|
+
|
|
197
|
+
- **Event-based pages**: Checks event-app roles for the selected event
|
|
198
|
+
- **Organisation-based pages**: Checks organisation roles for the selected organisation
|
|
199
|
+
- **'both' pages**: Checks both scopes and returns the union (higher permissions win)
|
|
200
|
+
|
|
201
|
+
## Implementation Steps
|
|
202
|
+
|
|
203
|
+
### Step 1: Database Configuration
|
|
204
|
+
|
|
205
|
+
1. Run the pace-core migration that adds `scope_type` to `rbac_app_pages`
|
|
206
|
+
2. Update all pace-mint pages with appropriate `scope_type` values
|
|
207
|
+
3. Configure page permissions for each scope type
|
|
208
|
+
|
|
209
|
+
### Step 2: Landing Page
|
|
210
|
+
|
|
211
|
+
Create a landing page where users choose their mode:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
function MintLandingPage() {
|
|
215
|
+
const { selectedOrganisation, selectedEvent } = useUnifiedAuth();
|
|
216
|
+
const navigate = useNavigate();
|
|
217
|
+
|
|
218
|
+
const handleOrgMode = () => {
|
|
219
|
+
// Ensure organisation is selected
|
|
220
|
+
if (!selectedOrganisation) {
|
|
221
|
+
// Show organisation selector
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
navigate('/dashboard');
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const handleEventMode = () => {
|
|
228
|
+
// Ensure event is selected
|
|
229
|
+
if (!selectedEvent) {
|
|
230
|
+
// Show event selector
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
navigate('/budget');
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<main>
|
|
238
|
+
<section>
|
|
239
|
+
<h1>pace-mint</h1>
|
|
240
|
+
<button onClick={handleOrgMode}>
|
|
241
|
+
Organisation Financial Management
|
|
242
|
+
</button>
|
|
243
|
+
<button onClick={handleEventMode}>
|
|
244
|
+
Event Financial Management
|
|
245
|
+
</button>
|
|
246
|
+
</section>
|
|
247
|
+
</main>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Step 3: Navigation Configuration
|
|
253
|
+
|
|
254
|
+
Configure navigation items with proper page names:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
const orgNavItems = [
|
|
258
|
+
{ label: 'Dashboard', pageName: 'dashboard', href: '/dashboard' },
|
|
259
|
+
{ label: 'Reports', pageName: 'reports', href: '/reports' },
|
|
260
|
+
// ... other org-based pages
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const eventNavItems = [
|
|
264
|
+
{ label: 'Budget', pageName: 'budget', href: '/budget' },
|
|
265
|
+
{ label: 'Budget Variables', pageName: 'budget-variables', href: '/budget-variables' },
|
|
266
|
+
// ... other event-based pages
|
|
267
|
+
];
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Step 4: Use Context Selector in Header
|
|
271
|
+
|
|
272
|
+
The `PaceAppLayout` component includes the unified `ContextSelector` by default:
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import { PaceAppLayout } from '@jmruthers/pace-core';
|
|
276
|
+
|
|
277
|
+
function App() {
|
|
278
|
+
const { selectedOrganisation, selectedEvent, setSelectedOrganisation, setSelectedEvent } = useUnifiedAuth();
|
|
279
|
+
const navigate = useNavigate();
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<PaceAppLayout
|
|
283
|
+
appName="MINT"
|
|
284
|
+
navItems={/* your nav items */}
|
|
285
|
+
showContextSelector={true} // Default: shows unified selector
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The `ContextSelector` is automatically integrated into the header and handles organisation/event selection. You can also use it directly if you need custom behavior:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { ContextSelector } from '@jmruthers/pace-core';
|
|
295
|
+
|
|
296
|
+
<ContextSelector
|
|
297
|
+
onOrganisationSelect={(org) => {
|
|
298
|
+
setSelectedOrganisation(org);
|
|
299
|
+
setSelectedEvent(null);
|
|
300
|
+
navigate('/dashboard');
|
|
301
|
+
}}
|
|
302
|
+
onEventSelect={(event) => {
|
|
303
|
+
setSelectedEvent(event);
|
|
304
|
+
setSelectedOrganisation(null);
|
|
305
|
+
navigate('/budget');
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Step 5: Page Protection
|
|
311
|
+
|
|
312
|
+
Use `PagePermissionGuard` for all pages:
|
|
313
|
+
|
|
314
|
+
```tsx
|
|
315
|
+
import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
|
|
316
|
+
|
|
317
|
+
function BudgetPage() {
|
|
318
|
+
return (
|
|
319
|
+
<PagePermissionGuard pageName="budget" operation="read">
|
|
320
|
+
{/* Budget page content */}
|
|
321
|
+
</PagePermissionGuard>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Permission Precedence
|
|
327
|
+
|
|
328
|
+
When a user has both organisation and event permissions for a 'both' page:
|
|
329
|
+
|
|
330
|
+
1. **Union of permissions**: User gets permissions from both scopes
|
|
331
|
+
2. **Higher order wins**: More permissive permission takes precedence
|
|
332
|
+
- Access levels: `super` > `admin` > `user` > `none`
|
|
333
|
+
- Permission values: `true` > `false`
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
- User has `read:budget` at org level (org_admin)
|
|
337
|
+
- User has `write:budget` at event level (planner)
|
|
338
|
+
- Result: User gets `write:budget` (higher permission wins)
|
|
339
|
+
|
|
340
|
+
## Super Admin Behavior
|
|
341
|
+
|
|
342
|
+
Super admins:
|
|
343
|
+
- See **all events** in the hybrid selector
|
|
344
|
+
- See **all organisations** in the hybrid selector
|
|
345
|
+
- Can access **all pages** regardless of scope
|
|
346
|
+
- Bypass all permission checks
|
|
347
|
+
|
|
348
|
+
## Testing Checklist
|
|
349
|
+
|
|
350
|
+
- [ ] All pages have correct `scope_type` set in database
|
|
351
|
+
- [ ] Event-based pages only accessible when event is selected
|
|
352
|
+
- [ ] Organisation-based pages only accessible when organisation is selected
|
|
353
|
+
- [ ] Navigation filters correctly based on current context
|
|
354
|
+
- [ ] Context selector shows all accessible orgs and events
|
|
355
|
+
- [ ] Permission checks work correctly for each scope type
|
|
356
|
+
- [ ] 'both' pages return union of permissions
|
|
357
|
+
- [ ] Super admins can access all pages
|
|
358
|
+
- [ ] Users with both org and event roles see appropriate pages
|
|
359
|
+
|
|
360
|
+
## Troubleshooting
|
|
361
|
+
|
|
362
|
+
### Pages not showing in navigation
|
|
363
|
+
|
|
364
|
+
1. Check page `scope_type` matches current context
|
|
365
|
+
2. Verify user has permissions for the page
|
|
366
|
+
3. Ensure page exists in `rbac_app_pages` table
|
|
367
|
+
4. Check page permissions are configured in `rbac_page_permissions`
|
|
368
|
+
|
|
369
|
+
### Permission denied errors
|
|
370
|
+
|
|
371
|
+
1. Verify user has the required role for the page
|
|
372
|
+
2. Check page `scope_type` matches selected context
|
|
373
|
+
3. Ensure permissions are configured for the correct organisation/event
|
|
374
|
+
4. Verify user's roles are active (valid_from/valid_to dates)
|
|
375
|
+
|
|
376
|
+
### Context selector not showing items
|
|
377
|
+
|
|
378
|
+
1. Check user has organisation memberships or event-app roles
|
|
379
|
+
2. Verify organisations/events are active
|
|
380
|
+
3. Check RLS policies allow user to see the items
|
|
381
|
+
4. Ensure super admin status if testing as super admin
|
|
382
|
+
|
|
383
|
+
## Related Documentation
|
|
384
|
+
|
|
385
|
+
- [RBAC Scope Migration Guide](../migration/RBAC_SCOPE_MIGRATION.md) - Complete migration guide
|
|
386
|
+
- [Context Selector Component](../components/context-selector.md) - Unified selector documentation
|
|
387
|
+
- [RBAC System Overview](../rbac/README.md)
|
|
388
|
+
- [Event-Based Apps](../rbac/event-based-apps.md)
|
|
389
|
+
- [Navigation Filtering](../components/NavigationMenu.md)
|
|
390
|
+
- [Page Permission Guard](../rbac/components/PagePermissionGuard.md)
|
|
391
|
+
|