@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.
Files changed (210) hide show
  1. package/dist/{DataTable-GX3XERFJ.js → DataTable-ZQDRE46Q.js} +7 -6
  2. package/dist/{PublicLoadingSpinner-DztrzuJr.d.ts → PublicLoadingSpinner-Bq_-BeK-.d.ts} +1 -1
  3. package/dist/RBACProvider-BO4ilsQB.d.ts +63 -0
  4. package/dist/{UnifiedAuthProvider-w66zSCUf.d.ts → UnifiedAuthProvider-DGQsy-vY.d.ts} +2 -59
  5. package/dist/{api-ETQ6YJ3C.js → api-H5A3H4IR.js} +2 -2
  6. package/dist/{chunk-T3XIA4AJ.js → chunk-5H3C2SWM.js} +14 -16
  7. package/dist/chunk-5H3C2SWM.js.map +1 -0
  8. package/dist/chunk-5SIXIV7R.js +1925 -0
  9. package/dist/chunk-5SIXIV7R.js.map +1 -0
  10. package/dist/chunk-GNTALZV3.js +17 -0
  11. package/dist/chunk-GNTALZV3.js.map +1 -0
  12. package/dist/{chunk-C5G2A4PO.js → chunk-GWSBHC4J.js} +6 -6
  13. package/dist/{chunk-XJK2J4N6.js → chunk-HD7PYDUV.js} +4 -6
  14. package/dist/{chunk-XJK2J4N6.js.map → chunk-HD7PYDUV.js.map} +1 -1
  15. package/dist/{chunk-TGDCLPP2.js → chunk-HXX35Q2M.js} +6 -21
  16. package/dist/chunk-HXX35Q2M.js.map +1 -0
  17. package/dist/{chunk-5EL3KHOQ.js → chunk-K6B7BLSE.js} +2 -2
  18. package/dist/{chunk-GSNM5D6H.js → chunk-M4RW7PIP.js} +4 -4
  19. package/dist/{chunk-U6JDHVC2.js → chunk-PVMYVQSM.js} +6 -8
  20. package/dist/{chunk-U6JDHVC2.js.map → chunk-PVMYVQSM.js.map} +1 -1
  21. package/dist/{chunk-6CR3MRZN.js → chunk-QKHFMQ5R.js} +372 -11
  22. package/dist/{chunk-6CR3MRZN.js.map → chunk-QKHFMQ5R.js.map} +1 -1
  23. package/dist/chunk-QVYBYGT2.js +428 -0
  24. package/dist/chunk-QVYBYGT2.js.map +1 -0
  25. package/dist/{chunk-OEGRKULD.js → chunk-WJARTBCT.js} +56 -1
  26. package/dist/chunk-WJARTBCT.js.map +1 -0
  27. package/dist/components.d.ts +4 -3
  28. package/dist/components.js +16 -162
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.d.ts +2 -2
  31. package/dist/hooks.js +7 -9
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +8 -6
  34. package/dist/index.js +152 -17
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.d.ts +3 -2
  37. package/dist/providers.js +6 -12
  38. package/dist/rbac/index.d.ts +167 -98
  39. package/dist/rbac/index.js +48 -1881
  40. package/dist/rbac/index.js.map +1 -1
  41. package/dist/styles/core.css +0 -55
  42. package/dist/types.d.ts +2 -2
  43. package/dist/{unified-CM7T0aTK.d.ts → unified-CMPjE_fv.d.ts} +1 -1
  44. package/dist/{usePublicRouteParams-B6i0KtXW.d.ts → usePublicRouteParams-B2OcAsur.d.ts} +1 -1
  45. package/dist/utils.js +12 -14
  46. package/dist/utils.js.map +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +73 -0
  49. package/docs/api/classes/MissingUserContextError.md +66 -0
  50. package/docs/api/classes/OrganisationContextRequiredError.md +66 -0
  51. package/docs/api/classes/PermissionDeniedError.md +73 -0
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +270 -0
  54. package/docs/api/classes/RBACCache.md +284 -0
  55. package/docs/api/classes/RBACEngine.md +141 -0
  56. package/docs/api/classes/RBACError.md +76 -0
  57. package/docs/api/classes/RBACNotInitializedError.md +66 -0
  58. package/docs/api/classes/SecureSupabaseClient.md +135 -0
  59. package/docs/api/interfaces/AggregateConfig.md +1 -1
  60. package/docs/api/interfaces/ButtonProps.md +1 -1
  61. package/docs/api/interfaces/CardProps.md +1 -1
  62. package/docs/api/interfaces/ColorPalette.md +1 -1
  63. package/docs/api/interfaces/ColorShade.md +1 -1
  64. package/docs/api/interfaces/DataAccessRecord.md +96 -0
  65. package/docs/api/interfaces/DataTableAction.md +1 -1
  66. package/docs/api/interfaces/DataTableColumn.md +1 -1
  67. package/docs/api/interfaces/DataTableProps.md +1 -1
  68. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  69. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  70. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +235 -0
  71. package/docs/api/interfaces/EventContextType.md +1 -1
  72. package/docs/api/interfaces/EventLogoProps.md +1 -1
  73. package/docs/api/interfaces/EventProviderProps.md +1 -1
  74. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  75. package/docs/api/interfaces/FileUploadProps.md +1 -1
  76. package/docs/api/interfaces/FooterProps.md +1 -1
  77. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  78. package/docs/api/interfaces/InputProps.md +1 -1
  79. package/docs/api/interfaces/LabelProps.md +1 -1
  80. package/docs/api/interfaces/LoginFormProps.md +1 -1
  81. package/docs/api/interfaces/NavigationAccessRecord.md +107 -0
  82. package/docs/api/interfaces/NavigationContextType.md +164 -0
  83. package/docs/api/interfaces/NavigationGuardProps.md +139 -0
  84. package/docs/api/interfaces/NavigationItem.md +1 -1
  85. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  86. package/docs/api/interfaces/NavigationProviderProps.md +117 -0
  87. package/docs/api/interfaces/Organisation.md +1 -1
  88. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  89. package/docs/api/interfaces/OrganisationMembership.md +2 -2
  90. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  91. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  92. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  93. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  94. package/docs/api/interfaces/PageAccessRecord.md +85 -0
  95. package/docs/api/interfaces/PagePermissionContextType.md +140 -0
  96. package/docs/api/interfaces/PagePermissionGuardProps.md +153 -0
  97. package/docs/api/interfaces/PagePermissionProviderProps.md +119 -0
  98. package/docs/api/interfaces/PaletteData.md +1 -1
  99. package/docs/api/interfaces/PermissionEnforcerProps.md +153 -0
  100. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  101. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  102. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  103. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  104. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  105. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  106. package/docs/api/interfaces/RBACConfig.md +99 -0
  107. package/docs/api/interfaces/RBACContextType.md +474 -0
  108. package/docs/api/interfaces/RBACLogger.md +112 -0
  109. package/docs/api/interfaces/RBACProviderProps.md +107 -0
  110. package/docs/api/interfaces/RoleBasedRouterContextType.md +151 -0
  111. package/docs/api/interfaces/RoleBasedRouterProps.md +156 -0
  112. package/docs/api/interfaces/RouteAccessRecord.md +107 -0
  113. package/docs/api/interfaces/RouteConfig.md +121 -0
  114. package/docs/api/interfaces/SecureDataContextType.md +168 -0
  115. package/docs/api/interfaces/SecureDataProviderProps.md +132 -0
  116. package/docs/api/interfaces/StorageConfig.md +1 -1
  117. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  118. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  119. package/docs/api/interfaces/StorageListOptions.md +1 -1
  120. package/docs/api/interfaces/StorageListResult.md +1 -1
  121. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  122. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  123. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  124. package/docs/api/interfaces/StyleImport.md +1 -1
  125. package/docs/api/interfaces/ToastActionElement.md +1 -1
  126. package/docs/api/interfaces/ToastProps.md +1 -1
  127. package/docs/api/interfaces/UnifiedAuthContextType.md +85 -85
  128. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  129. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  130. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  131. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  132. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  133. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  134. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  135. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  136. package/docs/api/interfaces/UserEventAccess.md +11 -11
  137. package/docs/api/interfaces/UserMenuProps.md +1 -1
  138. package/docs/api/interfaces/UserProfile.md +1 -1
  139. package/docs/api/modules.md +2244 -3
  140. package/docs/migration-guide.md +43 -18
  141. package/docs/styles/README.md +187 -98
  142. package/docs/usage.md +32 -7
  143. package/package.json +2 -2
  144. package/src/components/Footer/Footer.test.tsx +482 -0
  145. package/src/components/Form/Form.test.tsx +1158 -0
  146. package/src/components/Header/Header.test.tsx +582 -0
  147. package/src/components/Header/Header.tsx +1 -1
  148. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +489 -0
  149. package/src/components/Input/Input.test.tsx +466 -0
  150. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +450 -0
  151. package/src/components/LoginForm/LoginForm.test.tsx +816 -0
  152. package/src/components/NavigationMenu/NavigationMenu.test.tsx +883 -0
  153. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +748 -0
  154. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +891 -0
  155. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +475 -0
  156. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +621 -0
  157. package/src/components/PasswordReset/PasswordResetForm.test.tsx +605 -0
  158. package/src/components/Select/Select.test.tsx +948 -0
  159. package/src/components/SuperAdminGuard.tsx +1 -1
  160. package/src/components/Toast/Toast.test.tsx +586 -0
  161. package/src/components/Tooltip/Tooltip.test.tsx +852 -0
  162. package/src/components/UserMenu/UserMenu.test.tsx +702 -0
  163. package/src/components/UserMenu/UserMenu.tsx +2 -2
  164. package/src/hooks/useDebounce.test.ts +375 -0
  165. package/src/hooks/useOrganisationPermissions.test.ts +528 -0
  166. package/src/hooks/useOrganisationSecurity.test.ts +734 -0
  167. package/src/hooks/usePermissionCache.test.ts +542 -0
  168. package/src/hooks/usePermissionCache.ts +1 -1
  169. package/src/index.ts +2 -3
  170. package/src/providers/UnifiedAuthProvider.tsx +2 -2
  171. package/src/providers/index.ts +3 -1
  172. package/src/rbac/__tests__/integration.test.tsx +218 -0
  173. package/src/rbac/api.test.ts +952 -0
  174. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +843 -0
  175. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +1007 -0
  176. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +806 -0
  177. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +741 -0
  178. package/src/rbac/hooks/index.ts +21 -0
  179. package/src/rbac/hooks/useCan.test.ts +461 -0
  180. package/src/rbac/hooks/usePermissions.test.ts +364 -0
  181. package/src/rbac/hooks/usePermissions.ts +567 -0
  182. package/src/rbac/hooks/useRBAC.simple.test.ts +90 -0
  183. package/src/rbac/hooks/useRBAC.test.ts +551 -0
  184. package/src/{hooks → rbac/hooks}/useRBAC.ts +7 -7
  185. package/src/rbac/index.ts +5 -10
  186. package/src/{providers → rbac/providers}/RBACProvider.tsx +6 -6
  187. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +687 -0
  188. package/src/rbac/providers/index.ts +11 -0
  189. package/src/styles/core.css +0 -55
  190. package/src/utils/formatDate.test.ts +241 -0
  191. package/dist/chunk-AUE24LVR.js +0 -268
  192. package/dist/chunk-AUE24LVR.js.map +0 -1
  193. package/dist/chunk-COBPIXXQ.js +0 -379
  194. package/dist/chunk-COBPIXXQ.js.map +0 -1
  195. package/dist/chunk-OEGRKULD.js.map +0 -1
  196. package/dist/chunk-OYRY44Q2.js +0 -62
  197. package/dist/chunk-OYRY44Q2.js.map +0 -1
  198. package/dist/chunk-T3XIA4AJ.js.map +0 -1
  199. package/dist/chunk-TGDCLPP2.js.map +0 -1
  200. package/src/components/RBAC/PagePermissionGuard.tsx +0 -287
  201. package/src/components/RBAC/RBACGuard.tsx +0 -143
  202. package/src/components/RBAC/RBACProvider.tsx +0 -186
  203. package/src/components/RBAC/RoleBasedContent.tsx +0 -129
  204. package/src/components/RBAC/index.ts +0 -23
  205. package/src/rbac/hooks.ts +0 -570
  206. /package/dist/{DataTable-GX3XERFJ.js.map → DataTable-ZQDRE46Q.js.map} +0 -0
  207. /package/dist/{api-ETQ6YJ3C.js.map → api-H5A3H4IR.js.map} +0 -0
  208. /package/dist/{chunk-C5G2A4PO.js.map → chunk-GWSBHC4J.js.map} +0 -0
  209. /package/dist/{chunk-5EL3KHOQ.js.map → chunk-K6B7BLSE.js.map} +0 -0
  210. /package/dist/{chunk-GSNM5D6H.js.map → chunk-M4RW7PIP.js.map} +0 -0
@@ -1,15 +1,5 @@
1
1
  @import "tailwindcss";
2
2
 
3
- /* @source directives for Tailwind v4 content scanning */
4
- @source "../../src/**/*.{js,ts,jsx,tsx}";
5
- @source "../../src/components/**/*.{js,ts,jsx,tsx}";
6
- @source "../../src/providers/**/*.{js,ts,jsx,tsx}";
7
- @source "../../src/hooks/**/*.{js,ts,jsx,tsx}";
8
- @source "../../src/utils/**/*.{js,ts,jsx,tsx}";
9
- @source "../../src/types/**/*.{js,ts,jsx,tsx}";
10
- @source "../../src/validation/**/*.{js,ts,jsx,tsx}";
11
- @source "../../src/rbac/**/*.{js,ts,jsx,tsx}";
12
-
13
3
  @theme {
14
4
  /* reset all color variables and fonts */
15
5
  --color-*: initial;
@@ -17,55 +7,10 @@
17
7
  --font-serif: "Open Sans", sans-serif;
18
8
  --font-mono: "Reddit Mono", monospace;
19
9
  }
20
-
21
10
  @theme static {
22
-
23
11
  --app-width: 90rem;
24
-
25
- /* MAIN palette - silver */
26
- --color-main-raw: oklch(0.7 0.057 252.02);
27
- --color-main-50: oklch(0.98 0.001 252.02);
28
- --color-main-100: oklch(0.96 0.005 252.02);
29
- --color-main-200: oklch(0.927 0.012 252.02);
30
- --color-main-300: oklch(0.881 0.021 252.02);
31
- --color-main-400: oklch(0.822 0.032 252.02);
32
- --color-main-500: oklch(0.75 0.047 252.02);
33
- --color-main-600: oklch(0.7 0.057 252.02);
34
- --color-main-700: oklch(0.567 0.044 252.02);
35
- --color-main-800: oklch(0.456 0.034 252.02);
36
- --color-main-900: oklch(0.332 0.024 252.02);
37
- --color-main-950: oklch(0.195 0.014 252.02);
38
-
39
- /* SEC palette - violet */
40
- --color-sec-raw: oklch(0.58 0.23 280.75);
41
- --color-sec-50: oklch(0.98 0.003 280.75);
42
- --color-sec-100: oklch(0.96 0.014 280.75);
43
- --color-sec-200: oklch(0.927 0.033 280.75);
44
- --color-sec-300: oklch(0.881 0.059 280.75);
45
- --color-sec-400: oklch(0.822 0.093 280.75);
46
- --color-sec-500: oklch(0.75 0.133 280.75);
47
- --color-sec-600: oklch(0.665 0.182 280.75);
48
- --color-sec-700: oklch(0.58 0.23 280.75);
49
- --color-sec-800: oklch(0.456 0.158 280.75);
50
- --color-sec-900: oklch(0.332 0.099 280.75);
51
- --color-sec-950: oklch(0.195 0.047 280.75);
52
-
53
- /* ACC palette - blood orange */
54
- --color-acc-raw: oklch(0.64 0.21 37.76);
55
- --color-acc-50: oklch(0.98 0.003 37.76);
56
- --color-acc-100: oklch(0.96 0.015 37.76);
57
- --color-acc-200: oklch(0.927 0.035 37.76);
58
- --color-acc-300: oklch(0.881 0.063 37.76);
59
- --color-acc-400: oklch(0.822 0.099 37.76);
60
- --color-acc-500: oklch(0.75 0.143 37.76);
61
- --color-acc-600: oklch(0.64 0.21 37.76);
62
- --color-acc-700: oklch(0.567 0.177 37.76);
63
- --color-acc-800: oklch(0.456 0.13 37.76);
64
- --color-acc-900: oklch(0.332 0.085 37.76);
65
- --color-acc-950: oklch(0.195 0.044 37.76);
66
12
  }
67
13
 
68
-
69
14
  @theme inline {
70
15
  /* Semantic token mapping */
71
16
  --color-border: var(--color-main-500);
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @file formatDate Utility Tests
3
+ * @description Comprehensive tests for the formatDate utility function
4
+ */
5
+
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { formatDate } from './formatting';
8
+
9
+ describe('formatDate Utility', () => {
10
+ let originalLocale: string;
11
+ let originalTimezone: string;
12
+
13
+ beforeEach(() => {
14
+ // Store original values
15
+ originalLocale = Intl.DateTimeFormat().resolvedOptions().locale;
16
+ originalTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
17
+ });
18
+
19
+ afterEach(() => {
20
+ vi.restoreAllMocks();
21
+ });
22
+
23
+ describe('Date Object Input', () => {
24
+ it('formats a Date object correctly', () => {
25
+ const date = new Date('2024-01-15T10:30:00.000Z');
26
+ const result = formatDate(date);
27
+
28
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
29
+ });
30
+
31
+ it('formats different Date objects', () => {
32
+ const dates = [
33
+ new Date('2023-12-25T00:00:00.000Z'),
34
+ new Date('2024-06-15T12:00:00.000Z'),
35
+ new Date('2024-12-31T23:59:59.999Z')
36
+ ];
37
+
38
+ dates.forEach(date => {
39
+ const result = formatDate(date);
40
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
41
+ });
42
+ });
43
+ });
44
+
45
+ describe('String Input', () => {
46
+ it('formats ISO date strings correctly', () => {
47
+ const result = formatDate('2024-01-15T10:30:00.000Z');
48
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
49
+ });
50
+
51
+ it('formats date-only strings correctly', () => {
52
+ const result = formatDate('2024-01-15');
53
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
54
+ });
55
+
56
+ it('formats various date string formats', () => {
57
+ const dateStrings = [
58
+ '2024-01-15',
59
+ '2024-01-15T10:30:00Z',
60
+ '2024-01-15T10:30:00.000Z',
61
+ '2024-01-15T10:30:00+00:00'
62
+ ];
63
+
64
+ dateStrings.forEach(dateString => {
65
+ const result = formatDate(dateString);
66
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
67
+ });
68
+ });
69
+ });
70
+
71
+ describe('Number Input', () => {
72
+ it('formats timestamp numbers correctly', () => {
73
+ const timestamp = new Date('2024-01-15T10:30:00.000Z').getTime();
74
+ const result = formatDate(timestamp);
75
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
76
+ });
77
+
78
+ it('formats Unix timestamps correctly', () => {
79
+ const unixTimestamp = Math.floor(new Date('2024-01-15T10:30:00.000Z').getTime() / 1000);
80
+ const result = formatDate(unixTimestamp * 1000); // Convert back to milliseconds
81
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
82
+ });
83
+ });
84
+
85
+ describe('Edge Cases', () => {
86
+ it('handles invalid date strings gracefully', () => {
87
+ const invalidDates = [
88
+ 'invalid-date',
89
+ 'not-a-date',
90
+ '2024-13-45', // Invalid month and day
91
+ '2024-02-30' // Invalid day for February
92
+ ];
93
+
94
+ invalidDates.forEach(invalidDate => {
95
+ const result = formatDate(invalidDate);
96
+ // Should return a string even for invalid dates (may be "Invalid Date")
97
+ expect(typeof result).toBe('string');
98
+ expect(result.length).toBeGreaterThan(0);
99
+ });
100
+ });
101
+
102
+ it('handles empty string input', () => {
103
+ const result = formatDate('');
104
+ expect(typeof result).toBe('string');
105
+ });
106
+
107
+ it('handles null and undefined input', () => {
108
+ // @ts-expect-error - Testing runtime behavior with invalid types
109
+ expect(() => formatDate(null)).toThrow();
110
+ // @ts-expect-error - Testing runtime behavior with invalid types
111
+ expect(() => formatDate(undefined)).toThrow();
112
+ });
113
+
114
+ it('handles very large numbers', () => {
115
+ const largeNumber = Number.MAX_SAFE_INTEGER;
116
+ const result = formatDate(largeNumber);
117
+ expect(typeof result).toBe('string');
118
+ });
119
+
120
+ it('handles negative numbers', () => {
121
+ const negativeNumber = -1000000;
122
+ const result = formatDate(negativeNumber);
123
+ expect(typeof result).toBe('string');
124
+ });
125
+
126
+ it('handles zero', () => {
127
+ const result = formatDate(0);
128
+ expect(typeof result).toBe('string');
129
+ });
130
+ });
131
+
132
+ describe('Format Consistency', () => {
133
+ it('returns consistent format for same date', () => {
134
+ const date = new Date('2024-01-15T10:30:00.000Z');
135
+ const result1 = formatDate(date);
136
+ const result2 = formatDate(date);
137
+
138
+ expect(result1).toBe(result2);
139
+ });
140
+
141
+ it('returns consistent format across different input types for same date', () => {
142
+ const date = new Date('2024-01-15T10:30:00.000Z');
143
+ const dateString = '2024-01-15T10:30:00.000Z';
144
+ const timestamp = date.getTime();
145
+
146
+ const result1 = formatDate(date);
147
+ const result2 = formatDate(dateString);
148
+ const result3 = formatDate(timestamp);
149
+
150
+ expect(result1).toBe(result2);
151
+ expect(result2).toBe(result3);
152
+ });
153
+ });
154
+
155
+ describe('Locale Handling', () => {
156
+ it('uses default locale when no locale is specified', () => {
157
+ const date = new Date('2024-01-15T10:30:00.000Z');
158
+ const result = formatDate(date);
159
+
160
+ // Should use the default locale format
161
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
162
+ });
163
+
164
+ it('handles different locales correctly', () => {
165
+ const date = new Date('2024-01-15T10:30:00.000Z');
166
+
167
+ const result = formatDate(date);
168
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
169
+ });
170
+ });
171
+
172
+ describe('Time Zone Handling', () => {
173
+ it('handles different time zones correctly', () => {
174
+ const date = new Date('2024-01-15T10:30:00.000Z');
175
+ const result = formatDate(date);
176
+
177
+ // Should format consistently regardless of timezone
178
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
179
+ });
180
+ });
181
+
182
+ describe('Performance', () => {
183
+ it('handles large number of calls efficiently', () => {
184
+ const date = new Date('2024-01-15T10:30:00.000Z');
185
+ const startTime = performance.now();
186
+
187
+ // Call formatDate many times
188
+ for (let i = 0; i < 1000; i++) {
189
+ formatDate(date);
190
+ }
191
+
192
+ const endTime = performance.now();
193
+ const duration = endTime - startTime;
194
+
195
+ // Should complete in reasonable time (less than 100ms for 1000 calls)
196
+ expect(duration).toBeLessThan(100);
197
+ });
198
+ });
199
+
200
+ describe('Type Safety', () => {
201
+ it('accepts valid Date types', () => {
202
+ const date = new Date();
203
+ expect(() => formatDate(date)).not.toThrow();
204
+ });
205
+
206
+ it('accepts valid string types', () => {
207
+ const dateString = '2024-01-15';
208
+ expect(() => formatDate(dateString)).not.toThrow();
209
+ });
210
+
211
+ it('accepts valid number types', () => {
212
+ const timestamp = Date.now();
213
+ expect(() => formatDate(timestamp)).not.toThrow();
214
+ });
215
+ });
216
+
217
+ describe('Return Value', () => {
218
+ it('returns a string', () => {
219
+ const date = new Date('2024-01-15T10:30:00.000Z');
220
+ const result = formatDate(date);
221
+
222
+ expect(typeof result).toBe('string');
223
+ });
224
+
225
+ it('returns non-empty string for valid dates', () => {
226
+ const date = new Date('2024-01-15T10:30:00.000Z');
227
+ const result = formatDate(date);
228
+
229
+ expect(result).toBeTruthy();
230
+ expect(result.length).toBeGreaterThan(0);
231
+ });
232
+
233
+ it('returns expected format pattern', () => {
234
+ const date = new Date('2024-01-15T10:30:00.000Z');
235
+ const result = formatDate(date);
236
+
237
+ // Should match pattern: "MMM DD, YYYY"
238
+ expect(result).toMatch(/^[A-Za-z]{3} \d{1,2}, \d{4}$/);
239
+ });
240
+ });
241
+ });
@@ -1,268 +0,0 @@
1
- import {
2
- getAccessLevel,
3
- getPermissionMap,
4
- isPermitted,
5
- isPermittedCached
6
- } from "./chunk-C5G2A4PO.js";
7
-
8
- // src/rbac/hooks.ts
9
- import { useState, useEffect, useCallback, useMemo } from "react";
10
- function usePermissions(userId, scope) {
11
- const [permissions, setPermissions] = useState({});
12
- const [isLoading, setIsLoading] = useState(true);
13
- const [error, setError] = useState(null);
14
- const fetchPermissions = useCallback(async () => {
15
- if (!userId) {
16
- setPermissions({});
17
- setIsLoading(false);
18
- return;
19
- }
20
- try {
21
- setIsLoading(true);
22
- setError(null);
23
- const result = await getPermissionMap({ userId, scope });
24
- setPermissions(result);
25
- } catch (err) {
26
- setError(err instanceof Error ? err : new Error("Failed to fetch permissions"));
27
- } finally {
28
- setIsLoading(false);
29
- }
30
- }, [userId, scope.organisationId, scope.eventId, scope.appId]);
31
- useEffect(() => {
32
- fetchPermissions();
33
- }, [fetchPermissions]);
34
- return {
35
- permissions,
36
- isLoading,
37
- error,
38
- refetch: fetchPermissions
39
- };
40
- }
41
- function useCan(userId, scope, permission, pageId, useCache = true) {
42
- const [can, setCan] = useState(false);
43
- const [isLoading, setIsLoading] = useState(true);
44
- const [error, setError] = useState(null);
45
- const check = useCallback(async () => {
46
- console.log("[useCan] check() called with:", { userId, scope, permission, pageId });
47
- console.log("[useCan] Hook parameters:", { userId, scope, permission, pageId, useCache });
48
- if (!userId) {
49
- console.log("[useCan] No userId, denying access");
50
- setCan(false);
51
- setIsLoading(false);
52
- return;
53
- }
54
- try {
55
- const { isSuperAdmin } = await import("./api-ETQ6YJ3C.js");
56
- const isSuper = await isSuperAdmin(userId);
57
- if (isSuper) {
58
- console.log("[useCan] User is super admin, granting access");
59
- setCan(true);
60
- setIsLoading(false);
61
- return;
62
- }
63
- } catch (error2) {
64
- console.error("[useCan] Error checking super admin status:", error2);
65
- }
66
- if (!scope || !scope.organisationId || !scope.appId) {
67
- console.log("[useCan] Incomplete scope, waiting for resolution:", scope);
68
- setCan(false);
69
- setIsLoading(true);
70
- return;
71
- }
72
- console.log("[useCan] Scope is complete, checking permission...");
73
- console.log("[useCan] Detailed scope info:", {
74
- organisationId: scope.organisationId,
75
- eventId: scope.eventId,
76
- appId: scope.appId,
77
- permission,
78
- pageId
79
- });
80
- try {
81
- setIsLoading(true);
82
- setError(null);
83
- console.log("[useCan] About to call isPermitted/isPermittedCached...");
84
- const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
85
- console.log("[useCan] Permission check result:", result);
86
- console.log("[useCan] Permission check details:", {
87
- userId,
88
- scope,
89
- permission,
90
- pageId,
91
- result,
92
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
93
- });
94
- setCan(result);
95
- } catch (err) {
96
- console.error("[useCan] Permission check error:", err);
97
- console.error("[useCan] Error details:", {
98
- userId,
99
- scope,
100
- permission,
101
- pageId,
102
- error: err instanceof Error ? err.message : "Unknown error",
103
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
104
- });
105
- setError(err instanceof Error ? err : new Error("Failed to check permission"));
106
- setCan(false);
107
- } finally {
108
- setIsLoading(false);
109
- }
110
- }, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);
111
- useEffect(() => {
112
- check();
113
- }, [check]);
114
- return {
115
- can,
116
- isLoading,
117
- error,
118
- check
119
- };
120
- }
121
- function useAccessLevel(userId, scope) {
122
- const [accessLevel, setAccessLevel] = useState(null);
123
- const [isLoading, setIsLoading] = useState(true);
124
- const [error, setError] = useState(null);
125
- const fetchAccessLevel = useCallback(async () => {
126
- if (!userId) {
127
- setAccessLevel(null);
128
- setIsLoading(false);
129
- return;
130
- }
131
- try {
132
- setIsLoading(true);
133
- setError(null);
134
- const result = await getAccessLevel({ userId, scope });
135
- setAccessLevel(result);
136
- } catch (err) {
137
- setError(err instanceof Error ? err : new Error("Failed to fetch access level"));
138
- setAccessLevel(null);
139
- } finally {
140
- setIsLoading(false);
141
- }
142
- }, [userId, scope.organisationId, scope.eventId, scope.appId]);
143
- useEffect(() => {
144
- fetchAccessLevel();
145
- }, [fetchAccessLevel]);
146
- return {
147
- accessLevel,
148
- isLoading,
149
- error,
150
- refetch: fetchAccessLevel
151
- };
152
- }
153
- function useMultiplePermissions(userId, scope, permissions, pageId, useCache = true) {
154
- const [permissionResults, setPermissionResults] = useState({});
155
- const [isLoading, setIsLoading] = useState(true);
156
- const [error, setError] = useState(null);
157
- const fetchPermissions = useCallback(async () => {
158
- if (!userId || permissions.length === 0) {
159
- setPermissionResults({});
160
- setIsLoading(false);
161
- return;
162
- }
163
- try {
164
- setIsLoading(true);
165
- setError(null);
166
- const results = {};
167
- const promises = permissions.map(async (permission) => {
168
- const result = useCache ? await isPermittedCached({ userId, scope, permission, pageId }) : await isPermitted({ userId, scope, permission, pageId });
169
- return { permission, result };
170
- });
171
- const resolved = await Promise.all(promises);
172
- resolved.forEach(({ permission, result }) => {
173
- results[permission] = result;
174
- });
175
- setPermissionResults(results);
176
- } catch (err) {
177
- setError(err instanceof Error ? err : new Error("Failed to check permissions"));
178
- setPermissionResults({});
179
- } finally {
180
- setIsLoading(false);
181
- }
182
- }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, pageId, useCache]);
183
- useEffect(() => {
184
- fetchPermissions();
185
- }, [fetchPermissions]);
186
- return {
187
- permissions: permissionResults,
188
- isLoading,
189
- error,
190
- refetch: fetchPermissions
191
- };
192
- }
193
- function useHasAnyPermission(userId, scope, permissions, pageId) {
194
- const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
195
- userId,
196
- scope,
197
- permissions,
198
- pageId
199
- );
200
- const hasAny = useMemo(() => {
201
- return Object.values(permissionResults).some(Boolean);
202
- }, [permissionResults]);
203
- return {
204
- hasAny,
205
- isLoading,
206
- error,
207
- refetch
208
- };
209
- }
210
- function useHasAllPermissions(userId, scope, permissions, pageId) {
211
- const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(
212
- userId,
213
- scope,
214
- permissions,
215
- pageId
216
- );
217
- const hasAll = useMemo(() => {
218
- return Object.values(permissionResults).every(Boolean);
219
- }, [permissionResults]);
220
- return {
221
- hasAll,
222
- isLoading,
223
- error,
224
- refetch
225
- };
226
- }
227
- function useCachedPermissions(userId, scope) {
228
- const [permissions, setPermissions] = useState({});
229
- const [isLoading, setIsLoading] = useState(true);
230
- const [error, setError] = useState(null);
231
- const fetchCachedPermissions = useCallback(async () => {
232
- if (!userId) {
233
- setPermissions({});
234
- setIsLoading(false);
235
- return;
236
- }
237
- try {
238
- setIsLoading(true);
239
- setError(null);
240
- const result = await getPermissionMap({ userId, scope });
241
- setPermissions(result);
242
- } catch (err) {
243
- setError(err instanceof Error ? err : new Error("Failed to fetch cached permissions"));
244
- } finally {
245
- setIsLoading(false);
246
- }
247
- }, [userId, scope.organisationId, scope.eventId, scope.appId]);
248
- useEffect(() => {
249
- fetchCachedPermissions();
250
- }, [fetchCachedPermissions]);
251
- return {
252
- permissions,
253
- isLoading,
254
- error,
255
- refetch: fetchCachedPermissions
256
- };
257
- }
258
-
259
- export {
260
- usePermissions,
261
- useCan,
262
- useAccessLevel,
263
- useMultiplePermissions,
264
- useHasAnyPermission,
265
- useHasAllPermissions,
266
- useCachedPermissions
267
- };
268
- //# sourceMappingURL=chunk-AUE24LVR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/rbac/hooks.ts"],"sourcesContent":["/**\n * RBAC React 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 } from 'react';\nimport { \n UUID, \n Scope, \n Permission, \n AccessLevel, \n PermissionMap,\n UsePermissionsReturn,\n UseCanReturn\n} 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 - Permission scope\n * @returns Permission data and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = usePermissions(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\n * \n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['page-1']?.includes('read') && <ReadButton />}\n * {permissions['page-1']?.includes('manage') && <ManageButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions(userId: UUID, scope: Scope): UsePermissionsReturn {\n const [permissions, setPermissions] = useState<PermissionMap>({});\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchPermissions = 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 result = await getPermissionMap({ userId, scope });\n setPermissions(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchPermissions();\n }, [fetchPermissions]);\n\n return {\n permissions,\n isLoading,\n error,\n refetch: fetchPermissions,\n };\n}\n\n/**\n * Hook to check if user has a specific permission\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @param permission - Permission to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results (default: true)\n * @returns Permission check result and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { can, isLoading } = useCan(\n * 'user-123',\n * { organisationId: 'org-456' },\n * 'manage:events',\n * 'page-789'\n * );\n * \n * if (isLoading) return <div>Checking permission...</div>;\n * \n * return (\n * <div>\n * {can ? <AdminPanel /> : <AccessDenied />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useCan(\n userId: UUID,\n scope: Scope,\n permission: Permission,\n pageId?: UUID,\n useCache: boolean = true\n): UseCanReturn {\n const [can, setCan] = useState(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const check = useCallback(async () => {\n console.log('[useCan] check() called with:', { userId, scope, permission, pageId });\n console.log('[useCan] Hook parameters:', { userId, scope, permission, pageId, useCache });\n \n if (!userId) {\n console.log('[useCan] No userId, denying access');\n setCan(false);\n setIsLoading(false);\n return;\n }\n\n // Check for super admin status first - super admins bypass all scope requirements\n try {\n const { isSuperAdmin } = await import('./api');\n const isSuper = await isSuperAdmin(userId);\n if (isSuper) {\n console.log('[useCan] User is super admin, granting access');\n setCan(true);\n setIsLoading(false);\n return;\n }\n } catch (error) {\n console.error('[useCan] Error checking super admin status:', error);\n // Continue with normal permission check if super admin check fails\n }\n\n // Check if scope is incomplete (missing required fields)\n if (!scope || !scope.organisationId || !scope.appId) {\n console.log('[useCan] Incomplete scope, waiting for resolution:', scope);\n setCan(false);\n setIsLoading(true); // Keep loading until scope is complete\n return;\n }\n\n console.log('[useCan] Scope is complete, checking permission...');\n console.log('[useCan] Detailed scope info:', {\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n permission,\n pageId\n });\n \n try {\n setIsLoading(true);\n setError(null);\n \n console.log('[useCan] About to call isPermitted/isPermittedCached...');\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission, pageId })\n : await isPermitted({ userId, scope, permission, pageId });\n \n console.log('[useCan] Permission check result:', result);\n console.log('[useCan] Permission check details:', {\n userId,\n scope,\n permission,\n pageId,\n result,\n timestamp: new Date().toISOString()\n });\n setCan(result);\n } catch (err) {\n console.error('[useCan] Permission check error:', err);\n console.error('[useCan] Error details:', {\n userId,\n scope,\n permission,\n pageId,\n error: err instanceof Error ? err.message : 'Unknown error',\n timestamp: new Date().toISOString()\n });\n setError(err instanceof Error ? err : new Error('Failed to check permission'));\n setCan(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permission, pageId, useCache]);\n\n useEffect(() => {\n check();\n }, [check]);\n\n return {\n can,\n isLoading,\n error,\n check,\n };\n}\n\n/**\n * Hook to get user's access level in a scope\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @returns Access level and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { accessLevel, isLoading } = useAccessLevel(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\n * \n * if (isLoading) return <div>Loading...</div>;\n * \n * return (\n * <div>\n * {accessLevel === 'super' && <SuperAdminPanel />}\n * {accessLevel === 'admin' && <AdminPanel />}\n * {accessLevel === 'planner' && <PlannerPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAccessLevel(userId: UUID, scope: Scope): {\n accessLevel: AccessLevel | null;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [accessLevel, setAccessLevel] = useState<AccessLevel | null>(null);\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(null);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const result = await getAccessLevel({ userId, scope });\n setAccessLevel(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch access level'));\n setAccessLevel(null);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\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 - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results (default: true)\n * @returns Object with permission results and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading } = useMultiplePermissions(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events', 'delete:events']\n * );\n * \n * return (\n * <div>\n * {permissions['read:events'] && <ReadButton />}\n * {permissions['manage:events'] && <ManageButton />}\n * {permissions['delete:events'] && <DeleteButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMultiplePermissions(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID,\n useCache: boolean = true\n): {\n permissions: Record<Permission, boolean>;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [permissionResults, setPermissionResults] = 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 fetchPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setPermissionResults({} as Record<Permission, boolean>);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const results: Record<Permission, boolean> = {} as Record<Permission, boolean>;\n \n // Check all permissions in parallel\n const promises = permissions.map(async (permission) => {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission, pageId })\n : await isPermitted({ userId, scope, permission, pageId });\n \n return { permission, result };\n });\n \n const resolved = await Promise.all(promises);\n \n resolved.forEach(({ permission, result }) => {\n results[permission] = result;\n });\n \n setPermissionResults(results);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setPermissionResults({} as Record<Permission, boolean>);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, pageId, useCache]);\n\n useEffect(() => {\n fetchPermissions();\n }, [fetchPermissions]);\n\n return {\n permissions: permissionResults,\n isLoading,\n error,\n refetch: fetchPermissions,\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 - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @returns True if user has any permission and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAny, isLoading } = useHasAnyPermission(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events']\n * );\n * \n * return (\n * <div>\n * {hasAny ? <EventContent /> : <AccessDenied />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useHasAnyPermission(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID\n): {\n hasAny: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(\n userId,\n scope,\n permissions,\n pageId\n );\n\n const hasAny = useMemo(() => {\n return Object.values(permissionResults).some(Boolean);\n }, [permissionResults]);\n\n return {\n hasAny,\n isLoading,\n error,\n refetch,\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 - Permission scope\n * @param permissions - Array of permissions to check\n * @param pageId - Optional page ID\n * @returns True if user has all permissions and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAll, isLoading } = useHasAllPermissions(\n * 'user-123',\n * { organisationId: 'org-456' },\n * ['read:events', 'manage:events']\n * );\n * \n * return (\n * <div>\n * {hasAll ? <FullAccessPanel /> : <LimitedAccessPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useHasAllPermissions(\n userId: UUID,\n scope: Scope,\n permissions: Permission[],\n pageId?: UUID\n): {\n hasAll: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const { permissions: permissionResults, isLoading, error, refetch } = useMultiplePermissions(\n userId,\n scope,\n permissions,\n pageId\n );\n\n const hasAll = useMemo(() => {\n return Object.values(permissionResults).every(Boolean);\n }, [permissionResults]);\n\n return {\n hasAll,\n isLoading,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to read cached permissions (contract requirement)\n * \n * This hook only reads from the core cache and does not perform\n * any bespoke caching as per the contract requirements.\n * \n * @param userId - User ID\n * @param scope - Permission scope\n * @returns Cached permission data and loading state\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = useCachedPermissions(\n * 'user-123',\n * { organisationId: 'org-456' }\n * );\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['page-1']?.includes('read') && <ReadButton />}\n * {permissions['page-1']?.includes('manage') && <ManageButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useCachedPermissions(userId: UUID, scope: Scope): {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\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 // Use cached version of getPermissionMap\n const result = await getPermissionMap({ userId, scope });\n setPermissions(result);\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.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n return {\n permissions,\n isLoading,\n error,\n refetch: fetchCachedPermissions,\n };\n}\n"],"mappings":";;;;;;;;AASA,SAAS,UAAU,WAAW,aAAa,eAAe;AA4CnD,SAAS,eAAe,QAAc,OAAoC;AAC/E,QAAM,CAAC,aAAa,cAAc,IAAI,SAAwB,CAAC,CAAC;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,mBAAmB,YAAY,YAAY;AAC/C,QAAI,CAAC,QAAQ;AACX,qBAAe,CAAC,CAAC;AACjB,mBAAa,KAAK;AAClB;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,YAAM,SAAS,MAAM,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AACvD,qBAAe,MAAM;AAAA,IACvB,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,6BAA6B,CAAC;AAAA,IAChF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,gBAAgB,MAAM,SAAS,MAAM,KAAK,CAAC;AAE7D,YAAU,MAAM;AACd,qBAAiB;AAAA,EACnB,GAAG,CAAC,gBAAgB,CAAC;AAErB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;AAgCO,SAAS,OACd,QACA,OACA,YACA,QACA,WAAoB,MACN;AACd,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,KAAK;AACpC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,QAAQ,YAAY,YAAY;AACpC,YAAQ,IAAI,iCAAiC,EAAE,QAAQ,OAAO,YAAY,OAAO,CAAC;AAClF,YAAQ,IAAI,6BAA6B,EAAE,QAAQ,OAAO,YAAY,QAAQ,SAAS,CAAC;AAExF,QAAI,CAAC,QAAQ;AACX,cAAQ,IAAI,oCAAoC;AAChD,aAAO,KAAK;AACZ,mBAAa,KAAK;AAClB;AAAA,IACF;AAGA,QAAI;AACF,YAAM,EAAE,aAAa,IAAI,MAAM,OAAO,mBAAO;AAC7C,YAAM,UAAU,MAAM,aAAa,MAAM;AACzC,UAAI,SAAS;AACX,gBAAQ,IAAI,+CAA+C;AAC3D,eAAO,IAAI;AACX,qBAAa,KAAK;AAClB;AAAA,MACF;AAAA,IACF,SAASA,QAAO;AACd,cAAQ,MAAM,+CAA+CA,MAAK;AAAA,IAEpE;AAGA,QAAI,CAAC,SAAS,CAAC,MAAM,kBAAkB,CAAC,MAAM,OAAO;AACnD,cAAQ,IAAI,sDAAsD,KAAK;AACvE,aAAO,KAAK;AACZ,mBAAa,IAAI;AACjB;AAAA,IACF;AAEA,YAAQ,IAAI,oDAAoD;AAChE,YAAQ,IAAI,iCAAiC;AAAA,MAC3C,gBAAgB,MAAM;AAAA,MACtB,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,cAAQ,IAAI,yDAAyD;AACrE,YAAM,SAAS,WACX,MAAM,kBAAkB,EAAE,QAAQ,OAAO,YAAY,OAAO,CAAC,IAC7D,MAAM,YAAY,EAAE,QAAQ,OAAO,YAAY,OAAO,CAAC;AAE3D,cAAQ,IAAI,qCAAqC,MAAM;AACvD,cAAQ,IAAI,sCAAsC;AAAA,QAChD;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AACD,aAAO,MAAM;AAAA,IACf,SAAS,KAAK;AACZ,cAAQ,MAAM,oCAAoC,GAAG;AACrD,cAAQ,MAAM,2BAA2B;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC5C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC,CAAC;AACD,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,4BAA4B,CAAC;AAC7E,aAAO,KAAK;AAAA,IACd,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,gBAAgB,MAAM,SAAS,MAAM,OAAO,YAAY,QAAQ,QAAQ,CAAC;AAE3F,YAAU,MAAM;AACd,UAAM;AAAA,EACR,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA6BO,SAAS,eAAe,QAAc,OAK3C;AACA,QAAM,CAAC,aAAa,cAAc,IAAI,SAA6B,IAAI;AACvE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,mBAAmB,YAAY,YAAY;AAC/C,QAAI,CAAC,QAAQ;AACX,qBAAe,IAAI;AACnB,mBAAa,KAAK;AAClB;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,YAAM,SAAS,MAAM,eAAe,EAAE,QAAQ,MAAM,CAAC;AACrD,qBAAe,MAAM;AAAA,IACvB,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,8BAA8B,CAAC;AAC/E,qBAAe,IAAI;AAAA,IACrB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,gBAAgB,MAAM,SAAS,MAAM,KAAK,CAAC;AAE7D,YAAU,MAAM;AACd,qBAAiB;AAAA,EACnB,GAAG,CAAC,gBAAgB,CAAC;AAErB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;AA+BO,SAAS,uBACd,QACA,OACA,aACA,QACA,WAAoB,MAMpB;AACA,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAsC,CAAC,CAAgC;AACzH,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,mBAAmB,YAAY,YAAY;AAC/C,QAAI,CAAC,UAAU,YAAY,WAAW,GAAG;AACvC,2BAAqB,CAAC,CAAgC;AACtD,mBAAa,KAAK;AAClB;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,YAAM,UAAuC,CAAC;AAG9C,YAAM,WAAW,YAAY,IAAI,OAAO,eAAe;AACrD,cAAM,SAAS,WACX,MAAM,kBAAkB,EAAE,QAAQ,OAAO,YAAY,OAAO,CAAC,IAC7D,MAAM,YAAY,EAAE,QAAQ,OAAO,YAAY,OAAO,CAAC;AAE3D,eAAO,EAAE,YAAY,OAAO;AAAA,MAC9B,CAAC;AAED,YAAM,WAAW,MAAM,QAAQ,IAAI,QAAQ;AAE3C,eAAS,QAAQ,CAAC,EAAE,YAAY,OAAO,MAAM;AAC3C,gBAAQ,UAAU,IAAI;AAAA,MACxB,CAAC;AAED,2BAAqB,OAAO;AAAA,IAC9B,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,6BAA6B,CAAC;AAC9E,2BAAqB,CAAC,CAAgC;AAAA,IACxD,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,gBAAgB,MAAM,SAAS,MAAM,OAAO,aAAa,QAAQ,QAAQ,CAAC;AAE5F,YAAU,MAAM;AACd,qBAAiB;AAAA,EACnB,GAAG,CAAC,gBAAgB,CAAC;AAErB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;AA4BO,SAAS,oBACd,QACA,OACA,aACA,QAMA;AACA,QAAM,EAAE,aAAa,mBAAmB,WAAW,OAAO,QAAQ,IAAI;AAAA,IACpE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,MAAM;AAC3B,WAAO,OAAO,OAAO,iBAAiB,EAAE,KAAK,OAAO;AAAA,EACtD,GAAG,CAAC,iBAAiB,CAAC;AAEtB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BO,SAAS,qBACd,QACA,OACA,aACA,QAMA;AACA,QAAM,EAAE,aAAa,mBAAmB,WAAW,OAAO,QAAQ,IAAI;AAAA,IACpE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,MAAM;AAC3B,WAAO,OAAO,OAAO,iBAAiB,EAAE,MAAM,OAAO;AAAA,EACvD,GAAG,CAAC,iBAAiB,CAAC;AAEtB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAgCO,SAAS,qBAAqB,QAAc,OAKjD;AACA,QAAM,CAAC,aAAa,cAAc,IAAI,SAAwB,CAAC,CAAC;AAChE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,QAAM,yBAAyB,YAAY,YAAY;AACrD,QAAI,CAAC,QAAQ;AACX,qBAAe,CAAC,CAAC;AACjB,mBAAa,KAAK;AAClB;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAGb,YAAM,SAAS,MAAM,iBAAiB,EAAE,QAAQ,MAAM,CAAC;AACvD,qBAAe,MAAM;AAAA,IACvB,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,oCAAoC,CAAC;AAAA,IACvF,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,gBAAgB,MAAM,SAAS,MAAM,KAAK,CAAC;AAE7D,YAAU,MAAM;AACd,2BAAuB;AAAA,EACzB,GAAG,CAAC,sBAAsB,CAAC;AAE3B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX;AACF;","names":["error"]}