@jmruthers/pace-core 0.5.1 → 0.5.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/dist/{DataTable-GX3XERFJ.js → DataTable-ZQDRE46Q.js} +7 -6
- package/dist/{PublicLoadingSpinner-DztrzuJr.d.ts → PublicLoadingSpinner-Bq_-BeK-.d.ts} +1 -1
- package/dist/RBACProvider-BO4ilsQB.d.ts +63 -0
- package/dist/{UnifiedAuthProvider-w66zSCUf.d.ts → UnifiedAuthProvider-DGQsy-vY.d.ts} +2 -59
- package/dist/{api-ETQ6YJ3C.js → api-H5A3H4IR.js} +2 -2
- package/dist/{chunk-T3XIA4AJ.js → chunk-5H3C2SWM.js} +14 -16
- package/dist/chunk-5H3C2SWM.js.map +1 -0
- package/dist/chunk-5SIXIV7R.js +1925 -0
- package/dist/chunk-5SIXIV7R.js.map +1 -0
- package/dist/chunk-GNTALZV3.js +17 -0
- package/dist/chunk-GNTALZV3.js.map +1 -0
- package/dist/{chunk-C5G2A4PO.js → chunk-GWSBHC4J.js} +6 -6
- package/dist/{chunk-XJK2J4N6.js → chunk-HD7PYDUV.js} +4 -6
- package/dist/{chunk-XJK2J4N6.js.map → chunk-HD7PYDUV.js.map} +1 -1
- package/dist/{chunk-TGDCLPP2.js → chunk-HXX35Q2M.js} +6 -21
- package/dist/chunk-HXX35Q2M.js.map +1 -0
- package/dist/{chunk-5EL3KHOQ.js → chunk-K6B7BLSE.js} +2 -2
- package/dist/{chunk-GSNM5D6H.js → chunk-M4RW7PIP.js} +4 -4
- package/dist/{chunk-U6JDHVC2.js → chunk-PVMYVQSM.js} +6 -8
- package/dist/{chunk-U6JDHVC2.js.map → chunk-PVMYVQSM.js.map} +1 -1
- package/dist/{chunk-6CR3MRZN.js → chunk-QKHFMQ5R.js} +372 -11
- package/dist/{chunk-6CR3MRZN.js.map → chunk-QKHFMQ5R.js.map} +1 -1
- package/dist/chunk-QVYBYGT2.js +428 -0
- package/dist/chunk-QVYBYGT2.js.map +1 -0
- package/dist/{chunk-OEGRKULD.js → chunk-WJARTBCT.js} +56 -1
- package/dist/chunk-WJARTBCT.js.map +1 -0
- package/dist/components.d.ts +4 -3
- package/dist/components.js +16 -162
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +152 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -2
- package/dist/providers.js +6 -12
- package/dist/rbac/index.d.ts +167 -98
- package/dist/rbac/index.js +48 -1881
- package/dist/rbac/index.js.map +1 -1
- package/dist/styles/core.css +0 -55
- package/dist/types.d.ts +2 -2
- package/dist/{unified-CM7T0aTK.d.ts → unified-CMPjE_fv.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B6i0KtXW.d.ts → usePublicRouteParams-B2OcAsur.d.ts} +1 -1
- package/dist/utils.js +12 -14
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +73 -0
- package/docs/api/classes/MissingUserContextError.md +66 -0
- package/docs/api/classes/OrganisationContextRequiredError.md +66 -0
- package/docs/api/classes/PermissionDeniedError.md +73 -0
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +270 -0
- package/docs/api/classes/RBACCache.md +284 -0
- package/docs/api/classes/RBACEngine.md +141 -0
- package/docs/api/classes/RBACError.md +76 -0
- package/docs/api/classes/RBACNotInitializedError.md +66 -0
- package/docs/api/classes/SecureSupabaseClient.md +135 -0
- 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 +96 -0
- 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 +235 -0
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.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 +107 -0
- package/docs/api/interfaces/NavigationContextType.md +164 -0
- package/docs/api/interfaces/NavigationGuardProps.md +139 -0
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +117 -0
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +2 -2
- 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 +85 -0
- package/docs/api/interfaces/PagePermissionContextType.md +140 -0
- package/docs/api/interfaces/PagePermissionGuardProps.md +153 -0
- package/docs/api/interfaces/PagePermissionProviderProps.md +119 -0
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +153 -0
- 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 +99 -0
- package/docs/api/interfaces/RBACContextType.md +474 -0
- package/docs/api/interfaces/RBACLogger.md +112 -0
- package/docs/api/interfaces/RBACProviderProps.md +107 -0
- package/docs/api/interfaces/RoleBasedRouterContextType.md +151 -0
- package/docs/api/interfaces/RoleBasedRouterProps.md +156 -0
- package/docs/api/interfaces/RouteAccessRecord.md +107 -0
- package/docs/api/interfaces/RouteConfig.md +121 -0
- package/docs/api/interfaces/SecureDataContextType.md +168 -0
- package/docs/api/interfaces/SecureDataProviderProps.md +132 -0
- 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/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +85 -85
- 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/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2244 -3
- package/docs/migration-guide.md +43 -18
- package/docs/styles/README.md +187 -98
- package/docs/usage.md +32 -7
- package/package.json +2 -2
- package/src/components/Footer/Footer.test.tsx +482 -0
- package/src/components/Form/Form.test.tsx +1158 -0
- package/src/components/Header/Header.test.tsx +582 -0
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +489 -0
- package/src/components/Input/Input.test.tsx +466 -0
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +450 -0
- package/src/components/LoginForm/LoginForm.test.tsx +816 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +883 -0
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +748 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +891 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +475 -0
- package/src/components/PasswordReset/PasswordChangeForm.test.tsx +621 -0
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +605 -0
- package/src/components/Select/Select.test.tsx +948 -0
- package/src/components/SuperAdminGuard.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +586 -0
- package/src/components/Tooltip/Tooltip.test.tsx +852 -0
- package/src/components/UserMenu/UserMenu.test.tsx +702 -0
- package/src/components/UserMenu/UserMenu.tsx +2 -2
- package/src/hooks/useDebounce.test.ts +375 -0
- package/src/hooks/useOrganisationPermissions.test.ts +528 -0
- package/src/hooks/useOrganisationSecurity.test.ts +734 -0
- package/src/hooks/usePermissionCache.test.ts +542 -0
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/index.ts +2 -3
- package/src/providers/UnifiedAuthProvider.tsx +2 -2
- package/src/providers/index.ts +3 -1
- package/src/rbac/__tests__/integration.test.tsx +218 -0
- package/src/rbac/api.test.ts +952 -0
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +843 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1007 -0
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +806 -0
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +741 -0
- package/src/rbac/hooks/index.ts +21 -0
- package/src/rbac/hooks/useCan.test.ts +461 -0
- package/src/rbac/hooks/usePermissions.test.ts +364 -0
- package/src/rbac/hooks/usePermissions.ts +567 -0
- package/src/rbac/hooks/useRBAC.simple.test.ts +90 -0
- package/src/rbac/hooks/useRBAC.test.ts +551 -0
- package/src/{hooks → rbac/hooks}/useRBAC.ts +7 -7
- package/src/rbac/index.ts +5 -10
- package/src/{providers → rbac/providers}/RBACProvider.tsx +6 -6
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +687 -0
- package/src/rbac/providers/index.ts +11 -0
- package/src/styles/core.css +0 -55
- package/src/utils/formatDate.test.ts +241 -0
- package/dist/chunk-AUE24LVR.js +0 -268
- package/dist/chunk-AUE24LVR.js.map +0 -1
- package/dist/chunk-COBPIXXQ.js +0 -379
- package/dist/chunk-COBPIXXQ.js.map +0 -1
- package/dist/chunk-OEGRKULD.js.map +0 -1
- package/dist/chunk-OYRY44Q2.js +0 -62
- package/dist/chunk-OYRY44Q2.js.map +0 -1
- package/dist/chunk-T3XIA4AJ.js.map +0 -1
- package/dist/chunk-TGDCLPP2.js.map +0 -1
- package/src/components/RBAC/PagePermissionGuard.tsx +0 -287
- package/src/components/RBAC/RBACGuard.tsx +0 -143
- package/src/components/RBAC/RBACProvider.tsx +0 -186
- package/src/components/RBAC/RoleBasedContent.tsx +0 -129
- package/src/components/RBAC/index.ts +0 -23
- package/src/rbac/hooks.ts +0 -570
- /package/dist/{DataTable-GX3XERFJ.js.map → DataTable-ZQDRE46Q.js.map} +0 -0
- /package/dist/{api-ETQ6YJ3C.js.map → api-H5A3H4IR.js.map} +0 -0
- /package/dist/{chunk-C5G2A4PO.js.map → chunk-GWSBHC4J.js.map} +0 -0
- /package/dist/{chunk-5EL3KHOQ.js.map → chunk-K6B7BLSE.js.map} +0 -0
- /package/dist/{chunk-GSNM5D6H.js.map → chunk-M4RW7PIP.js.map} +0 -0
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Role-Based Content Component
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Components/RBAC
|
|
5
|
-
* @since 0.3.0
|
|
6
|
-
*
|
|
7
|
-
* A component that conditionally renders children based on user roles.
|
|
8
|
-
* This component integrates with the new RBAC system to provide role-based
|
|
9
|
-
* UI rendering with support for multiple role types.
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Role-based conditional rendering
|
|
13
|
-
* - Support for global, organisation, and event-app roles
|
|
14
|
-
* - Multiple role checking
|
|
15
|
-
* - Fallback content support
|
|
16
|
-
* - Type-safe role checking
|
|
17
|
-
* - Automatic context detection
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* import { RoleBasedContent } from '@jmruthers/pace-core';
|
|
22
|
-
*
|
|
23
|
-
* function MyComponent() {
|
|
24
|
-
* return (
|
|
25
|
-
* <div>
|
|
26
|
-
* <h1>Admin Panel</h1>
|
|
27
|
-
*
|
|
28
|
-
* <RoleBasedContent globalRoles={['super_admin']}>
|
|
29
|
-
* <SuperAdminPanel />
|
|
30
|
-
* </RoleBasedContent>
|
|
31
|
-
*
|
|
32
|
-
* <RoleBasedContent organisationRoles={['org_admin', 'leader']}>
|
|
33
|
-
* <OrganisationManagementPanel />
|
|
34
|
-
* </RoleBasedContent>
|
|
35
|
-
*
|
|
36
|
-
* <RoleBasedContent eventAppRoles={['event_admin', 'planner']}>
|
|
37
|
-
* <EventManagementPanel />
|
|
38
|
-
* </RoleBasedContent>
|
|
39
|
-
*
|
|
40
|
-
* <RoleBasedContent
|
|
41
|
-
* globalRoles={['super_admin']}
|
|
42
|
-
* organisationRoles={['org_admin']}
|
|
43
|
-
* fallback={<p>You need admin privileges to access this content.</p>}
|
|
44
|
-
* >
|
|
45
|
-
* <AdminOnlyContent />
|
|
46
|
-
* </RoleBasedContent>
|
|
47
|
-
* </div>
|
|
48
|
-
* );
|
|
49
|
-
* }
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @accessibility
|
|
53
|
-
* - Supports screen reader friendly conditional content
|
|
54
|
-
* - Maintains focus management in conditional renders
|
|
55
|
-
* - Provides accessible fallback content
|
|
56
|
-
*
|
|
57
|
-
* @security
|
|
58
|
-
* - Role-based access control
|
|
59
|
-
* - Secure role checking
|
|
60
|
-
* - Organisation context enforcement
|
|
61
|
-
*
|
|
62
|
-
* @performance
|
|
63
|
-
* - Optimized with React.memo
|
|
64
|
-
* - Minimal re-renders
|
|
65
|
-
* - Efficient role checking
|
|
66
|
-
*
|
|
67
|
-
* @dependencies
|
|
68
|
-
* - React 18+ - Components and hooks
|
|
69
|
-
* - useRBAC hook - Role checking
|
|
70
|
-
* - RBAC types - Type definitions
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
import React from 'react';
|
|
74
|
-
import { useRBAC } from '../../hooks/useRBAC';
|
|
75
|
-
import type {
|
|
76
|
-
RoleBasedContentProps,
|
|
77
|
-
GlobalRole,
|
|
78
|
-
OrganisationRole,
|
|
79
|
-
EventAppRole
|
|
80
|
-
} from '../../rbac/types';
|
|
81
|
-
|
|
82
|
-
export function RoleBasedContent({
|
|
83
|
-
children,
|
|
84
|
-
globalRoles = [],
|
|
85
|
-
organisationRoles = [],
|
|
86
|
-
eventAppRoles = [],
|
|
87
|
-
fallback = null
|
|
88
|
-
}: RoleBasedContentProps) {
|
|
89
|
-
const {
|
|
90
|
-
globalRole,
|
|
91
|
-
organisationRole,
|
|
92
|
-
eventAppRole,
|
|
93
|
-
isLoading,
|
|
94
|
-
error
|
|
95
|
-
} = useRBAC();
|
|
96
|
-
|
|
97
|
-
// Show loading state while checking roles
|
|
98
|
-
if (isLoading) {
|
|
99
|
-
return (
|
|
100
|
-
<div className="rbac-loading" role="status" aria-live="polite">
|
|
101
|
-
<span className="sr-only">Checking roles...</span>
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Show error state - render fallback if provided, otherwise null
|
|
107
|
-
if (error) {
|
|
108
|
-
if (fallback) {
|
|
109
|
-
return (
|
|
110
|
-
<div className="rbac-error" role="alert">
|
|
111
|
-
<span className="sr-only">Role check error</span>
|
|
112
|
-
{fallback}
|
|
113
|
-
</div>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check if user has any of the required roles
|
|
120
|
-
const hasGlobalRole = globalRoles.length > 0 && globalRole && globalRoles.includes(globalRole as GlobalRole);
|
|
121
|
-
const hasOrgRole = organisationRoles.length > 0 && organisationRole && organisationRoles.includes(organisationRole as OrganisationRole);
|
|
122
|
-
const hasEventRole = eventAppRoles.length > 0 && eventAppRole && eventAppRoles.includes(eventAppRole as EventAppRole);
|
|
123
|
-
|
|
124
|
-
// User has access if they have any of the required roles
|
|
125
|
-
const hasAccess = hasGlobalRole || hasOrgRole || hasEventRole;
|
|
126
|
-
|
|
127
|
-
// Render children if user has required role, otherwise show fallback
|
|
128
|
-
return hasAccess ? <>{children}</> : <>{fallback}</>;
|
|
129
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RBAC Components Index
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module Components/RBAC
|
|
5
|
-
* @since 0.3.0
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Export RBAC components
|
|
9
|
-
export { RBACGuard } from './RBACGuard';
|
|
10
|
-
export { RoleBasedContent } from './RoleBasedContent';
|
|
11
|
-
export { RBACProvider } from './RBACProvider';
|
|
12
|
-
export {
|
|
13
|
-
PagePermissionGuard,
|
|
14
|
-
ReadPermissionGuard,
|
|
15
|
-
CreatePermissionGuard,
|
|
16
|
-
UpdatePermissionGuard,
|
|
17
|
-
DeletePermissionGuard,
|
|
18
|
-
PAGE_IDS
|
|
19
|
-
} from './PagePermissionGuard';
|
|
20
|
-
|
|
21
|
-
// Export types
|
|
22
|
-
export type { RBACGuardProps } from '../../rbac/types';
|
|
23
|
-
export type { RoleBasedContentProps } from '../../rbac/types';
|
package/src/rbac/hooks.ts
DELETED
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RBAC React Hooks
|
|
3
|
-
* @package @jmruthers/pace-core
|
|
4
|
-
* @module RBAC/Hooks
|
|
5
|
-
* @since 1.0.0
|
|
6
|
-
*
|
|
7
|
-
* This module provides React hooks for RBAC functionality.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
11
|
-
import {
|
|
12
|
-
UUID,
|
|
13
|
-
Scope,
|
|
14
|
-
Permission,
|
|
15
|
-
AccessLevel,
|
|
16
|
-
PermissionMap,
|
|
17
|
-
UsePermissionsReturn,
|
|
18
|
-
UseCanReturn
|
|
19
|
-
} from './types';
|
|
20
|
-
import {
|
|
21
|
-
getAccessLevel,
|
|
22
|
-
getPermissionMap,
|
|
23
|
-
isPermitted,
|
|
24
|
-
isPermittedCached
|
|
25
|
-
} from './api';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Hook to get user's permissions in a scope
|
|
29
|
-
*
|
|
30
|
-
* @param userId - User ID
|
|
31
|
-
* @param scope - Permission scope
|
|
32
|
-
* @returns Permission data and loading state
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```tsx
|
|
36
|
-
* function MyComponent() {
|
|
37
|
-
* const { permissions, isLoading, error } = usePermissions(
|
|
38
|
-
* 'user-123',
|
|
39
|
-
* { organisationId: 'org-456' }
|
|
40
|
-
* );
|
|
41
|
-
*
|
|
42
|
-
* if (isLoading) return <div>Loading...</div>;
|
|
43
|
-
* if (error) return <div>Error: {error.message}</div>;
|
|
44
|
-
*
|
|
45
|
-
* return (
|
|
46
|
-
* <div>
|
|
47
|
-
* {permissions['page-1']?.includes('read') && <ReadButton />}
|
|
48
|
-
* {permissions['page-1']?.includes('manage') && <ManageButton />}
|
|
49
|
-
* </div>
|
|
50
|
-
* );
|
|
51
|
-
* }
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export function usePermissions(userId: UUID, scope: Scope): UsePermissionsReturn {
|
|
55
|
-
const [permissions, setPermissions] = useState<PermissionMap>({});
|
|
56
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
57
|
-
const [error, setError] = useState<Error | null>(null);
|
|
58
|
-
|
|
59
|
-
const fetchPermissions = useCallback(async () => {
|
|
60
|
-
if (!userId) {
|
|
61
|
-
setPermissions({});
|
|
62
|
-
setIsLoading(false);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
setIsLoading(true);
|
|
68
|
-
setError(null);
|
|
69
|
-
|
|
70
|
-
const result = await getPermissionMap({ userId, scope });
|
|
71
|
-
setPermissions(result);
|
|
72
|
-
} catch (err) {
|
|
73
|
-
setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));
|
|
74
|
-
} finally {
|
|
75
|
-
setIsLoading(false);
|
|
76
|
-
}
|
|
77
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
78
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
fetchPermissions();
|
|
81
|
-
}, [fetchPermissions]);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
permissions,
|
|
85
|
-
isLoading,
|
|
86
|
-
error,
|
|
87
|
-
refetch: fetchPermissions,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Hook to check if user has a specific permission
|
|
93
|
-
*
|
|
94
|
-
* @param userId - User ID
|
|
95
|
-
* @param scope - Permission scope
|
|
96
|
-
* @param permission - Permission to check
|
|
97
|
-
* @param pageId - Optional page ID
|
|
98
|
-
* @param useCache - Whether to use cached results (default: true)
|
|
99
|
-
* @returns Permission check result and loading state
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```tsx
|
|
103
|
-
* function MyComponent() {
|
|
104
|
-
* const { can, isLoading } = useCan(
|
|
105
|
-
* 'user-123',
|
|
106
|
-
* { organisationId: 'org-456' },
|
|
107
|
-
* 'manage:events',
|
|
108
|
-
* 'page-789'
|
|
109
|
-
* );
|
|
110
|
-
*
|
|
111
|
-
* if (isLoading) return <div>Checking permission...</div>;
|
|
112
|
-
*
|
|
113
|
-
* return (
|
|
114
|
-
* <div>
|
|
115
|
-
* {can ? <AdminPanel /> : <AccessDenied />}
|
|
116
|
-
* </div>
|
|
117
|
-
* );
|
|
118
|
-
* }
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
export function useCan(
|
|
122
|
-
userId: UUID,
|
|
123
|
-
scope: Scope,
|
|
124
|
-
permission: Permission,
|
|
125
|
-
pageId?: UUID,
|
|
126
|
-
useCache: boolean = true
|
|
127
|
-
): UseCanReturn {
|
|
128
|
-
const [can, setCan] = useState(false);
|
|
129
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
130
|
-
const [error, setError] = useState<Error | null>(null);
|
|
131
|
-
|
|
132
|
-
const check = useCallback(async () => {
|
|
133
|
-
console.log('[useCan] check() called with:', { userId, scope, permission, pageId });
|
|
134
|
-
console.log('[useCan] Hook parameters:', { userId, scope, permission, pageId, useCache });
|
|
135
|
-
|
|
136
|
-
if (!userId) {
|
|
137
|
-
console.log('[useCan] No userId, denying access');
|
|
138
|
-
setCan(false);
|
|
139
|
-
setIsLoading(false);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Check for super admin status first - super admins bypass all scope requirements
|
|
144
|
-
try {
|
|
145
|
-
const { isSuperAdmin } = await import('./api');
|
|
146
|
-
const isSuper = await isSuperAdmin(userId);
|
|
147
|
-
if (isSuper) {
|
|
148
|
-
console.log('[useCan] User is super admin, granting access');
|
|
149
|
-
setCan(true);
|
|
150
|
-
setIsLoading(false);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error('[useCan] Error checking super admin status:', error);
|
|
155
|
-
// Continue with normal permission check if super admin check fails
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Check if scope is incomplete (missing required fields)
|
|
159
|
-
if (!scope || !scope.organisationId || !scope.appId) {
|
|
160
|
-
console.log('[useCan] Incomplete scope, waiting for resolution:', scope);
|
|
161
|
-
setCan(false);
|
|
162
|
-
setIsLoading(true); // Keep loading until scope is complete
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
console.log('[useCan] Scope is complete, checking permission...');
|
|
167
|
-
console.log('[useCan] Detailed scope info:', {
|
|
168
|
-
organisationId: scope.organisationId,
|
|
169
|
-
eventId: scope.eventId,
|
|
170
|
-
appId: scope.appId,
|
|
171
|
-
permission,
|
|
172
|
-
pageId
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
setIsLoading(true);
|
|
177
|
-
setError(null);
|
|
178
|
-
|
|
179
|
-
console.log('[useCan] About to call isPermitted/isPermittedCached...');
|
|
180
|
-
const result = useCache
|
|
181
|
-
? await isPermittedCached({ userId, scope, permission, pageId })
|
|
182
|
-
: await isPermitted({ userId, scope, permission, pageId });
|
|
183
|
-
|
|
184
|
-
console.log('[useCan] Permission check result:', result);
|
|
185
|
-
console.log('[useCan] Permission check details:', {
|
|
186
|
-
userId,
|
|
187
|
-
scope,
|
|
188
|
-
permission,
|
|
189
|
-
pageId,
|
|
190
|
-
result,
|
|
191
|
-
timestamp: new Date().toISOString()
|
|
192
|
-
});
|
|
193
|
-
setCan(result);
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.error('[useCan] Permission check error:', err);
|
|
196
|
-
console.error('[useCan] Error details:', {
|
|
197
|
-
userId,
|
|
198
|
-
scope,
|
|
199
|
-
permission,
|
|
200
|
-
pageId,
|
|
201
|
-
error: err instanceof Error ? err.message : 'Unknown error',
|
|
202
|
-
timestamp: new Date().toISOString()
|
|
203
|
-
});
|
|
204
|
-
setError(err instanceof Error ? err : new Error('Failed to check permission'));
|
|
205
|
-
setCan(false);
|
|
206
|
-
} finally {
|
|
207
|
-
setIsLoading(false);
|
|
208
|
-
}
|
|
209
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);
|
|
210
|
-
|
|
211
|
-
useEffect(() => {
|
|
212
|
-
check();
|
|
213
|
-
}, [check]);
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
can,
|
|
217
|
-
isLoading,
|
|
218
|
-
error,
|
|
219
|
-
check,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Hook to get user's access level in a scope
|
|
225
|
-
*
|
|
226
|
-
* @param userId - User ID
|
|
227
|
-
* @param scope - Permission scope
|
|
228
|
-
* @returns Access level and loading state
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* ```tsx
|
|
232
|
-
* function MyComponent() {
|
|
233
|
-
* const { accessLevel, isLoading } = useAccessLevel(
|
|
234
|
-
* 'user-123',
|
|
235
|
-
* { organisationId: 'org-456' }
|
|
236
|
-
* );
|
|
237
|
-
*
|
|
238
|
-
* if (isLoading) return <div>Loading...</div>;
|
|
239
|
-
*
|
|
240
|
-
* return (
|
|
241
|
-
* <div>
|
|
242
|
-
* {accessLevel === 'super' && <SuperAdminPanel />}
|
|
243
|
-
* {accessLevel === 'admin' && <AdminPanel />}
|
|
244
|
-
* {accessLevel === 'planner' && <PlannerPanel />}
|
|
245
|
-
* </div>
|
|
246
|
-
* );
|
|
247
|
-
* }
|
|
248
|
-
* ```
|
|
249
|
-
*/
|
|
250
|
-
export function useAccessLevel(userId: UUID, scope: Scope): {
|
|
251
|
-
accessLevel: AccessLevel | null;
|
|
252
|
-
isLoading: boolean;
|
|
253
|
-
error: Error | null;
|
|
254
|
-
refetch: () => Promise<void>;
|
|
255
|
-
} {
|
|
256
|
-
const [accessLevel, setAccessLevel] = useState<AccessLevel | null>(null);
|
|
257
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
258
|
-
const [error, setError] = useState<Error | null>(null);
|
|
259
|
-
|
|
260
|
-
const fetchAccessLevel = useCallback(async () => {
|
|
261
|
-
if (!userId) {
|
|
262
|
-
setAccessLevel(null);
|
|
263
|
-
setIsLoading(false);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
setIsLoading(true);
|
|
269
|
-
setError(null);
|
|
270
|
-
|
|
271
|
-
const result = await getAccessLevel({ userId, scope });
|
|
272
|
-
setAccessLevel(result);
|
|
273
|
-
} catch (err) {
|
|
274
|
-
setError(err instanceof Error ? err : new Error('Failed to fetch access level'));
|
|
275
|
-
setAccessLevel(null);
|
|
276
|
-
} finally {
|
|
277
|
-
setIsLoading(false);
|
|
278
|
-
}
|
|
279
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
280
|
-
|
|
281
|
-
useEffect(() => {
|
|
282
|
-
fetchAccessLevel();
|
|
283
|
-
}, [fetchAccessLevel]);
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
accessLevel,
|
|
287
|
-
isLoading,
|
|
288
|
-
error,
|
|
289
|
-
refetch: fetchAccessLevel,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Hook to check multiple permissions at once
|
|
295
|
-
*
|
|
296
|
-
* @param userId - User ID
|
|
297
|
-
* @param scope - Permission scope
|
|
298
|
-
* @param permissions - Array of permissions to check
|
|
299
|
-
* @param pageId - Optional page ID
|
|
300
|
-
* @param useCache - Whether to use cached results (default: true)
|
|
301
|
-
* @returns Object with permission results and loading state
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```tsx
|
|
305
|
-
* function MyComponent() {
|
|
306
|
-
* const { permissions, isLoading } = useMultiplePermissions(
|
|
307
|
-
* 'user-123',
|
|
308
|
-
* { organisationId: 'org-456' },
|
|
309
|
-
* ['read:events', 'manage:events', 'delete:events']
|
|
310
|
-
* );
|
|
311
|
-
*
|
|
312
|
-
* return (
|
|
313
|
-
* <div>
|
|
314
|
-
* {permissions['read:events'] && <ReadButton />}
|
|
315
|
-
* {permissions['manage:events'] && <ManageButton />}
|
|
316
|
-
* {permissions['delete:events'] && <DeleteButton />}
|
|
317
|
-
* </div>
|
|
318
|
-
* );
|
|
319
|
-
* }
|
|
320
|
-
* ```
|
|
321
|
-
*/
|
|
322
|
-
export function useMultiplePermissions(
|
|
323
|
-
userId: UUID,
|
|
324
|
-
scope: Scope,
|
|
325
|
-
permissions: Permission[],
|
|
326
|
-
pageId?: UUID,
|
|
327
|
-
useCache: boolean = true
|
|
328
|
-
): {
|
|
329
|
-
permissions: Record<Permission, boolean>;
|
|
330
|
-
isLoading: boolean;
|
|
331
|
-
error: Error | null;
|
|
332
|
-
refetch: () => Promise<void>;
|
|
333
|
-
} {
|
|
334
|
-
const [permissionResults, setPermissionResults] = useState<Record<Permission, boolean>>({} as Record<Permission, boolean>);
|
|
335
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
336
|
-
const [error, setError] = useState<Error | null>(null);
|
|
337
|
-
|
|
338
|
-
const fetchPermissions = useCallback(async () => {
|
|
339
|
-
if (!userId || permissions.length === 0) {
|
|
340
|
-
setPermissionResults({} as Record<Permission, boolean>);
|
|
341
|
-
setIsLoading(false);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
try {
|
|
346
|
-
setIsLoading(true);
|
|
347
|
-
setError(null);
|
|
348
|
-
|
|
349
|
-
const results: Record<Permission, boolean> = {} as Record<Permission, boolean>;
|
|
350
|
-
|
|
351
|
-
// Check all permissions in parallel
|
|
352
|
-
const promises = permissions.map(async (permission) => {
|
|
353
|
-
const result = useCache
|
|
354
|
-
? await isPermittedCached({ userId, scope, permission, pageId })
|
|
355
|
-
: await isPermitted({ userId, scope, permission, pageId });
|
|
356
|
-
|
|
357
|
-
return { permission, result };
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const resolved = await Promise.all(promises);
|
|
361
|
-
|
|
362
|
-
resolved.forEach(({ permission, result }) => {
|
|
363
|
-
results[permission] = result;
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
setPermissionResults(results);
|
|
367
|
-
} catch (err) {
|
|
368
|
-
setError(err instanceof Error ? err : new Error('Failed to check permissions'));
|
|
369
|
-
setPermissionResults({} as Record<Permission, boolean>);
|
|
370
|
-
} finally {
|
|
371
|
-
setIsLoading(false);
|
|
372
|
-
}
|
|
373
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, pageId, useCache]);
|
|
374
|
-
|
|
375
|
-
useEffect(() => {
|
|
376
|
-
fetchPermissions();
|
|
377
|
-
}, [fetchPermissions]);
|
|
378
|
-
|
|
379
|
-
return {
|
|
380
|
-
permissions: permissionResults,
|
|
381
|
-
isLoading,
|
|
382
|
-
error,
|
|
383
|
-
refetch: fetchPermissions,
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Hook to check if user has any of the specified permissions
|
|
389
|
-
*
|
|
390
|
-
* @param userId - User ID
|
|
391
|
-
* @param scope - Permission scope
|
|
392
|
-
* @param permissions - Array of permissions to check
|
|
393
|
-
* @param pageId - Optional page ID
|
|
394
|
-
* @returns True if user has any permission and loading state
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* ```tsx
|
|
398
|
-
* function MyComponent() {
|
|
399
|
-
* const { hasAny, isLoading } = useHasAnyPermission(
|
|
400
|
-
* 'user-123',
|
|
401
|
-
* { organisationId: 'org-456' },
|
|
402
|
-
* ['read:events', 'manage:events']
|
|
403
|
-
* );
|
|
404
|
-
*
|
|
405
|
-
* return (
|
|
406
|
-
* <div>
|
|
407
|
-
* {hasAny ? <EventContent /> : <AccessDenied />}
|
|
408
|
-
* </div>
|
|
409
|
-
* );
|
|
410
|
-
* }
|
|
411
|
-
* ```
|
|
412
|
-
*/
|
|
413
|
-
export function useHasAnyPermission(
|
|
414
|
-
userId: UUID,
|
|
415
|
-
scope: Scope,
|
|
416
|
-
permissions: Permission[],
|
|
417
|
-
pageId?: UUID
|
|
418
|
-
): {
|
|
419
|
-
hasAny: boolean;
|
|
420
|
-
isLoading: boolean;
|
|
421
|
-
error: Error | null;
|
|
422
|
-
refetch: () => Promise<void>;
|
|
423
|
-
} {
|
|
424
|
-
const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
|
|
425
|
-
userId,
|
|
426
|
-
scope,
|
|
427
|
-
permissions,
|
|
428
|
-
pageId
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
const hasAny = useMemo(() => {
|
|
432
|
-
return Object.values(permissionResults).some(Boolean);
|
|
433
|
-
}, [permissionResults]);
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
hasAny,
|
|
437
|
-
isLoading,
|
|
438
|
-
error,
|
|
439
|
-
refetch,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Hook to check if user has all of the specified permissions
|
|
445
|
-
*
|
|
446
|
-
* @param userId - User ID
|
|
447
|
-
* @param scope - Permission scope
|
|
448
|
-
* @param permissions - Array of permissions to check
|
|
449
|
-
* @param pageId - Optional page ID
|
|
450
|
-
* @returns True if user has all permissions and loading state
|
|
451
|
-
*
|
|
452
|
-
* @example
|
|
453
|
-
* ```tsx
|
|
454
|
-
* function MyComponent() {
|
|
455
|
-
* const { hasAll, isLoading } = useHasAllPermissions(
|
|
456
|
-
* 'user-123',
|
|
457
|
-
* { organisationId: 'org-456' },
|
|
458
|
-
* ['read:events', 'manage:events']
|
|
459
|
-
* );
|
|
460
|
-
*
|
|
461
|
-
* return (
|
|
462
|
-
* <div>
|
|
463
|
-
* {hasAll ? <FullAccessPanel /> : <LimitedAccessPanel />}
|
|
464
|
-
* </div>
|
|
465
|
-
* );
|
|
466
|
-
* }
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
|
-
export function useHasAllPermissions(
|
|
470
|
-
userId: UUID,
|
|
471
|
-
scope: Scope,
|
|
472
|
-
permissions: Permission[],
|
|
473
|
-
pageId?: UUID
|
|
474
|
-
): {
|
|
475
|
-
hasAll: boolean;
|
|
476
|
-
isLoading: boolean;
|
|
477
|
-
error: Error | null;
|
|
478
|
-
refetch: () => Promise<void>;
|
|
479
|
-
} {
|
|
480
|
-
const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
|
|
481
|
-
userId,
|
|
482
|
-
scope,
|
|
483
|
-
permissions,
|
|
484
|
-
pageId
|
|
485
|
-
);
|
|
486
|
-
|
|
487
|
-
const hasAll = useMemo(() => {
|
|
488
|
-
return Object.values(permissionResults).every(Boolean);
|
|
489
|
-
}, [permissionResults]);
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
hasAll,
|
|
493
|
-
isLoading,
|
|
494
|
-
error,
|
|
495
|
-
refetch,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Hook to read cached permissions (contract requirement)
|
|
501
|
-
*
|
|
502
|
-
* This hook only reads from the core cache and does not perform
|
|
503
|
-
* any bespoke caching as per the contract requirements.
|
|
504
|
-
*
|
|
505
|
-
* @param userId - User ID
|
|
506
|
-
* @param scope - Permission scope
|
|
507
|
-
* @returns Cached permission data and loading state
|
|
508
|
-
*
|
|
509
|
-
* @example
|
|
510
|
-
* ```tsx
|
|
511
|
-
* function MyComponent() {
|
|
512
|
-
* const { permissions, isLoading, error } = useCachedPermissions(
|
|
513
|
-
* 'user-123',
|
|
514
|
-
* { organisationId: 'org-456' }
|
|
515
|
-
* );
|
|
516
|
-
*
|
|
517
|
-
* if (isLoading) return <div>Loading cached permissions...</div>;
|
|
518
|
-
* if (error) return <div>Error: {error.message}</div>;
|
|
519
|
-
*
|
|
520
|
-
* return (
|
|
521
|
-
* <div>
|
|
522
|
-
* {permissions['page-1']?.includes('read') && <ReadButton />}
|
|
523
|
-
* {permissions['page-1']?.includes('manage') && <ManageButton />}
|
|
524
|
-
* </div>
|
|
525
|
-
* );
|
|
526
|
-
* }
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
529
|
-
export function useCachedPermissions(userId: UUID, scope: Scope): {
|
|
530
|
-
permissions: PermissionMap;
|
|
531
|
-
isLoading: boolean;
|
|
532
|
-
error: Error | null;
|
|
533
|
-
refetch: () => Promise<void>;
|
|
534
|
-
} {
|
|
535
|
-
const [permissions, setPermissions] = useState<PermissionMap>({});
|
|
536
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
537
|
-
const [error, setError] = useState<Error | null>(null);
|
|
538
|
-
|
|
539
|
-
const fetchCachedPermissions = useCallback(async () => {
|
|
540
|
-
if (!userId) {
|
|
541
|
-
setPermissions({});
|
|
542
|
-
setIsLoading(false);
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
try {
|
|
547
|
-
setIsLoading(true);
|
|
548
|
-
setError(null);
|
|
549
|
-
|
|
550
|
-
// Use cached version of getPermissionMap
|
|
551
|
-
const result = await getPermissionMap({ userId, scope });
|
|
552
|
-
setPermissions(result);
|
|
553
|
-
} catch (err) {
|
|
554
|
-
setError(err instanceof Error ? err : new Error('Failed to fetch cached permissions'));
|
|
555
|
-
} finally {
|
|
556
|
-
setIsLoading(false);
|
|
557
|
-
}
|
|
558
|
-
}, [userId, scope.organisationId, scope.eventId, scope.appId]);
|
|
559
|
-
|
|
560
|
-
useEffect(() => {
|
|
561
|
-
fetchCachedPermissions();
|
|
562
|
-
}, [fetchCachedPermissions]);
|
|
563
|
-
|
|
564
|
-
return {
|
|
565
|
-
permissions,
|
|
566
|
-
isLoading,
|
|
567
|
-
error,
|
|
568
|
-
refetch: fetchCachedPermissions,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
File without changes
|