@jmruthers/pace-core 0.5.113 → 0.5.114
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/dist/{DataTable-YNEB5WDK.js → DataTable-3JRLZXER.js} +2 -2
- package/dist/{chunk-YUKG6PXE.js → chunk-5YIZFEUQ.js} +13 -5
- package/dist/chunk-5YIZFEUQ.js.map +1 -0
- package/dist/{chunk-KISJX3XZ.js → chunk-JHWQNJP3.js} +4 -3
- package/dist/{chunk-KISJX3XZ.js.map → chunk-JHWQNJP3.js.map} +1 -1
- package/dist/components.js +2 -2
- package/dist/index.js +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- 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 +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +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 +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- 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/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +4 -4
- package/docs/architecture/rpc-function-standards.md +131 -11
- package/package.json +1 -1
- package/src/components/Dialog/Dialog.tsx +3 -2
- package/src/components/NavigationMenu/NavigationMenu.tsx +29 -5
- package/dist/chunk-YUKG6PXE.js.map +0 -1
- /package/dist/{DataTable-YNEB5WDK.js.map → DataTable-3JRLZXER.js.map} +0 -0
package/docs/api/modules.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
[@jmruthers/pace-core - v0.5.
|
|
1
|
+
[@jmruthers/pace-core - v0.5.114](README.md) / Exports
|
|
2
2
|
|
|
3
|
-
# @jmruthers/pace-core - v0.5.
|
|
3
|
+
# @jmruthers/pace-core - v0.5.114
|
|
4
4
|
|
|
5
5
|
**`File`**
|
|
6
6
|
|
|
@@ -1572,7 +1572,7 @@ ___
|
|
|
1572
1572
|
|
|
1573
1573
|
#### Defined in
|
|
1574
1574
|
|
|
1575
|
-
[packages/core/src/components/Dialog/Dialog.tsx:
|
|
1575
|
+
[packages/core/src/components/Dialog/Dialog.tsx:707](https://github.com/jmruthers/pace-core/blob/main/packages/core/src/components/Dialog/Dialog.tsx#L707)
|
|
1576
1576
|
|
|
1577
1577
|
___
|
|
1578
1578
|
|
|
@@ -1592,7 +1592,7 @@ ___
|
|
|
1592
1592
|
|
|
1593
1593
|
#### Defined in
|
|
1594
1594
|
|
|
1595
|
-
[packages/core/src/components/Dialog/Dialog.tsx:
|
|
1595
|
+
[packages/core/src/components/Dialog/Dialog.tsx:742](https://github.com/jmruthers/pace-core/blob/main/packages/core/src/components/Dialog/Dialog.tsx#L742)
|
|
1596
1596
|
|
|
1597
1597
|
___
|
|
1598
1598
|
|
|
@@ -120,13 +120,16 @@ BEGIN
|
|
|
120
120
|
|
|
121
121
|
-- 4. Dynamic RBAC permission check (replaces manual organisation/event checks)
|
|
122
122
|
-- Use rbac_check_permission_simplified() for consistency with RLS policies
|
|
123
|
+
-- ⚠️ CRITICAL: Permission format must match what rbac_permissions_get returns
|
|
124
|
+
-- Format: 'operation:page.{pageName}' (e.g., 'read:page.dishes', 'create:page.meals')
|
|
125
|
+
-- The permission string is passed to rbac_permissions_get and must match exactly
|
|
123
126
|
IF NOT rbac_check_permission_simplified(
|
|
124
127
|
COALESCE(p_user_id, auth.uid()),
|
|
125
|
-
'read:
|
|
128
|
+
'read:page.dishes', -- Format: operation:page.{pageName} - MUST match rbac_permissions_get output
|
|
126
129
|
p_organisation_id,
|
|
127
|
-
|
|
130
|
+
p_event_id, -- p_event_id if event-scoped, NULL if org-scoped
|
|
128
131
|
(SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1), -- p_app_id
|
|
129
|
-
'
|
|
132
|
+
'dishes' -- p_page_id (page name from rbac_app_pages)
|
|
130
133
|
) THEN
|
|
131
134
|
RETURN; -- No permission
|
|
132
135
|
END IF;
|
|
@@ -258,13 +261,15 @@ BEGIN
|
|
|
258
261
|
END IF;
|
|
259
262
|
|
|
260
263
|
-- Use rbac_check_permission_simplified for dynamic RBAC checking
|
|
264
|
+
-- ⚠️ CRITICAL: Permission format must be 'operation:page.{pageName}'
|
|
265
|
+
-- This matches the format returned by rbac_permissions_get()
|
|
261
266
|
IF NOT rbac_check_permission_simplified(
|
|
262
267
|
p_user_id,
|
|
263
|
-
'read:meals',
|
|
268
|
+
'read:page.meals', -- Format: operation:page.{pageName}
|
|
264
269
|
p_organisation_id,
|
|
265
270
|
p_event_id,
|
|
266
271
|
(SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
|
|
267
|
-
'meals'
|
|
272
|
+
'meals' -- Page name (resolved to page ID internally)
|
|
268
273
|
) THEN
|
|
269
274
|
RETURN; -- No permission
|
|
270
275
|
END IF;
|
|
@@ -376,11 +381,11 @@ CREATE POLICY "rbac_cake_meal_select" ON cake_meal
|
|
|
376
381
|
is_user_super_admin()
|
|
377
382
|
OR rbac_check_permission_simplified(
|
|
378
383
|
auth.uid(),
|
|
379
|
-
'read:meals',
|
|
384
|
+
'read:page.meals', -- Format: operation:page.{pageName}
|
|
380
385
|
organisation_id,
|
|
381
386
|
meal_event_id,
|
|
382
387
|
(SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
|
|
383
|
-
'meals'
|
|
388
|
+
'meals' -- Page name
|
|
384
389
|
)
|
|
385
390
|
)
|
|
386
391
|
);
|
|
@@ -396,11 +401,11 @@ BEGIN
|
|
|
396
401
|
v_is_super_admin OR
|
|
397
402
|
rbac_check_permission_simplified(
|
|
398
403
|
p_user_id,
|
|
399
|
-
'read:meals',
|
|
404
|
+
'read:page.meals', -- Format: operation:page.{pageName}
|
|
400
405
|
p_organisation_id,
|
|
401
406
|
p_event_id,
|
|
402
407
|
(SELECT id FROM rbac_apps WHERE name = 'CAKE' LIMIT 1),
|
|
403
|
-
'meals'
|
|
408
|
+
'meals' -- Page name
|
|
404
409
|
)
|
|
405
410
|
) THEN
|
|
406
411
|
RETURN;
|
|
@@ -598,11 +603,91 @@ SELECT * FROM data_user_events_get(NULL, 'user-id', 'org-123');
|
|
|
598
603
|
-- Should return empty gracefully
|
|
599
604
|
```
|
|
600
605
|
|
|
606
|
+
## Permission Format
|
|
607
|
+
|
|
608
|
+
### ⚠️ CRITICAL: Correct Permission Format
|
|
609
|
+
|
|
610
|
+
**The permission string format is critical and must match exactly what `rbac_permissions_get()` returns.**
|
|
611
|
+
|
|
612
|
+
#### Correct Format
|
|
613
|
+
|
|
614
|
+
```sql
|
|
615
|
+
-- ✅ CORRECT: Format is 'operation:page.{pageName}'
|
|
616
|
+
rbac_check_permission_simplified(
|
|
617
|
+
p_user_id,
|
|
618
|
+
'create:page.dishes', -- Format: operation:page.{pageName}
|
|
619
|
+
p_organisation_id,
|
|
620
|
+
p_event_id,
|
|
621
|
+
v_app_id,
|
|
622
|
+
'dishes' -- Page name (resolved to page ID internally)
|
|
623
|
+
)
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### Common Mistakes
|
|
627
|
+
|
|
628
|
+
```sql
|
|
629
|
+
-- ❌ WRONG: Missing 'page.' prefix
|
|
630
|
+
'create:dishes'
|
|
631
|
+
|
|
632
|
+
-- ❌ WRONG: Wrong separator
|
|
633
|
+
'create:page/dishes'
|
|
634
|
+
|
|
635
|
+
-- ❌ WRONG: Wrong format entirely
|
|
636
|
+
'dishes:create'
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### How to Determine the Correct Format
|
|
640
|
+
|
|
641
|
+
1. **Check existing migrations** - Look at recent migrations that work
|
|
642
|
+
2. **Check RLS policies** - Use the same format as RLS policies for the same resource
|
|
643
|
+
3. **Verify with rbac_permissions_get** - The permission string must match what this function returns in the `permission_type` column
|
|
644
|
+
|
|
645
|
+
#### Examples by Operation Type
|
|
646
|
+
|
|
647
|
+
```sql
|
|
648
|
+
-- Page-based permissions (most common)
|
|
649
|
+
'read:page.dishes' -- Read dishes page
|
|
650
|
+
'create:page.dishes' -- Create dishes
|
|
651
|
+
'update:page.dishes' -- Update dishes
|
|
652
|
+
'delete:page.dishes' -- Delete dishes
|
|
653
|
+
|
|
654
|
+
'read:page.meals' -- Read meals page
|
|
655
|
+
'read:page.distribution' -- Read distribution page
|
|
656
|
+
'read:page.deliveries' -- Read deliveries page
|
|
657
|
+
|
|
658
|
+
-- File-based permissions (special case)
|
|
659
|
+
'read:files' -- Read files
|
|
660
|
+
'create:files' -- Create files
|
|
661
|
+
'delete:files' -- Delete files
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
#### Parameter Guidelines
|
|
665
|
+
|
|
666
|
+
```sql
|
|
667
|
+
rbac_check_permission_simplified(
|
|
668
|
+
p_user_id, -- User ID (required)
|
|
669
|
+
'create:page.dishes', -- Permission string (format: operation:page.{pageName})
|
|
670
|
+
p_organisation_id, -- Organisation ID (required for non-super-admin)
|
|
671
|
+
p_event_id, -- Event ID (optional, for event-scoped permissions)
|
|
672
|
+
v_app_id, -- App ID (required, usually CAKE app)
|
|
673
|
+
'dishes' -- Page name (required, matches the page name in rbac_app_pages)
|
|
674
|
+
)
|
|
675
|
+
```
|
|
676
|
+
|
|
601
677
|
## Migration Guidelines
|
|
602
678
|
|
|
603
679
|
### Creating New Functions
|
|
604
680
|
|
|
605
|
-
1. **Create migration file**:
|
|
681
|
+
1. **Create migration file with correct timestamp**:
|
|
682
|
+
```bash
|
|
683
|
+
# Generate timestamp: YYYYMMDDHHMMSS
|
|
684
|
+
date +%Y%m%d%H%M%S
|
|
685
|
+
# Example output: 20251104113510
|
|
686
|
+
|
|
687
|
+
# Create file: supabase/migrations/20251104113510_descriptive_name.sql
|
|
688
|
+
```
|
|
689
|
+
⚠️ **CRITICAL**: Always generate a NEW timestamp for each migration. Never reuse timestamps from previous migrations, even if they haven't been pushed yet.
|
|
690
|
+
|
|
606
691
|
2. **Add function**: Use standard template above
|
|
607
692
|
3. **Add comments**: Document purpose, parameters, returns
|
|
608
693
|
4. **Add grants**: `GRANT EXECUTE ON FUNCTION ... TO authenticated;`
|
|
@@ -610,6 +695,28 @@ SELECT * FROM data_user_events_get(NULL, 'user-id', 'org-123');
|
|
|
610
695
|
6. **Regenerate types**: Run `npm run generate-types` to automatically generate TypeScript types from the database schema
|
|
611
696
|
7. **Verify types**: Check that `Database['public']['Functions']['function_name']` exists in `packages/core/src/types/database.generated.ts`
|
|
612
697
|
|
|
698
|
+
### ⚠️ Common Migration Mistakes
|
|
699
|
+
|
|
700
|
+
1. **Reusing old migration timestamps**:
|
|
701
|
+
- ❌ **WRONG**: Reusing `20251104113047` if it's already been pushed
|
|
702
|
+
- ✅ **CORRECT**: Generate new timestamp: `date +%Y%m%d%H%M%S` → `20251104113510`
|
|
703
|
+
- Always create a NEW file with a NEW timestamp for each migration
|
|
704
|
+
|
|
705
|
+
2. **Wrong permission format**:
|
|
706
|
+
- ❌ **WRONG**: `'create:dishes'` or `'read:meals'`
|
|
707
|
+
- ✅ **CORRECT**: `'create:page.dishes'` or `'read:page.meals'`
|
|
708
|
+
- Always use `'operation:page.{pageName}'` format, not `'operation:resource'`
|
|
709
|
+
- The format must match exactly what `rbac_permissions_get()` returns
|
|
710
|
+
|
|
711
|
+
3. **Missing page_id parameter**: Always provide the page name as the last parameter (e.g., `'dishes'`, `'meals'`, `'distribution'`)
|
|
712
|
+
|
|
713
|
+
4. **Ambiguous column references**:
|
|
714
|
+
- ❌ **WRONG**: `SELECT mealtype_name FROM cake_mealtype`
|
|
715
|
+
- ✅ **CORRECT**: `SELECT cake_mealtype.mealtype_name FROM cake_mealtype`
|
|
716
|
+
- Always fully qualify table names when there might be ambiguity
|
|
717
|
+
|
|
718
|
+
5. **Not checking existing migrations**: Before creating a new migration, check if a similar one already exists to avoid duplicates
|
|
719
|
+
|
|
613
720
|
### Updating Existing Functions
|
|
614
721
|
|
|
615
722
|
1. **Use `CREATE OR REPLACE FUNCTION`** - Ensures idempotency
|
|
@@ -735,6 +842,7 @@ $$;
|
|
|
735
842
|
|
|
736
843
|
When creating or reviewing an RPC function, ensure:
|
|
737
844
|
|
|
845
|
+
### Function Structure
|
|
738
846
|
- ✅ Follows naming convention: `<family>_<domain>_<verb>`
|
|
739
847
|
- ✅ Uses `SECURITY DEFINER`
|
|
740
848
|
- ✅ Sets `search_path TO 'public'`
|
|
@@ -742,7 +850,7 @@ When creating or reviewing an RPC function, ensure:
|
|
|
742
850
|
- ✅ Checks super_admin status (if applicable)
|
|
743
851
|
- ✅ Validates organisation membership (if applicable)
|
|
744
852
|
- ✅ Validates event access (if event-scoped)
|
|
745
|
-
- ✅ Uses proper error handling
|
|
853
|
+
- ✅ Uses proper error handling with `EXCEPTION WHEN OTHERS`
|
|
746
854
|
- ✅ Returns empty on error (fail-secure)
|
|
747
855
|
- ✅ Has documentation comments
|
|
748
856
|
- ✅ Uses descriptive parameter names (`p_` prefix)
|
|
@@ -751,6 +859,18 @@ When creating or reviewing an RPC function, ensure:
|
|
|
751
859
|
- ✅ Includes `GRANT EXECUTE` statement
|
|
752
860
|
- ✅ Tested with various scenarios
|
|
753
861
|
|
|
862
|
+
### ⚠️ Critical Permission Format
|
|
863
|
+
- ✅ **Permission format is correct**: `'operation:page.{pageName}'` (e.g., `'create:page.dishes'`, `'read:page.meals'`)
|
|
864
|
+
- ✅ **NOT using wrong format**: `'operation:resource'` (e.g., `'create:dishes'`, `'read:meals'`)
|
|
865
|
+
- ✅ **Page name parameter provided**: Last parameter is the page name (e.g., `'dishes'`, `'meals'`)
|
|
866
|
+
- ✅ **Permission format matches rbac_permissions_get output**: Verify by checking existing working migrations
|
|
867
|
+
|
|
868
|
+
### ⚠️ Migration File Requirements
|
|
869
|
+
- ✅ **New timestamp generated**: Run `date +%Y%m%d%H%M%S` to get current timestamp
|
|
870
|
+
- ✅ **New file created**: Never reuse timestamps from previous migrations
|
|
871
|
+
- ✅ **File naming**: `YYYYMMDDHHMMSS_descriptive_name.sql`
|
|
872
|
+
- ✅ **No ambiguous column references**: Fully qualify table names (e.g., `cake_mealtype.mealtype_name`)
|
|
873
|
+
|
|
754
874
|
## References
|
|
755
875
|
|
|
756
876
|
- [RBAC System Documentation](../core-concepts/rbac-system.md)
|
package/package.json
CHANGED
|
@@ -694,8 +694,9 @@ const DialogFooter = ({
|
|
|
694
694
|
}: DialogFooterProps) => (
|
|
695
695
|
<footer
|
|
696
696
|
className={cn(
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
// Only apply default layout classes if no custom className is provided
|
|
698
|
+
!className && 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
|
699
|
+
!className && (sticky ? 'sticky bottom-0 z-10 bg-background p-6 pt-4 border-t' : 'p-6 pt-4'),
|
|
699
700
|
className
|
|
700
701
|
)}
|
|
701
702
|
{...props}
|
|
@@ -448,18 +448,22 @@ export const NavigationMenu = React.forwardRef<
|
|
|
448
448
|
|
|
449
449
|
// Stabilize scope object to prevent unnecessary permission refetches
|
|
450
450
|
// This prevents the permission map from being cleared when scope object reference changes
|
|
451
|
+
// IMPORTANT: Only update stable scope when scope is resolved (not loading) to prevent
|
|
452
|
+
// clearing permissions during event/scope transitions
|
|
451
453
|
const stableScopeRef = React.useRef<{ organisationId: string; eventId?: string; appId?: string }>({
|
|
452
454
|
organisationId: '',
|
|
453
455
|
eventId: undefined,
|
|
454
456
|
appId: undefined
|
|
455
457
|
});
|
|
456
458
|
|
|
457
|
-
// Only update stable scope
|
|
458
|
-
|
|
459
|
+
// Only update stable scope when scope is resolved (not loading) AND values actually changed
|
|
460
|
+
// This prevents triggering permission refetches during scope transitions
|
|
461
|
+
if (!scopeLoading && resolvedScope?.organisationId) {
|
|
459
462
|
const newOrgId = resolvedScope.organisationId;
|
|
460
463
|
const newEventId = resolvedScope.eventId;
|
|
461
464
|
const newAppId = resolvedScope.appId;
|
|
462
465
|
|
|
466
|
+
// Only update if values actually changed
|
|
463
467
|
if (stableScopeRef.current.organisationId !== newOrgId ||
|
|
464
468
|
stableScopeRef.current.eventId !== newEventId ||
|
|
465
469
|
stableScopeRef.current.appId !== newAppId) {
|
|
@@ -469,8 +473,8 @@ export const NavigationMenu = React.forwardRef<
|
|
|
469
473
|
appId: newAppId
|
|
470
474
|
};
|
|
471
475
|
}
|
|
472
|
-
} else if (!resolvedScope) {
|
|
473
|
-
// Only reset if we had a previous value - don't clear
|
|
476
|
+
} else if (!scopeLoading && !resolvedScope) {
|
|
477
|
+
// Only reset if we had a previous value and scope is resolved - don't clear during loading
|
|
474
478
|
if (stableScopeRef.current.organisationId !== '') {
|
|
475
479
|
stableScopeRef.current = {
|
|
476
480
|
organisationId: '',
|
|
@@ -479,6 +483,7 @@ export const NavigationMenu = React.forwardRef<
|
|
|
479
483
|
};
|
|
480
484
|
}
|
|
481
485
|
}
|
|
486
|
+
// If scopeLoading is true, keep using the previous stable scope to prevent clearing permissions
|
|
482
487
|
|
|
483
488
|
const stableScope = stableScopeRef.current;
|
|
484
489
|
|
|
@@ -491,15 +496,31 @@ export const NavigationMenu = React.forwardRef<
|
|
|
491
496
|
|
|
492
497
|
// NEW: Phase 2 - Enhanced Security Features
|
|
493
498
|
// Filter navigation items based on permissions using RBAC hooks
|
|
499
|
+
// Use ref to preserve previous filtered items during permission refetches (e.g., when event changes)
|
|
500
|
+
const previousFilteredItemsRef = React.useRef<NavigationItem[]>([]);
|
|
501
|
+
|
|
494
502
|
const filteredItems = React.useMemo(() => {
|
|
495
503
|
// Security: If filtering is enabled but we're missing required context or still loading, show NO items
|
|
496
504
|
// This prevents security risk of showing items before permissions are verified
|
|
497
505
|
if (filterByPermissions) {
|
|
498
|
-
|
|
506
|
+
// During initial load or when scope is loading, show nothing
|
|
507
|
+
if (!authContext || !rbacContext || scopeLoading || !resolvedScope?.organisationId) {
|
|
499
508
|
// Still loading - show nothing to prevent security risk
|
|
500
509
|
return [];
|
|
501
510
|
}
|
|
502
511
|
|
|
512
|
+
// During permission refetch (after initial load), preserve previous items if we have them
|
|
513
|
+
// This prevents navigation from disappearing when switching events
|
|
514
|
+
if (permissionsLoading) {
|
|
515
|
+
// If we have previous items, keep showing them during refetch
|
|
516
|
+
// This prevents the navigation from disappearing when event changes
|
|
517
|
+
if (previousFilteredItemsRef.current.length > 0) {
|
|
518
|
+
return previousFilteredItemsRef.current;
|
|
519
|
+
}
|
|
520
|
+
// Otherwise, show nothing during initial load
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
|
|
503
524
|
// If there's an error or empty permission map after loading, show nothing
|
|
504
525
|
// Security: Better to show nothing than risk showing unauthorized items
|
|
505
526
|
if (permissionsError || !permissionMap || Object.keys(permissionMap).length === 0) {
|
|
@@ -675,6 +696,9 @@ export const NavigationMenu = React.forwardRef<
|
|
|
675
696
|
.map(item => filterItem(item))
|
|
676
697
|
.filter((item): item is NavigationItem => item !== null);
|
|
677
698
|
|
|
699
|
+
// Update the ref with the new filtered items so we can preserve them during refetches
|
|
700
|
+
previousFilteredItemsRef.current = filtered;
|
|
701
|
+
|
|
678
702
|
return filtered;
|
|
679
703
|
}, [
|
|
680
704
|
items,
|