@jmruthers/pace-core 0.5.14 → 0.5.16

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.
Files changed (116) hide show
  1. package/dist/{DataTable-NNCMQSDG.js → DataTable-RICY7YDA.js} +3 -3
  2. package/dist/{chunk-AMOT5ZSZ.js → chunk-F6IHN3DC.js} +57 -17
  3. package/dist/chunk-F6IHN3DC.js.map +1 -0
  4. package/dist/{chunk-BUWLPWDA.js → chunk-JPXJGMOO.js} +2 -2
  5. package/dist/{chunk-PILT65PA.js → chunk-JZCNOXSG.js} +2 -2
  6. package/dist/{chunk-FBBET5X5.js → chunk-V7ZZCH3G.js} +19 -14
  7. package/dist/chunk-V7ZZCH3G.js.map +1 -0
  8. package/dist/components.js +3 -3
  9. package/dist/index.js +4 -4
  10. package/dist/rbac/index.js +2 -2
  11. package/dist/utils.js +1 -1
  12. package/docs/api/classes/ErrorBoundary.md +1 -1
  13. package/docs/api/classes/InvalidScopeError.md +1 -1
  14. package/docs/api/classes/MissingUserContextError.md +1 -1
  15. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  16. package/docs/api/classes/PermissionDeniedError.md +1 -1
  17. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  18. package/docs/api/classes/RBACAuditManager.md +1 -1
  19. package/docs/api/classes/RBACCache.md +1 -1
  20. package/docs/api/classes/RBACEngine.md +1 -1
  21. package/docs/api/classes/RBACError.md +1 -1
  22. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  23. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  24. package/docs/api/interfaces/AggregateConfig.md +1 -1
  25. package/docs/api/interfaces/ButtonProps.md +1 -1
  26. package/docs/api/interfaces/CardProps.md +1 -1
  27. package/docs/api/interfaces/ColorPalette.md +1 -1
  28. package/docs/api/interfaces/ColorShade.md +1 -1
  29. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  30. package/docs/api/interfaces/DataTableAction.md +1 -1
  31. package/docs/api/interfaces/DataTableColumn.md +1 -1
  32. package/docs/api/interfaces/DataTableProps.md +1 -1
  33. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  34. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  35. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  36. package/docs/api/interfaces/EventContextType.md +1 -1
  37. package/docs/api/interfaces/EventLogoProps.md +1 -1
  38. package/docs/api/interfaces/EventProviderProps.md +1 -1
  39. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  40. package/docs/api/interfaces/FileUploadProps.md +1 -1
  41. package/docs/api/interfaces/FooterProps.md +1 -1
  42. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  43. package/docs/api/interfaces/InputProps.md +1 -1
  44. package/docs/api/interfaces/LabelProps.md +1 -1
  45. package/docs/api/interfaces/LoginFormProps.md +1 -1
  46. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  47. package/docs/api/interfaces/NavigationContextType.md +1 -1
  48. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  49. package/docs/api/interfaces/NavigationItem.md +1 -1
  50. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  51. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  52. package/docs/api/interfaces/Organisation.md +1 -1
  53. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  54. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  55. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  56. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  57. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  58. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  59. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  60. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  61. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  62. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  63. package/docs/api/interfaces/PaletteData.md +1 -1
  64. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  65. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  66. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  67. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  68. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  69. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  70. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  71. package/docs/api/interfaces/RBACConfig.md +1 -1
  72. package/docs/api/interfaces/RBACContextType.md +1 -1
  73. package/docs/api/interfaces/RBACLogger.md +1 -1
  74. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  75. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  76. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  77. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  78. package/docs/api/interfaces/RouteConfig.md +1 -1
  79. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  80. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  81. package/docs/api/interfaces/StorageConfig.md +1 -1
  82. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  83. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  84. package/docs/api/interfaces/StorageListOptions.md +1 -1
  85. package/docs/api/interfaces/StorageListResult.md +1 -1
  86. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  87. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  88. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  89. package/docs/api/interfaces/StyleImport.md +1 -1
  90. package/docs/api/interfaces/ToastActionElement.md +1 -1
  91. package/docs/api/interfaces/ToastProps.md +1 -1
  92. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  93. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  94. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  95. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  96. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  97. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  98. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  99. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  100. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  101. package/docs/api/interfaces/UserEventAccess.md +1 -1
  102. package/docs/api/interfaces/UserMenuProps.md +1 -1
  103. package/docs/api/interfaces/UserProfile.md +1 -1
  104. package/docs/api/modules.md +8 -8
  105. package/package.json +1 -1
  106. package/src/rbac/components/PagePermissionGuard.tsx +20 -13
  107. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +478 -0
  108. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +157 -0
  109. package/src/rbac/hooks/useCan.test.ts +1 -1
  110. package/src/rbac/hooks/usePermissions.test.ts +5 -8
  111. package/src/rbac/hooks/usePermissions.ts +74 -22
  112. package/dist/chunk-AMOT5ZSZ.js.map +0 -1
  113. package/dist/chunk-FBBET5X5.js.map +0 -1
  114. /package/dist/{DataTable-NNCMQSDG.js.map → DataTable-RICY7YDA.js.map} +0 -0
  115. /package/dist/{chunk-BUWLPWDA.js.map → chunk-JPXJGMOO.js.map} +0 -0
  116. /package/dist/{chunk-PILT65PA.js.map → chunk-JZCNOXSG.js.map} +0 -0
@@ -32,8 +32,8 @@ import {
32
32
  useDataTableContext,
33
33
  usePluginRegistry,
34
34
  useStateManager
35
- } from "./chunk-BUWLPWDA.js";
36
- import "./chunk-AMOT5ZSZ.js";
35
+ } from "./chunk-JPXJGMOO.js";
36
+ import "./chunk-F6IHN3DC.js";
37
37
  import "./chunk-GWSBHC4J.js";
38
38
  import "./chunk-7BNPOCLL.js";
39
39
  import "./chunk-7UEIZCST.js";
@@ -96,4 +96,4 @@ export {
96
96
  usePluginRegistry,
97
97
  useStateManager
98
98
  };
99
- //# sourceMappingURL=DataTable-NNCMQSDG.js.map
99
+ //# sourceMappingURL=DataTable-RICY7YDA.js.map
@@ -157,18 +157,69 @@ function useRBAC(pageId) {
157
157
  }
158
158
 
159
159
  // src/rbac/hooks/usePermissions.ts
160
- import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
160
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef } from "react";
161
161
  function usePermissions(userId, scope) {
162
162
  const [permissions, setPermissions] = useState2({});
163
163
  const [isLoading, setIsLoading] = useState2(true);
164
164
  const [error, setError] = useState2(null);
165
- const fetchPermissions = useCallback2(async () => {
165
+ const isFetchingRef = useRef(false);
166
+ useEffect2(() => {
167
+ const fetchPermissions = async () => {
168
+ if (isFetchingRef.current) {
169
+ return;
170
+ }
171
+ if (!userId) {
172
+ setPermissions({});
173
+ setIsLoading(false);
174
+ return;
175
+ }
176
+ if (!scope.organisationId || scope.organisationId.trim() === "") {
177
+ setPermissions({});
178
+ setIsLoading(true);
179
+ setError(null);
180
+ return;
181
+ }
182
+ try {
183
+ isFetchingRef.current = true;
184
+ setIsLoading(true);
185
+ setError(null);
186
+ const permissionMap = await getPermissionMap({ userId, scope });
187
+ setPermissions(permissionMap);
188
+ } catch (err) {
189
+ setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
190
+ } finally {
191
+ setIsLoading(false);
192
+ isFetchingRef.current = false;
193
+ }
194
+ };
195
+ fetchPermissions();
196
+ }, [userId, scope.organisationId, scope.eventId, scope.appId]);
197
+ const hasPermission = useCallback2((permission) => {
198
+ return !!permissions[permission];
199
+ }, [permissions]);
200
+ const hasAnyPermission = useCallback2((permissionList) => {
201
+ return permissionList.some((p) => !!permissions[p]);
202
+ }, [permissions]);
203
+ const hasAllPermissions = useCallback2((permissionList) => {
204
+ return permissionList.every((p) => !!permissions[p]);
205
+ }, [permissions]);
206
+ const refetch = useCallback2(async () => {
207
+ if (isFetchingRef.current) {
208
+ return;
209
+ }
166
210
  if (!userId) {
167
211
  setPermissions({});
168
212
  setIsLoading(false);
169
213
  return;
170
214
  }
215
+ if (!scope.organisationId || scope.organisationId.trim() === "") {
216
+ setPermissions({});
217
+ setIsLoading(true);
218
+ setError(null);
219
+ return;
220
+ }
171
221
  try {
222
+ isFetchingRef.current = true;
172
223
  setIsLoading(true);
173
224
  setError(null);
174
225
  const permissionMap = await getPermissionMap({ userId, scope });
@@ -177,20 +228,9 @@ function usePermissions(userId, scope) {
177
228
  setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
178
229
  } finally {
179
230
  setIsLoading(false);
231
+ isFetchingRef.current = false;
180
232
  }
181
233
  }, [userId, scope]);
182
- useEffect2(() => {
183
- fetchPermissions();
184
- }, [fetchPermissions]);
185
- const hasPermission = useCallback2((permission) => {
186
- return !!permissions[permission];
187
- }, [permissions]);
188
- const hasAnyPermission = useCallback2((permissionList) => {
189
- return permissionList.some((p) => !!permissions[p]);
190
- }, [permissions]);
191
- const hasAllPermissions = useCallback2((permissionList) => {
192
- return permissionList.every((p) => !!permissions[p]);
193
- }, [permissions]);
194
234
  return {
195
235
  permissions,
196
236
  isLoading,
@@ -198,7 +238,7 @@ function usePermissions(userId, scope) {
198
238
  hasPermission,
199
239
  hasAnyPermission,
200
240
  hasAllPermissions,
201
- refetch: fetchPermissions
241
+ refetch
202
242
  };
203
243
  }
204
244
  function useCan(userId, scope, permission, pageId, useCache = true) {
@@ -211,7 +251,7 @@ function useCan(userId, scope, permission, pageId, useCache = true) {
211
251
  setIsLoading(false);
212
252
  return;
213
253
  }
214
- if (!scope.organisationId) {
254
+ if (!scope.organisationId || scope.organisationId.trim() === "") {
215
255
  setCan(false);
216
256
  setIsLoading(true);
217
257
  return;
@@ -430,4 +470,4 @@ export {
430
470
  useHasAllPermissions,
431
471
  useCachedPermissions
432
472
  };
433
- //# sourceMappingURL=chunk-AMOT5ZSZ.js.map
473
+ //# sourceMappingURL=chunk-F6IHN3DC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rbac/hooks/useRBAC.ts","../src/rbac/hooks/usePermissions.ts"],"sourcesContent":["/**\n * @file RBAC Hook\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 0.3.0\n *\n * A React hook that provides access to the new RBAC (Role-Based Access Control) system.\n * This hook integrates with the database to provide real-time role and permission information.\n *\n * Features:\n * - Real-time role detection (global, organisation, event-app)\n * - Permission checking with database validation\n * - Hierarchical permission resolution\n * - Loading states and error handling\n * - Type-safe permission operations\n * - Automatic context detection\n *\n * @example\n * ```tsx\n * import { useRBAC } from '@jmruthers/pace-core/rbac';\n * \n * function MyComponent() {\n * const {\n * globalRole,\n * organisationRole,\n * eventAppRole,\n * hasPermission,\n * isSuperAdmin,\n * isLoading,\n * error\n * } = useRBAC();\n * \n * if (isLoading) return <div>Loading permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {isSuperAdmin && <AdminPanel />}\n * {hasPermission('read', 'dashboard') && <Dashboard />}\n * {hasPermission('create', 'events') && <CreateEventButton />}\n * </div>\n * );\n * }\n * ```\n *\n * @accessibility\n * - No direct accessibility concerns (hook)\n * - Enables accessible permission-based UI rendering\n * - Supports screen reader friendly conditional content\n *\n * @security\n * - Database-backed permission validation\n * - Hierarchical permission resolution\n * - Organisation context enforcement\n * - Real-time permission updates\n *\n * @performance\n * - Optimized with useMemo and useCallback\n * - Permission caching\n * - Minimal re-renders\n * - Lazy loading of permissions\n *\n * @dependencies\n * - React 18+ - Hooks and effects\n * - @supabase/supabase-js - Database integration\n * - RBAC types - Type definitions\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useUnifiedAuth } from '../../providers/UnifiedAuthProvider';\nimport { useOrganisations } from '../../providers/OrganisationProvider';\nimport { useEvents } from '../../providers/EventProvider';\nimport type { \n UserRBACContext, \n GlobalRole, \n OrganisationRole, \n EventAppRole, \n Operation,\n RBACPermission \n} from '../types';\n\nexport function useRBAC(pageId?: string): UserRBACContext {\n const { user, session, supabase, appName } = useUnifiedAuth();\n const { selectedOrganisation } = useOrganisations();\n \n // Try to get events context, but don't fail if not available\n let selectedEvent = null;\n try {\n const eventsContext = useEvents();\n selectedEvent = eventsContext.selectedEvent;\n } catch (error) {\n // EventProvider not available, continue without event context\n console.debug('useRBAC: EventProvider not available, continuing without event context');\n }\n \n // State\n const [globalRole, setGlobalRole] = useState<GlobalRole | null>(null);\n const [organisationRole, setOrganisationRole] = useState<OrganisationRole | null>(null);\n const [eventAppRole, setEventAppRole] = useState<EventAppRole | null>(null);\n const [permissions, setPermissions] = useState<RBACPermission[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Load RBAC context\n const loadRBACContext = useCallback(async () => {\n if (!user || !session || !supabase || !appName) {\n console.log('[useRBAC] Missing required dependencies, clearing RBAC state');\n setGlobalRole(null);\n setOrganisationRole(null);\n setEventAppRole(null);\n setPermissions([]);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n // First resolve app name to app_id\n const { data: appData, error: appError } = await supabase\n .from('rbac_apps')\n .select('id')\n .eq('name', appName)\n .eq('is_active', true)\n .single();\n\n if (appError || !appData) {\n console.warn('App not found or inactive:', appName);\n setIsLoading(false);\n return;\n }\n\n const { data, error: rpcError } = await supabase.rpc('get_rbac_permissions', {\n p_user_id: user.id,\n p_app_id: appData.id,\n p_event_id: selectedEvent?.event_id || null,\n p_organisation_id: selectedOrganisation?.id || null,\n p_page_id: pageId || null\n });\n\n if (rpcError) {\n throw new Error(`Failed to load RBAC permissions: ${rpcError.message}`);\n }\n\n if (data) {\n // Extract roles from permissions based on permission_type\n const globalPerms = data.filter((p: RBACPermission) => p.permission_type === 'all_permissions');\n const orgPerms = data.filter((p: RBACPermission) => p.permission_type === 'organisation_access');\n const eventPerms = data.filter((p: RBACPermission) => p.permission_type === 'event_app_access');\n\n // Set roles (take the highest permission for each type)\n setGlobalRole(globalPerms.length > 0 ? 'super_admin' : null);\n setOrganisationRole(orgPerms.length > 0 ? orgPerms[0].role_name as OrganisationRole : null);\n setEventAppRole(eventPerms.length > 0 ? eventPerms[0].role_name as EventAppRole : null);\n setPermissions(data);\n } else {\n setPermissions([]);\n }\n } catch (err) {\n console.error('RBAC Context Loading Error:', err);\n setError(err instanceof Error ? err : new Error('Unknown error loading RBAC context'));\n setPermissions([]);\n } finally {\n setIsLoading(false);\n }\n }, [user?.id, session, supabase, appName, selectedEvent?.event_id, selectedOrganisation?.id, pageId]);\n\n // Permission checking function\n const hasPermission = useCallback(async (operation: Operation, targetPageId?: string): Promise<boolean> => {\n if (!user || !session || !supabase || !appName) return false;\n\n // Super admin has all permissions\n if (globalRole === 'super_admin') {\n return true;\n }\n\n try {\n // First resolve app name to app_id\n const { data: appData, error: appError } = await supabase\n .from('rbac_apps')\n .select('id')\n .eq('name', appName)\n .eq('is_active', true)\n .single();\n\n if (appError || !appData) {\n console.warn('App not found or inactive:', appName);\n return false;\n }\n\n const { data, error } = await supabase.rpc('check_page_permission', {\n p_user_id: user.id,\n p_app_id: appData.id,\n p_page_id: targetPageId || pageId || 'default',\n p_operation: operation,\n p_event_id: selectedEvent?.event_id,\n p_organisation_id: selectedOrganisation?.id\n });\n\n if (error) {\n console.error('Permission check failed:', error);\n return false;\n }\n\n return data === true;\n } catch (err) {\n console.error('Permission check error:', err);\n return false;\n }\n }, [user, supabase, appName, selectedEvent, selectedOrganisation, pageId, globalRole]);\n\n // Simple permission check for global roles (synchronous)\n const hasGlobalPermission = useCallback((permission: string): boolean => {\n // Super admin has all permissions\n if (globalRole === 'super_admin') {\n return true;\n }\n \n // Check specific global roles\n if (permission === 'super_admin') {\n return globalRole === 'super_admin';\n }\n \n if (permission === 'org_admin') {\n return organisationRole === 'org_admin' || globalRole === 'super_admin';\n }\n \n return false;\n }, [globalRole, organisationRole]);\n\n // Computed properties\n const isSuperAdmin = useMemo(() => globalRole === 'super_admin', [globalRole]);\n const isOrgAdmin = useMemo(() => organisationRole === 'org_admin', [organisationRole]);\n const isEventAdmin = useMemo(() => eventAppRole === 'event_admin', [eventAppRole]);\n const canManageOrganisation = useMemo(() => \n isSuperAdmin || isOrgAdmin, [isSuperAdmin, isOrgAdmin]\n );\n const canManageEvent = useMemo(() => \n isSuperAdmin || isEventAdmin, [isSuperAdmin, isEventAdmin]\n );\n\n // Load context when dependencies change\n useEffect(() => {\n loadRBACContext();\n }, [loadRBACContext]);\n\n return {\n user, // Add user to the returned context\n globalRole,\n organisationRole,\n eventAppRole,\n hasPermission,\n hasGlobalPermission,\n isSuperAdmin,\n isOrgAdmin,\n isEventAdmin,\n canManageOrganisation,\n canManageEvent,\n isLoading,\n error\n };\n}\n","/**\n * @file RBAC Permission Hooks\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 1.0.0\n * \n * This module provides React hooks for RBAC functionality.\n */\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { \n UUID, \n Scope, \n Permission, \n PermissionMap\n} from '../types';\nimport { AccessLevel as AccessLevelType } from '../types';\nimport { \n getAccessLevel, \n getPermissionMap, \n isPermitted,\n isPermittedCached \n} from '../api';\n\n/**\n * Hook to get user's permissions in a scope\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @returns Permission state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = usePermissions(userId, scope);\n * \n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['read:users'] && <UserList />}\n * {permissions['create:users'] && <CreateUserButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions(userId: UUID, scope: Scope) {\n const [permissions, setPermissions] = useState<PermissionMap>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const isFetchingRef = useRef(false);\n\n useEffect(() => {\n const fetchPermissions = async () => {\n // Prevent multiple simultaneous fetches\n if (isFetchingRef.current) {\n return;\n }\n\n if (!userId) {\n setPermissions({});\n setIsLoading(false);\n return;\n }\n\n // Don't fetch permissions if scope is invalid (e.g., organisationId is empty)\n // Return empty permissions and loading true for invalid scopes to prevent premature access denied\n if (!scope.organisationId || scope.organisationId.trim() === '') {\n setPermissions({});\n setIsLoading(true);\n setError(null);\n return;\n }\n\n try {\n isFetchingRef.current = true;\n setIsLoading(true);\n setError(null);\n \n const permissionMap = await getPermissionMap({ userId, scope });\n setPermissions(permissionMap);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n } finally {\n setIsLoading(false);\n isFetchingRef.current = false;\n }\n };\n\n fetchPermissions();\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n const hasPermission = useCallback((permission: Permission): boolean => {\n return !!permissions[permission];\n }, [permissions]);\n\n const hasAnyPermission = useCallback((permissionList: Permission[]): boolean => {\n return permissionList.some(p => !!permissions[p]);\n }, [permissions]);\n\n const hasAllPermissions = useCallback((permissionList: Permission[]): boolean => {\n return permissionList.every(p => !!permissions[p]);\n }, [permissions]);\n\n const refetch = useCallback(async () => {\n // Prevent multiple simultaneous fetches\n if (isFetchingRef.current) {\n return;\n }\n\n if (!userId) {\n setPermissions({});\n setIsLoading(false);\n return;\n }\n\n // Don't fetch permissions if scope is invalid (e.g., organisationId is empty)\n if (!scope.organisationId || scope.organisationId.trim() === '') {\n setPermissions({});\n setIsLoading(true);\n setError(null);\n return;\n }\n\n try {\n isFetchingRef.current = true;\n setIsLoading(true);\n setError(null);\n \n const permissionMap = await getPermissionMap({ userId, scope });\n setPermissions(permissionMap);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n } finally {\n setIsLoading(false);\n isFetchingRef.current = false;\n }\n }, [userId, scope]);\n\n return {\n permissions,\n isLoading,\n error,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n refetch\n };\n}\n\n/**\n * Hook to check if user can perform an action\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permission - Permission to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results\n * @returns Permission check state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { can, isLoading, error } = useCan(userId, scope, 'read:users');\n * \n * if (isLoading) return <div>Checking permission...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return can ? <UserList /> : <div>Access denied</div>;\n * }\n * ```\n */\nexport function useCan(\n userId: UUID, \n scope: Scope, \n permission: Permission, \n pageId?: UUID,\n useCache: boolean = true\n) {\n const [can, setCan] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkPermission = useCallback(async () => {\n if (!userId) {\n setCan(false);\n setIsLoading(false);\n return;\n }\n\n // Don't check permissions if scope is invalid (e.g., organisationId is empty)\n // Return can: false and loading: true for invalid scopes to prevent premature access denied\n if (!scope.organisationId || scope.organisationId.trim() === '') {\n setCan(false);\n setIsLoading(true);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const result = useCache \n ? await isPermittedCached({ userId, scope, permission, pageId })\n : await isPermitted({ userId, scope, permission, pageId });\n \n setCan(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permission'));\n setCan(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope, permission, pageId, useCache]);\n\n useEffect(() => {\n checkPermission();\n }, [checkPermission]);\n\n return {\n can,\n isLoading,\n error,\n refetch: checkPermission\n };\n}\n\n/**\n * Hook to get user's access level in a scope\n * \n * @param userId - User ID\n * @param scope - Scope for access level checking\n * @returns Access level state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { accessLevel, isLoading, error } = useAccessLevel(userId, scope);\n * \n * if (isLoading) return <div>Loading access level...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * Access Level: {accessLevel}\n * {accessLevel >= AccessLevel.ADMIN && <AdminPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAccessLevel(userId: UUID, scope: Scope): {\n accessLevel: AccessLevelType;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [accessLevel, setAccessLevel] = useState<AccessLevelType>('viewer');\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchAccessLevel = useCallback(async () => {\n if (!userId) {\n setAccessLevel('viewer');\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const level = await getAccessLevel({ userId, scope });\n setAccessLevel(level);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch access level'));\n setAccessLevel('viewer');\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope]);\n\n useEffect(() => {\n fetchAccessLevel();\n }, [fetchAccessLevel]);\n\n return {\n accessLevel,\n isLoading,\n error,\n refetch: fetchAccessLevel\n };\n}\n\n/**\n * Hook to check multiple permissions at once\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Multiple permission check results\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { results, isLoading, error } = useMultiplePermissions(\n * userId, \n * scope, \n * ['read:users', 'create:users', 'update:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {results['read:users'] && <UserList />}\n * {results['create:users'] && <CreateUserButton />}\n * {results['update:users'] && <EditUserButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMultiplePermissions(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n results: Record<Permission, boolean>;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [results, setResults] = useState<Record<Permission, boolean>>({} as Record<Permission, boolean>);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setResults({} as Record<Permission, boolean>);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const permissionResults: Record<Permission, boolean> = {} as Record<Permission, boolean>;\n \n // Check each permission\n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n permissionResults[permission] = result;\n }\n \n setResults(permissionResults);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setResults({} as Record<Permission, boolean>);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope, permissions, useCache]);\n\n useEffect(() => {\n checkPermissions();\n }, [checkPermissions]);\n\n return {\n results,\n isLoading,\n error,\n refetch: checkPermissions\n };\n}\n\n/**\n * Hook to check if user has any of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Whether user has any of the permissions\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAny, isLoading, error } = useHasAnyPermission(\n * userId, \n * scope, \n * ['read:users', 'create:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return hasAny ? <UserManagementPanel /> : <div>No user permissions</div>;\n * }\n * ```\n */\nexport function useHasAnyPermission(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n hasAny: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [hasAny, setHasAny] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkAnyPermission = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setHasAny(false);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n let hasAnyPermission = false;\n \n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n \n if (result) {\n hasAnyPermission = true;\n break;\n }\n }\n \n setHasAny(hasAnyPermission);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setHasAny(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope, permissions, useCache]);\n\n useEffect(() => {\n checkAnyPermission();\n }, [checkAnyPermission]);\n\n return {\n hasAny,\n isLoading,\n error,\n refetch: checkAnyPermission\n };\n}\n\n/**\n * Hook to check if user has all of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Whether user has all of the permissions\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAll, isLoading, error } = useHasAllPermissions(\n * userId, \n * scope, \n * ['read:users', 'create:users', 'update:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return hasAll ? <FullUserManagementPanel /> : <div>Insufficient permissions</div>;\n * }\n * ```\n */\nexport function useHasAllPermissions(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n hasAll: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [hasAll, setHasAll] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkAllPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setHasAll(false);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n let hasAllPermissions = true;\n \n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n \n if (!result) {\n hasAllPermissions = false;\n break;\n }\n }\n \n setHasAll(hasAllPermissions);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setHasAll(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope, permissions, useCache]);\n\n useEffect(() => {\n checkAllPermissions();\n }, [checkAllPermissions]);\n\n return {\n hasAll,\n isLoading,\n error,\n refetch: checkAllPermissions\n };\n}\n\n/**\n * Hook to get cached permissions with TTL management\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @returns Cached permission state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error, invalidateCache } = useCachedPermissions(userId, scope);\n * \n * if (isLoading) return <div>Loading cached permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['read:users'] && <UserList />}\n * <button onClick={invalidateCache}>Refresh Permissions</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCachedPermissions(userId: UUID, scope: Scope): {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\n invalidateCache: () => void;\n refetch: () => Promise<void>;\n} {\n const [permissions, setPermissions] = useState<PermissionMap>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchCachedPermissions = useCallback(async () => {\n if (!userId) {\n setPermissions({});\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const permissionMap = await getPermissionMap({ userId, scope });\n setPermissions(permissionMap);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch cached permissions'));\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope]);\n\n const invalidateCache = useCallback(() => {\n // This would typically invalidate the cache in the actual implementation\n // For now, we'll just refetch\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n useEffect(() => {\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n return {\n permissions,\n isLoading,\n error,\n invalidateCache,\n refetch: fetchCachedPermissions\n };\n}\n"],"mappings":"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","names":["error","useState","useEffect","useCallback","useState","useEffect","useCallback"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useCan
3
- } from "./chunk-AMOT5ZSZ.js";
3
+ } from "./chunk-F6IHN3DC.js";
4
4
  import {
5
5
  useDataTablePerformance
6
6
  } from "./chunk-SS3E6QLB.js";
@@ -5465,4 +5465,4 @@ export {
5465
5465
  GroupHeader,
5466
5466
  EditableRow
5467
5467
  };
5468
- //# sourceMappingURL=chunk-BUWLPWDA.js.map
5468
+ //# sourceMappingURL=chunk-JPXJGMOO.js.map
@@ -15,7 +15,7 @@ import {
15
15
  SelectSeparator,
16
16
  SelectTrigger,
17
17
  SelectValue
18
- } from "./chunk-BUWLPWDA.js";
18
+ } from "./chunk-JPXJGMOO.js";
19
19
  import {
20
20
  isPermitted
21
21
  } from "./chunk-GWSBHC4J.js";
@@ -3290,4 +3290,4 @@ export {
3290
3290
  PublicPageDiagnostic,
3291
3291
  PublicPageContextChecker
3292
3292
  };
3293
- //# sourceMappingURL=chunk-PILT65PA.js.map
3293
+ //# sourceMappingURL=chunk-JZCNOXSG.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  useAccessLevel,
3
3
  useCan
4
- } from "./chunk-AMOT5ZSZ.js";
4
+ } from "./chunk-F6IHN3DC.js";
5
5
  import {
6
6
  OrganisationContextRequiredError,
7
7
  RBACCache,
@@ -245,7 +245,7 @@ function usePagePermissions() {
245
245
  }
246
246
 
247
247
  // src/rbac/components/PagePermissionGuard.tsx
248
- import { useMemo as useMemo2, useEffect as useEffect2, useState as useState2 } from "react";
248
+ import { useMemo as useMemo2, useEffect as useEffect2, useState as useState2, useRef } from "react";
249
249
  init_UnifiedAuthProvider();
250
250
 
251
251
  // src/rbac/utils/eventContext.ts
@@ -287,6 +287,8 @@ function PagePermissionGuard({
287
287
  const [hasChecked, setHasChecked] = useState2(false);
288
288
  const [checkError, setCheckError] = useState2(null);
289
289
  const [resolvedScope, setResolvedScope] = useState2(null);
290
+ const supabaseRef = useRef(supabase);
291
+ supabaseRef.current = supabase;
290
292
  useEffect2(() => {
291
293
  const resolveScope = async () => {
292
294
  if (scope) {
@@ -294,15 +296,15 @@ function PagePermissionGuard({
294
296
  return;
295
297
  }
296
298
  let appId = void 0;
297
- if (supabase) {
299
+ if (supabaseRef.current) {
298
300
  const appName = getCurrentAppName();
299
301
  if (appName) {
300
302
  try {
301
303
  console.log("[PagePermissionGuard] Resolving app name to ID:", appName);
302
- const { data: app, error: error2 } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).eq("is_active", true).single();
304
+ const { data: app, error: error2 } = await supabaseRef.current.from("rbac_apps").select("id, name, is_active").eq("name", appName).eq("is_active", true).single();
303
305
  if (error2) {
304
306
  console.error("[PagePermissionGuard] Database error resolving app ID:", error2);
305
- const { data: inactiveApp } = await supabase.from("rbac_apps").select("id, name, is_active").eq("name", appName).single();
307
+ const { data: inactiveApp } = await supabaseRef.current.from("rbac_apps").select("id, name, is_active").eq("name", appName).single();
306
308
  if (inactiveApp) {
307
309
  console.error(`[PagePermissionGuard] App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
308
310
  } else {
@@ -381,9 +383,9 @@ function PagePermissionGuard({
381
383
  setCheckError(null);
382
384
  return;
383
385
  }
384
- if (selectedEventId && supabase) {
386
+ if (selectedEventId && supabaseRef.current) {
385
387
  try {
386
- const eventScope = await createScopeFromEvent(supabase, selectedEventId);
388
+ const eventScope = await createScopeFromEvent(supabaseRef.current, selectedEventId);
387
389
  if (!eventScope) {
388
390
  setCheckError(new Error("Could not resolve organization from event context"));
389
391
  setResolvedScope(null);
@@ -404,7 +406,7 @@ function PagePermissionGuard({
404
406
  setResolvedScope(null);
405
407
  };
406
408
  resolveScope();
407
- }, [scope, selectedOrganisationId, selectedEventId, supabase]);
409
+ }, [scope, selectedOrganisationId, selectedEventId]);
408
410
  const effectivePageId = useMemo2(() => {
409
411
  return pageId || pageName;
410
412
  }, [pageId, pageName]);
@@ -484,24 +486,27 @@ function PagePermissionGuard({
484
486
  resolvedScope: !!resolvedScope,
485
487
  checkError: checkError?.message,
486
488
  can,
489
+ canIsLoading,
490
+ shouldCheckPermissions,
487
491
  willShowLoading: isLoading || !resolvedScope,
488
492
  willShowError: !!checkError,
489
- willShowAccessDenied: !can,
493
+ willShowAccessDenied: !isLoading && !!resolvedScope && !checkError && !can,
490
494
  willShowContent: !isLoading && !!resolvedScope && !checkError && can,
491
495
  scopeKey: resolvedScope ? `${resolvedScope.organisationId}-${resolvedScope.eventId}-${resolvedScope.appId}` : "no-scope"
492
496
  });
493
497
  const scopeKey = resolvedScope ? `${resolvedScope.organisationId}-${resolvedScope.eventId}-${resolvedScope.appId}` : "no-scope";
498
+ const permissionKey = `${scopeKey}-${can}-${isLoading}-${!!checkError}`;
494
499
  if (isLoading || !resolvedScope) {
495
- return /* @__PURE__ */ jsx2("div", { children: loading }, `loading-${scopeKey}`);
500
+ return /* @__PURE__ */ jsx2("div", { children: loading }, `loading-${permissionKey}`);
496
501
  }
497
502
  if (checkError) {
498
503
  console.error(`[PagePermissionGuard] Permission check failed for page ${pageName}:`, checkError);
499
- return /* @__PURE__ */ jsx2("div", { children: fallback }, `error-${scopeKey}`);
504
+ return /* @__PURE__ */ jsx2("div", { children: fallback }, `error-${permissionKey}`);
500
505
  }
501
506
  if (!can) {
502
- return /* @__PURE__ */ jsx2("div", { children: fallback }, `denied-${scopeKey}`);
507
+ return /* @__PURE__ */ jsx2("div", { children: fallback }, `denied-${permissionKey}`);
503
508
  }
504
- return /* @__PURE__ */ jsx2("div", { children }, `content-${scopeKey}`);
509
+ return /* @__PURE__ */ jsx2("div", { children }, `content-${permissionKey}`);
505
510
  }
506
511
  function DefaultAccessDenied() {
507
512
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center min-h-[200px] p-8 text-center", children: [
@@ -1942,4 +1947,4 @@ export {
1942
1947
  getPermissionsForRole,
1943
1948
  ALL_PERMISSIONS
1944
1949
  };
1945
- //# sourceMappingURL=chunk-FBBET5X5.js.map
1950
+ //# sourceMappingURL=chunk-V7ZZCH3G.js.map