@jmruthers/pace-core 0.6.1 → 0.6.2

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 (502) hide show
  1. package/CHANGELOG.md +43 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +18 -91
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
  17. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  18. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  19. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  20. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  21. package/dist/chunk-24UVZUZG.js.map +1 -0
  22. package/dist/{chunk-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
  23. package/dist/chunk-2UOI2FG5.js.map +1 -0
  24. package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
  25. package/dist/chunk-3XC4CPTD.js.map +1 -0
  26. package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
  27. package/dist/chunk-6J4GEEJR.js.map +1 -0
  28. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  29. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
  31. package/dist/chunk-EHMR7VYL.js.map +1 -0
  32. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  33. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  34. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  35. package/dist/chunk-FFQEQTNW.js.map +1 -0
  36. package/dist/chunk-FMUCXFII.js +76 -0
  37. package/dist/chunk-FMUCXFII.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
  43. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  44. package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
  45. package/dist/chunk-NECFR5MM.js.map +1 -0
  46. package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
  47. package/dist/chunk-SFZUDBL5.js.map +1 -0
  48. package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
  49. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  50. package/dist/components.d.ts +5 -5
  51. package/dist/components.js +14 -11
  52. package/dist/components.js.map +1 -1
  53. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  54. package/dist/hooks.d.ts +55 -122
  55. package/dist/hooks.js +8 -12
  56. package/dist/hooks.js.map +1 -1
  57. package/dist/index.d.ts +60 -13
  58. package/dist/index.js +19 -19
  59. package/dist/index.js.map +1 -1
  60. package/dist/providers.d.ts +21 -3
  61. package/dist/providers.js +2 -2
  62. package/dist/rbac/index.d.ts +145 -114
  63. package/dist/rbac/index.js +8 -11
  64. package/dist/theming/runtime.d.ts +1 -13
  65. package/dist/theming/runtime.js +1 -1
  66. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  67. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  68. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  69. package/dist/types.d.ts +2 -2
  70. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
  71. package/dist/utils.d.ts +4 -5
  72. package/dist/utils.js +14 -14
  73. package/dist/utils.js.map +1 -1
  74. package/docs/api/README.md +7 -1
  75. package/docs/api/classes/ColumnFactory.md +8 -8
  76. package/docs/api/classes/InvalidScopeError.md +4 -4
  77. package/docs/api/classes/Logger.md +1 -1
  78. package/docs/api/classes/MissingUserContextError.md +4 -4
  79. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  80. package/docs/api/classes/PermissionDeniedError.md +4 -4
  81. package/docs/api/classes/RBACAuditManager.md +1 -1
  82. package/docs/api/classes/RBACCache.md +1 -1
  83. package/docs/api/classes/RBACEngine.md +1 -1
  84. package/docs/api/classes/RBACError.md +4 -4
  85. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  86. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  87. package/docs/api/classes/StorageUtils.md +1 -1
  88. package/docs/api/enums/FileCategory.md +1 -1
  89. package/docs/api/enums/LogLevel.md +1 -1
  90. package/docs/api/enums/RBACErrorCode.md +1 -1
  91. package/docs/api/enums/RPCFunction.md +1 -1
  92. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  93. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  94. package/docs/api/interfaces/AggregateConfig.md +4 -4
  95. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  96. package/docs/api/interfaces/AvatarProps.md +1 -1
  97. package/docs/api/interfaces/BadgeProps.md +9 -2
  98. package/docs/api/interfaces/ButtonProps.md +7 -4
  99. package/docs/api/interfaces/CalendarProps.md +8 -5
  100. package/docs/api/interfaces/CardProps.md +8 -5
  101. package/docs/api/interfaces/ColorPalette.md +1 -1
  102. package/docs/api/interfaces/ColorShade.md +1 -1
  103. package/docs/api/interfaces/ComplianceResult.md +1 -1
  104. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  105. package/docs/api/interfaces/DataRecord.md +1 -1
  106. package/docs/api/interfaces/DataTableAction.md +24 -21
  107. package/docs/api/interfaces/DataTableColumn.md +31 -31
  108. package/docs/api/interfaces/DataTableProps.md +1 -1
  109. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  110. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  111. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  112. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  113. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  115. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  116. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  117. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  118. package/docs/api/interfaces/ExportColumn.md +1 -1
  119. package/docs/api/interfaces/ExportOptions.md +8 -8
  120. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  121. package/docs/api/interfaces/FileMetadata.md +1 -1
  122. package/docs/api/interfaces/FileReference.md +1 -1
  123. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  124. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  125. package/docs/api/interfaces/FileUploadProps.md +26 -23
  126. package/docs/api/interfaces/FooterProps.md +10 -8
  127. package/docs/api/interfaces/FormFieldProps.md +10 -10
  128. package/docs/api/interfaces/FormProps.md +1 -1
  129. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  130. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  131. package/docs/api/interfaces/InputProps.md +7 -4
  132. package/docs/api/interfaces/LabelProps.md +1 -1
  133. package/docs/api/interfaces/LoggerConfig.md +1 -1
  134. package/docs/api/interfaces/LoginFormProps.md +14 -11
  135. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  136. package/docs/api/interfaces/NavigationContextType.md +1 -1
  137. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  138. package/docs/api/interfaces/NavigationItem.md +11 -11
  139. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  140. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  141. package/docs/api/interfaces/Organisation.md +1 -1
  142. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  143. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  144. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  145. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  146. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  147. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  148. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  149. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  150. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  151. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  152. package/docs/api/interfaces/PaletteData.md +1 -1
  153. package/docs/api/interfaces/ParsedAddress.md +1 -1
  154. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  155. package/docs/api/interfaces/ProgressProps.md +1 -1
  156. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  157. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  158. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  159. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  160. package/docs/api/interfaces/QuickFix.md +1 -1
  161. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  162. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  163. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  164. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  165. package/docs/api/interfaces/RBACConfig.md +1 -1
  166. package/docs/api/interfaces/RBACContext.md +1 -1
  167. package/docs/api/interfaces/RBACLogger.md +1 -1
  168. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  169. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  170. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  171. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  172. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  173. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  174. package/docs/api/interfaces/RBACResult.md +1 -1
  175. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  176. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  177. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  178. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  181. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  182. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  183. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  184. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  185. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  186. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  187. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  188. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  189. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  190. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  191. package/docs/api/interfaces/RouteConfig.md +1 -1
  192. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  193. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  194. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  195. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  196. package/docs/api/interfaces/SetupIssue.md +1 -1
  197. package/docs/api/interfaces/StorageConfig.md +1 -1
  198. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  199. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  200. package/docs/api/interfaces/StorageListOptions.md +1 -1
  201. package/docs/api/interfaces/StorageListResult.md +1 -1
  202. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  203. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  204. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  205. package/docs/api/interfaces/StyleImport.md +1 -1
  206. package/docs/api/interfaces/SwitchProps.md +1 -1
  207. package/docs/api/interfaces/TabsContentProps.md +1 -1
  208. package/docs/api/interfaces/TabsListProps.md +1 -1
  209. package/docs/api/interfaces/TabsProps.md +1 -1
  210. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  211. package/docs/api/interfaces/TextareaProps.md +1 -1
  212. package/docs/api/interfaces/ToastActionElement.md +4 -1
  213. package/docs/api/interfaces/ToastProps.md +1 -1
  214. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  215. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  216. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  217. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  218. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  219. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  220. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  221. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  222. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  223. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  224. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  225. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  226. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  227. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  228. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  229. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  230. package/docs/api/interfaces/UserEventAccess.md +14 -11
  231. package/docs/api/interfaces/UserMenuProps.md +8 -6
  232. package/docs/api/interfaces/UserProfile.md +1 -1
  233. package/docs/api/modules.md +575 -634
  234. package/docs/architecture/database-schema-requirements.md +161 -0
  235. package/docs/core-concepts/rbac-system.md +3 -3
  236. package/docs/documentation-index.md +2 -4
  237. package/docs/getting-started/cursor-rules.md +2 -1
  238. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  239. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  240. package/docs/migration/README.md +52 -6
  241. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  242. package/docs/migration/database-changes-december-2025.md +3 -3
  243. package/docs/rbac/event-based-apps.md +1 -1
  244. package/docs/rbac/getting-started.md +1 -1
  245. package/docs/rbac/quick-start.md +1 -1
  246. package/docs/standards/README.md +1 -0
  247. package/package.json +2 -1
  248. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  249. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  250. package/scripts/audit/core/checks/bundle.cjs +142 -0
  251. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +714 -687
  252. package/scripts/audit/core/checks/config.cjs +54 -0
  253. package/scripts/audit/core/checks/coverage.cjs +84 -0
  254. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  255. package/scripts/audit/core/checks/documentation.cjs +203 -0
  256. package/scripts/audit/core/checks/environment.cjs +128 -0
  257. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  258. package/scripts/audit/core/checks/forms.cjs +172 -0
  259. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  260. package/scripts/audit/core/checks/hooks.cjs +334 -0
  261. package/scripts/audit/core/checks/imports.cjs +244 -0
  262. package/scripts/audit/core/checks/performance.cjs +325 -0
  263. package/scripts/audit/core/checks/routes.cjs +117 -0
  264. package/scripts/audit/core/checks/state.cjs +130 -0
  265. package/scripts/audit/core/checks/structure.cjs +65 -0
  266. package/scripts/audit/core/checks/style.cjs +584 -0
  267. package/scripts/audit/core/checks/testing.cjs +122 -0
  268. package/scripts/audit/core/checks/typescript.cjs +61 -0
  269. package/scripts/audit/core/scanner.cjs +199 -0
  270. package/scripts/audit/core/utils.cjs +137 -0
  271. package/scripts/audit/index.cjs +223 -0
  272. package/scripts/audit/reporters/console.cjs +151 -0
  273. package/scripts/audit/reporters/json.cjs +54 -0
  274. package/scripts/audit/reporters/markdown.cjs +124 -0
  275. package/scripts/audit-consuming-app.cjs +61 -936
  276. package/scripts/build-docs/build-decision.js +240 -0
  277. package/scripts/build-docs/cache-utils.js +105 -0
  278. package/scripts/build-docs/content-normalization.js +150 -0
  279. package/scripts/build-docs/file-utils.js +105 -0
  280. package/scripts/build-docs/git-utils.js +86 -0
  281. package/scripts/build-docs/hash-utils.js +116 -0
  282. package/scripts/build-docs/typedoc-runner.js +220 -0
  283. package/scripts/build-docs-incremental.js +77 -913
  284. package/scripts/utils/command-runner.js +16 -11
  285. package/scripts/validate-formats.js +61 -56
  286. package/scripts/validate-master.js +74 -69
  287. package/scripts/validate-pre-publish.js +70 -65
  288. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  289. package/src/components/Alert/Alert.test.tsx +12 -18
  290. package/src/components/Alert/Alert.tsx +5 -7
  291. package/src/components/Avatar/Avatar.test.tsx +4 -4
  292. package/src/components/Badge/Badge.tsx +14 -0
  293. package/src/components/Button/Button.tsx +22 -0
  294. package/src/components/Calendar/Calendar.tsx +8 -2
  295. package/src/components/Card/Card.tsx +4 -0
  296. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  297. package/src/components/Checkbox/Checkbox.tsx +2 -2
  298. package/src/components/DataTable/DataTable.tsx +38 -4
  299. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  300. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  301. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  302. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  303. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  304. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  305. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  306. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  307. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  308. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  309. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  310. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  311. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  312. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  313. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  314. package/src/components/DataTable/components/EditFields.tsx +307 -0
  315. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  316. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  317. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  318. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  319. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  320. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  321. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  322. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  323. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  324. package/src/components/DataTable/components/UnifiedTableBody.tsx +61 -849
  325. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  326. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  327. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  328. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  329. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  330. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  331. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  332. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  333. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  334. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  335. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  336. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  337. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  338. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  339. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  340. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  341. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  342. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  343. package/src/components/DataTable/styles.ts +6 -6
  344. package/src/components/DataTable/types.ts +6 -10
  345. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  346. package/src/components/DataTable/utils/debugTools.ts +18 -113
  347. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  348. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  349. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  350. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  351. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  352. package/src/components/Dialog/Dialog.tsx +2 -2
  353. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  354. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  355. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  356. package/src/components/ErrorBoundary/index.ts +27 -2
  357. package/src/components/EventSelector/EventSelector.tsx +3 -0
  358. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  359. package/src/components/FileUpload/FileUpload.tsx +22 -2
  360. package/src/components/Footer/Footer.test.tsx +16 -16
  361. package/src/components/Footer/Footer.tsx +14 -11
  362. package/src/components/Form/Form.tsx +1 -0
  363. package/src/components/Header/Header.tsx +21 -10
  364. package/src/components/Input/Input.test.tsx +2 -2
  365. package/src/components/Input/Input.tsx +8 -4
  366. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  367. package/src/components/LoginForm/LoginForm.tsx +4 -0
  368. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  369. package/src/components/NavigationMenu/types.ts +56 -0
  370. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  371. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  372. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -2
  373. package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
  374. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  376. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  377. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  378. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  379. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  380. package/src/components/Select/Select.tsx +80 -434
  381. package/src/components/Select/context.ts +23 -0
  382. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  383. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  384. package/src/components/Select/hooks/useSelectState.ts +104 -0
  385. package/src/components/Select/index.ts +9 -1
  386. package/src/components/Select/types.ts +123 -0
  387. package/src/components/Select/utils/text.ts +26 -0
  388. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  389. package/src/components/Switch/Switch.tsx +4 -4
  390. package/src/components/Tabs/Tabs.tsx +1 -1
  391. package/src/components/Toast/Toast.tsx +4 -0
  392. package/src/components/Tooltip/Tooltip.tsx +2 -2
  393. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  394. package/src/components/UserMenu/UserMenu.tsx +21 -18
  395. package/src/components/index.ts +2 -2
  396. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  397. package/src/hooks/index.ts +1 -2
  398. package/src/hooks/public/usePublicEvent.ts +4 -0
  399. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  400. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  401. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  402. package/src/hooks/services/useAuth.ts +32 -0
  403. package/src/hooks/services/useCurrentEvent.ts +6 -0
  404. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  405. package/src/hooks/useDebounce.ts +9 -0
  406. package/src/hooks/useEventTheme.ts +6 -0
  407. package/src/hooks/useFileDisplay.ts +4 -0
  408. package/src/hooks/useFileReference.ts +25 -7
  409. package/src/hooks/useFileUrl.ts +11 -1
  410. package/src/hooks/useFocusManagement.ts +14 -0
  411. package/src/hooks/useFocusTrap.ts +3 -0
  412. package/src/hooks/useInactivityTracker.ts +3 -0
  413. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  414. package/src/hooks/useOrganisationPermissions.ts +4 -0
  415. package/src/hooks/useOrganisationSecurity.ts +4 -0
  416. package/src/hooks/usePerformanceMonitor.ts +4 -0
  417. package/src/hooks/usePermissionCache.ts +7 -0
  418. package/src/hooks/useQueryCache.ts +12 -1
  419. package/src/hooks/useSessionRestoration.ts +4 -0
  420. package/src/hooks/useStorage.ts +4 -0
  421. package/src/hooks/useToast.ts +1 -1
  422. package/src/index.ts +2 -1
  423. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  424. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  425. package/src/providers/services/EventServiceProvider.tsx +18 -0
  426. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  427. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  428. package/src/providers/services/UnifiedAuthProvider.tsx +36 -0
  429. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  430. package/src/rbac/README.md +1 -1
  431. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
  432. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  433. package/src/rbac/adapters.tsx +14 -5
  434. package/src/rbac/api.ts +100 -67
  435. package/src/rbac/components/NavigationProvider.tsx +4 -1
  436. package/src/rbac/components/PagePermissionGuard.tsx +157 -17
  437. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  438. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  439. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  440. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  441. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  442. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  443. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  444. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  445. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  446. package/src/rbac/engine.ts +38 -14
  447. package/src/rbac/hooks/permissions/index.ts +7 -0
  448. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  449. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  450. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  451. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  452. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  453. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  454. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  455. package/src/rbac/hooks/useCan.test.ts +71 -64
  456. package/src/rbac/hooks/usePermissions.ts +14 -995
  457. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  458. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  459. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  460. package/src/rbac/permissions.ts +0 -30
  461. package/src/rbac/secureClient.ts +200 -61
  462. package/src/rbac/types.ts +8 -0
  463. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  464. package/src/theming/parseEventColours.ts +5 -19
  465. package/src/types/vitest-globals.d.ts +51 -26
  466. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  467. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  468. package/src/utils/__tests__/index.unit.test.ts +2 -2
  469. package/src/utils/audit/audit.ts +0 -3
  470. package/src/utils/core/cn.ts +1 -1
  471. package/src/utils/file-reference/index.ts +53 -1
  472. package/src/utils/formatting/formatting.ts +8 -18
  473. package/src/utils/index.ts +0 -1
  474. package/dist/chunk-3QRJFVBR.js.map +0 -1
  475. package/dist/chunk-3XTALGJF.js.map +0 -1
  476. package/dist/chunk-4N5C5XZU.js.map +0 -1
  477. package/dist/chunk-4ZC4GX36.js.map +0 -1
  478. package/dist/chunk-BYFSK72L.js.map +0 -1
  479. package/dist/chunk-EXUD6RNJ.js +0 -451
  480. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  481. package/dist/chunk-GLK6VM3F.js.map +0 -1
  482. package/dist/chunk-I7PSE6JW.js.map +0 -1
  483. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  484. package/dist/chunk-KNC55RTG.js.map +0 -1
  485. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  486. package/dist/chunk-R77UEZ4E.js.map +0 -1
  487. package/dist/chunk-SQGMNID3.js.map +0 -1
  488. package/dist/chunk-T33XF5ZC.js.map +0 -1
  489. package/dist/chunk-XM25TVIE.js.map +0 -1
  490. package/docs/api/classes/ErrorBoundary.md +0 -144
  491. package/docs/migration/quick-migration-guide.md +0 -356
  492. package/docs/migration/service-architecture.md +0 -281
  493. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  494. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  495. package/src/hooks/useSecureDataAccess.ts +0 -681
  496. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  497. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  498. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  499. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  500. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  501. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  502. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -0,0 +1,105 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import { getAccessLevel } from '../../api';
4
+ import { OrganisationContextRequiredError } from '../../types';
5
+ import type { AccessLevel as AccessLevelType, Scope, UUID } from '../../types';
6
+ import { useAppConfig } from '../../../hooks/useAppConfig';
7
+
8
+ /**
9
+ * Hook to get user's access level in a scope
10
+ *
11
+ * @param userId - User ID
12
+ * @param scope - Scope for access level checking
13
+ * @returns Access level state and methods
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function MyComponent() {
18
+ * const { accessLevel, isLoading, error } = useAccessLevel(userId, scope);
19
+ *
20
+ * if (isLoading) return <div>Loading access level...</div>;
21
+ * if (error) return <div>Error: {error.message}</div>;
22
+ *
23
+ * return (
24
+ * <div>
25
+ * Access Level: {accessLevel}
26
+ * {accessLevel >= AccessLevel.ADMIN && <AdminPanel />}
27
+ * </div>
28
+ * );
29
+ * }
30
+ * ```
31
+ */
32
+ export function useAccessLevel(userId: UUID, scope: Scope): {
33
+ accessLevel: AccessLevelType;
34
+ isLoading: boolean;
35
+ error: Error | null;
36
+ refetch: () => Promise<void>;
37
+ } {
38
+ const [accessLevel, setAccessLevel] = useState<AccessLevelType>('viewer');
39
+ const [isLoading, setIsLoading] = useState(true);
40
+ const [error, setError] = useState<Error | null>(null);
41
+
42
+ // Get appName from context if available (safely handles missing context)
43
+ let appName: string | undefined;
44
+ try {
45
+ const { appName: contextAppName } = useAppConfig();
46
+ appName = contextAppName;
47
+ } catch {
48
+ // Not available, will use undefined
49
+ }
50
+
51
+ const fetchAccessLevel = useCallback(async () => {
52
+ if (!userId) {
53
+ setAccessLevel('viewer');
54
+ setIsLoading(false);
55
+ return;
56
+ }
57
+
58
+ try {
59
+ setIsLoading(true);
60
+ setError(null);
61
+
62
+ // Check super admin status first - super admins bypass context requirements
63
+ // This allows super admins to check their access level without organisation context
64
+ const { isSuperAdmin: checkSuperAdmin } = await import('../../api');
65
+ const isSuperAdminUser = await checkSuperAdmin(userId);
66
+
67
+ if (isSuperAdminUser) {
68
+ setAccessLevel('super');
69
+ setIsLoading(false);
70
+ return;
71
+ }
72
+
73
+ // Early validation: check if scope has required context
74
+ // PORTAL/ADMIN apps allow both contexts to be optional
75
+ if (appName !== 'PORTAL' && appName !== 'ADMIN' && !scope.organisationId && !scope.eventId) {
76
+ const orgError = new OrganisationContextRequiredError();
77
+ setError(orgError);
78
+ setAccessLevel('viewer');
79
+ setIsLoading(false);
80
+ return;
81
+ }
82
+
83
+ const level = await getAccessLevel({ userId, scope }, null, appName);
84
+ setAccessLevel(level);
85
+ } catch (err) {
86
+ const error = err instanceof Error ? err : new Error('Failed to fetch access level');
87
+ setError(error);
88
+ setAccessLevel('viewer');
89
+ } finally {
90
+ setIsLoading(false);
91
+ }
92
+ }, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
93
+
94
+ useEffect(() => {
95
+ fetchAccessLevel();
96
+ }, [fetchAccessLevel]);
97
+
98
+ // Memoize the return object to prevent unnecessary re-renders
99
+ return useMemo(() => ({
100
+ accessLevel,
101
+ isLoading,
102
+ error,
103
+ refetch: fetchAccessLevel
104
+ }), [accessLevel, isLoading, error, fetchAccessLevel]);
105
+ }
@@ -0,0 +1,79 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import { getPermissionMap } from '../../api';
4
+ import { PermissionMap, Scope, UUID } from '../../types';
5
+
6
+ /**
7
+ * Hook to get cached permissions with TTL management
8
+ *
9
+ * @param userId - User ID
10
+ * @param scope - Scope for permission checking
11
+ * @returns Cached permission state and methods
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * function MyComponent() {
16
+ * const { permissions, isLoading, error, invalidateCache } = useCachedPermissions(userId, scope);
17
+ *
18
+ * if (isLoading) return <div>Loading cached permissions...</div>;
19
+ * if (error) return <div>Error: {error.message}</div>;
20
+ *
21
+ * return (
22
+ * <div>
23
+ * {permissions['read:users'] && <UserList />}
24
+ * <button onClick={invalidateCache}>Refresh Permissions</button>
25
+ * </div>
26
+ * );
27
+ * }
28
+ * ```
29
+ */
30
+ export function useCachedPermissions(userId: UUID, scope: Scope): {
31
+ permissions: PermissionMap;
32
+ isLoading: boolean;
33
+ error: Error | null;
34
+ invalidateCache: () => void;
35
+ refetch: () => Promise<void>;
36
+ } {
37
+ const [permissions, setPermissions] = useState<PermissionMap>({} as PermissionMap);
38
+ const [isLoading, setIsLoading] = useState(true);
39
+ const [error, setError] = useState<Error | null>(null);
40
+
41
+ const fetchCachedPermissions = useCallback(async () => {
42
+ if (!userId) {
43
+ setPermissions({} as PermissionMap);
44
+ setIsLoading(false);
45
+ return;
46
+ }
47
+
48
+ try {
49
+ setIsLoading(true);
50
+ setError(null);
51
+
52
+ const permissionMap = await getPermissionMap({ userId, scope });
53
+ setPermissions(permissionMap);
54
+ } catch (err) {
55
+ setError(err instanceof Error ? err : new Error('Failed to fetch cached permissions'));
56
+ } finally {
57
+ setIsLoading(false);
58
+ }
59
+ }, [userId, scope.organisationId, scope.eventId, scope.appId]);
60
+
61
+ const invalidateCache = useCallback(() => {
62
+ // This would typically invalidate the cache in the actual implementation
63
+ // For now, we'll just refetch
64
+ fetchCachedPermissions();
65
+ }, [fetchCachedPermissions]);
66
+
67
+ useEffect(() => {
68
+ fetchCachedPermissions();
69
+ }, [fetchCachedPermissions]);
70
+
71
+ // Memoize the return object to prevent unnecessary re-renders
72
+ return useMemo(() => ({
73
+ permissions,
74
+ isLoading,
75
+ error,
76
+ invalidateCache,
77
+ refetch: fetchCachedPermissions
78
+ }), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);
79
+ }
@@ -0,0 +1,347 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import { isPermitted, isPermittedCached } from '../../api';
4
+ import { getRBACLogger } from '../../config';
5
+ import { Permission, Scope, UUID } from '../../types';
6
+ import { scopeEqual } from '../../utils/deep-equal';
7
+
8
+ /**
9
+ * Hook to check if user can perform an action
10
+ *
11
+ * @param userId - User ID
12
+ * @param scope - Scope for permission checking
13
+ * @param permission - Permission to check
14
+ * @param pageId - Optional page ID
15
+ * @param useCache - Whether to use cached results
16
+ * @param appName - Optional app name (for PORTAL/ADMIN special case)
17
+ * @returns Permission check state and methods
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * function MyComponent() {
22
+ * const { can, isLoading, error } = useCan(userId, scope, 'read:users');
23
+ *
24
+ * if (isLoading) return <div>Checking permission...</div>;
25
+ * if (error) return <div>Error: {error.message}</div>;
26
+ *
27
+ * return can ? <UserList /> : <div>Access denied</div>;
28
+ * }
29
+ * ```
30
+ */
31
+ export function useCan(
32
+ userId: UUID,
33
+ scope: Scope,
34
+ permission: Permission,
35
+ pageId?: UUID,
36
+ useCache: boolean = true,
37
+ /**
38
+ * Pre-computed super admin flag to avoid duplicate super admin checks.
39
+ * Callers should check super admin once and pass the result to all useCan hooks.
40
+ * Pass null if not checked yet, false/true if checked.
41
+ * Defaults to null (not checked yet) - hook will check if needed.
42
+ */
43
+ precomputedSuperAdmin: boolean | null = null,
44
+ appName?: string,
45
+ ) {
46
+ const [can, setCan] = useState<boolean>(false);
47
+ const [isLoading, setIsLoading] = useState(true);
48
+ const [error, setError] = useState<Error | null>(null);
49
+ const [isSuperAdmin, setIsSuperAdmin] = useState<boolean | null>(precomputedSuperAdmin ?? null);
50
+
51
+ // Validate scope parameter - handle undefined/null scope gracefully
52
+ const isValidScope = scope && typeof scope === 'object';
53
+ const organisationId = isValidScope ? scope.organisationId : undefined;
54
+ const eventId = isValidScope ? scope.eventId : undefined;
55
+ const appId = isValidScope ? scope.appId : undefined;
56
+
57
+ // Check super-admin status - super admins bypass organisation context requirements
58
+ // PERFORMANCE OPTIMIZATION: Use precomputed value directly - no duplicate checks
59
+ // Callers must check super admin once and pass the result (null if not checked yet)
60
+ useEffect(() => {
61
+ // If precomputed value is null, it means not checked yet - check ourselves
62
+ if (precomputedSuperAdmin === null) {
63
+ if (!userId) {
64
+ setIsSuperAdmin(false);
65
+ return;
66
+ }
67
+
68
+ let cancelled = false;
69
+ const checkSuperAdmin = async () => {
70
+ const startTime = Date.now();
71
+ try {
72
+ const { isSuperAdmin: checkSuperAdmin } = await import('../../api');
73
+
74
+ // Add timeout warning
75
+ const timeoutWarning = setTimeout(() => {
76
+ if (!cancelled) {
77
+ console.warn('[useCan] Super admin check taking longer than 5 seconds', {
78
+ userId,
79
+ elapsedMs: Date.now() - startTime,
80
+ });
81
+ }
82
+ }, 5000);
83
+
84
+ const isSuper = await checkSuperAdmin(userId);
85
+ clearTimeout(timeoutWarning);
86
+
87
+ if (!cancelled) {
88
+ const elapsed = Date.now() - startTime;
89
+ if (elapsed > 1000) {
90
+ console.warn('[useCan] Super admin check took longer than expected', {
91
+ userId,
92
+ elapsedMs: elapsed,
93
+ });
94
+ }
95
+ setIsSuperAdmin(isSuper);
96
+ }
97
+ } catch (err) {
98
+ if (!cancelled) {
99
+ const elapsed = Date.now() - startTime;
100
+ console.error('[useCan] Error checking super admin', {
101
+ userId,
102
+ error: err,
103
+ elapsedMs: elapsed,
104
+ });
105
+ setIsSuperAdmin(false);
106
+ }
107
+ }
108
+ };
109
+
110
+ checkSuperAdmin();
111
+ return () => {
112
+ cancelled = true;
113
+ };
114
+ } else {
115
+ // Precomputed value provided (true/false) - use it directly, no check needed
116
+ setIsSuperAdmin(precomputedSuperAdmin);
117
+ }
118
+ }, [userId, precomputedSuperAdmin]);
119
+
120
+ // Add timeout for missing organisation context (3 seconds)
121
+ // Only apply timeout for resource-level permissions, not page-level (which can handle null orgs)
122
+ // Super admins bypass this check
123
+ useEffect(() => {
124
+ const isPagePermission = permission.includes(':page.') || !!pageId;
125
+ const requiresOrgId = !isPagePermission;
126
+
127
+ // Don't block if user is super-admin (they bypass context requirements)
128
+ if (isSuperAdmin === true) {
129
+ return;
130
+ }
131
+
132
+ if (requiresOrgId && (!isValidScope || !organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
133
+ const timeoutId = setTimeout(() => {
134
+ setError(new Error('Organisation context is required for permission checks'));
135
+ setIsLoading(false);
136
+ setCan(false);
137
+ }, 3000); // 3 seconds - typical permission check is < 1 second
138
+
139
+ return () => clearTimeout(timeoutId);
140
+ }
141
+ // Clear error if organisation context becomes available
142
+ if (error?.message === 'Organisation context is required for permission checks') {
143
+ setError(null);
144
+ }
145
+ }, [isValidScope, organisationId, error, permission, pageId, isSuperAdmin]);
146
+
147
+ // Use refs to track the last values to prevent unnecessary re-runs
148
+ const lastUserIdRef = useRef<UUID | null>(null);
149
+ const lastScopeRef = useRef<string | null>(null);
150
+ const lastPermissionRef = useRef<Permission | null>(null);
151
+ const lastPageIdRef = useRef<UUID | undefined | null>(null);
152
+ const lastUseCacheRef = useRef<boolean | null>(null);
153
+
154
+ // Create a stable scope object for comparison
155
+ const stableScope = useMemo(() => {
156
+ if (!isValidScope) {
157
+ return null;
158
+ }
159
+ return {
160
+ organisationId,
161
+ eventId,
162
+ appId,
163
+ };
164
+ }, [isValidScope, organisationId, eventId, appId]);
165
+
166
+ // Track previous scope for deep equality comparison
167
+ const prevScopeRef = useRef<Scope | null>(null);
168
+
169
+ useEffect(() => {
170
+ // Use deep equality check for scope to prevent unnecessary re-runs
171
+ const scopeChanged = !scopeEqual(prevScopeRef.current, stableScope);
172
+
173
+ // Only run if something has actually changed
174
+ if (
175
+ lastUserIdRef.current !== userId ||
176
+ scopeChanged ||
177
+ lastPermissionRef.current !== permission ||
178
+ lastPageIdRef.current !== pageId ||
179
+ lastUseCacheRef.current !== useCache
180
+ ) {
181
+ lastUserIdRef.current = userId;
182
+ prevScopeRef.current = stableScope;
183
+ lastPermissionRef.current = permission;
184
+ lastPageIdRef.current = pageId;
185
+ lastUseCacheRef.current = useCache;
186
+
187
+ // Inline the permission check logic to avoid useCallback dependency issues
188
+ const checkPermission = async () => {
189
+ if (!userId) {
190
+ setCan(false);
191
+ setIsLoading(false);
192
+ return;
193
+ }
194
+
195
+ // CRITICAL: Super admins bypass all permission checks - grant immediately
196
+ // This must be checked BEFORE any other validation to avoid unnecessary API calls
197
+ if (isSuperAdmin === true) {
198
+ setCan(true);
199
+ setIsLoading(false);
200
+ setError(null);
201
+ return;
202
+ }
203
+
204
+ // If super admin status is still being checked (null), wait for it to complete
205
+ // Don't proceed with permission check until we know if user is super admin
206
+ if (isSuperAdmin === null) {
207
+ setIsLoading(true);
208
+ setCan(false);
209
+ setError(null);
210
+ return;
211
+ }
212
+
213
+ // Validate scope before accessing properties
214
+ if (!isValidScope) {
215
+ setIsLoading(true);
216
+ setCan(false);
217
+ setError(null);
218
+ // Timeout is handled in separate useEffect
219
+ return;
220
+ }
221
+
222
+ // For page-level permissions, allow undefined/null organisationId (database function handles it)
223
+ // For resource-level permissions, organisationId is required
224
+ const isPagePermission = permission.includes(':page.') || !!pageId;
225
+ const requiresOrgId = !isPagePermission;
226
+
227
+ // Check if pageId is a pageName (not a UUID) - if so, we need appId to resolve it
228
+ const isPageName = pageId && typeof pageId === 'string' && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pageId);
229
+ const needsAppIdForPageName = isPagePermission && isPageName;
230
+
231
+ // Don't check permissions if scope is invalid and orgId is required
232
+ // Wait for organisation context to resolve (unless it's a page permission that can handle null orgs)
233
+ if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
234
+ // Not super-admin (already checked above) - wait for org context
235
+ setIsLoading(true);
236
+ setCan(false);
237
+ setError(null);
238
+ // Timeout is handled in separate useEffect (Phase 1.4)
239
+ return;
240
+ }
241
+
242
+ // For page-level permissions with pageName (not UUID), we need appId to resolve the pageName to pageId
243
+ // Wait for appId to be available before checking permissions
244
+ if (needsAppIdForPageName && (!appId || appId === null || (typeof appId === 'string' && appId.trim() === ''))) {
245
+ setIsLoading(true);
246
+ setCan(false);
247
+ setError(null);
248
+ // Will re-run when appId becomes available (via scope change detection)
249
+ return;
250
+ }
251
+
252
+ try {
253
+ setIsLoading(true);
254
+ setError(null);
255
+
256
+ // Create a valid scope object for the API call
257
+ // For page-level permissions, organisationId can be undefined (database handles it)
258
+ const validScope: Scope = {
259
+ ...(organisationId ? { organisationId } : {}),
260
+ ...(eventId ? { eventId } : {}),
261
+ ...(appId ? { appId } : {})
262
+ };
263
+
264
+ // Pass super admin status to avoid duplicate check in isPermitted
265
+ // Note: isPermittedCached doesn't support precomputedSuperAdmin, but the check will be cached
266
+ // If we know user is NOT super admin (isSuperAdmin === false), pass false to skip the check
267
+ const result = useCache
268
+ ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
269
+ : await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName, isSuperAdmin === false ? false : null);
270
+
271
+ setCan(result);
272
+ } catch (err) {
273
+ const logger = getRBACLogger();
274
+ logger.error('Permission check error:', { permission, error: err });
275
+ console.error('[useCan] Permission check error', { userId, permission, error: err });
276
+ setError(err instanceof Error ? err : new Error('Failed to check permission'));
277
+ setCan(false);
278
+ } finally {
279
+ setIsLoading(false);
280
+ }
281
+ };
282
+
283
+ checkPermission();
284
+ }
285
+ }, [userId, stableScope, permission, pageId, useCache, appName, isSuperAdmin]);
286
+
287
+ const refetch = useCallback(async () => {
288
+ if (!userId) {
289
+ setCan(false);
290
+ setIsLoading(false);
291
+ return;
292
+ }
293
+
294
+ // Validate scope before accessing properties
295
+ if (!isValidScope) {
296
+ setCan(false);
297
+ setIsLoading(true);
298
+ setError(null);
299
+ return;
300
+ }
301
+
302
+ // For page-level permissions, allow undefined/null organisationId (database function handles it)
303
+ // For resource-level permissions, organisationId is required
304
+ const isPagePermission = permission.includes(':page.') || !!pageId;
305
+ const requiresOrgId = !isPagePermission;
306
+
307
+ // Don't check permissions if scope is invalid and orgId is required
308
+ if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
309
+ setCan(false);
310
+ setIsLoading(true);
311
+ setError(null);
312
+ return;
313
+ }
314
+
315
+ try {
316
+ setIsLoading(true);
317
+ setError(null);
318
+
319
+ // Create a valid scope object for the API call
320
+ // For page-level permissions, organisationId can be undefined (database handles it)
321
+ const validScope: Scope = {
322
+ ...(organisationId ? { organisationId } : {}),
323
+ ...(eventId ? { eventId } : {}),
324
+ ...(appId ? { appId } : {})
325
+ };
326
+
327
+ const result = useCache
328
+ ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
329
+ : await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName, null);
330
+
331
+ setCan(result);
332
+ } catch (err) {
333
+ setError(err instanceof Error ? err : new Error('Failed to check permission'));
334
+ setCan(false);
335
+ } finally {
336
+ setIsLoading(false);
337
+ }
338
+ }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
339
+
340
+ // Memoize the return object to prevent unnecessary re-renders
341
+ return useMemo(() => ({
342
+ can,
343
+ isLoading,
344
+ error,
345
+ refetch
346
+ }), [can, isLoading, error, refetch]);
347
+ }
@@ -0,0 +1,90 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import { isPermitted, isPermittedCached } from '../../api';
4
+ import { Permission, Scope, UUID } from '../../types';
5
+
6
+ /**
7
+ * Hook to check if user has all of the specified permissions
8
+ *
9
+ * @param userId - User ID
10
+ * @param scope - Scope for permission checking
11
+ * @param permissions - Array of permissions to check
12
+ * @param useCache - Whether to use cached results
13
+ * @returns Whether user has all of the permissions
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function MyComponent() {
18
+ * const { hasAll, isLoading, error } = useHasAllPermissions(
19
+ * userId,
20
+ * scope,
21
+ * ['read:users', 'create:users', 'update:users']
22
+ * );
23
+ *
24
+ * if (isLoading) return <div>Checking permissions...</div>;
25
+ * if (error) return <div>Error: {error.message}</div>;
26
+ *
27
+ * return hasAll ? <FullUserManagementPanel /> : <div>Insufficient permissions</div>;
28
+ * }
29
+ * ```
30
+ */
31
+ export function useHasAllPermissions(
32
+ userId: UUID,
33
+ scope: Scope,
34
+ permissions: Permission[],
35
+ useCache: boolean = true
36
+ ): {
37
+ hasAll: boolean;
38
+ isLoading: boolean;
39
+ error: Error | null;
40
+ refetch: () => Promise<void>;
41
+ } {
42
+ const [hasAll, setHasAll] = useState<boolean>(false);
43
+ const [isLoading, setIsLoading] = useState(true);
44
+ const [error, setError] = useState<Error | null>(null);
45
+
46
+ const checkAllPermissions = useCallback(async () => {
47
+ if (!userId || permissions.length === 0) {
48
+ setHasAll(false);
49
+ setIsLoading(false);
50
+ return;
51
+ }
52
+
53
+ try {
54
+ setIsLoading(true);
55
+ setError(null);
56
+
57
+ let hasAllPermissions = true;
58
+
59
+ for (const permission of permissions) {
60
+ const result = useCache
61
+ ? await isPermittedCached({ userId, scope, permission })
62
+ : await isPermitted({ userId, scope, permission }, null, undefined, null);
63
+
64
+ if (!result) {
65
+ hasAllPermissions = false;
66
+ break;
67
+ }
68
+ }
69
+
70
+ setHasAll(hasAllPermissions);
71
+ } catch (err) {
72
+ setError(err instanceof Error ? err : new Error('Failed to check permissions'));
73
+ setHasAll(false);
74
+ } finally {
75
+ setIsLoading(false);
76
+ }
77
+ }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);
78
+
79
+ useEffect(() => {
80
+ checkAllPermissions();
81
+ }, [checkAllPermissions]);
82
+
83
+ // Memoize the return object to prevent unnecessary re-renders
84
+ return useMemo(() => ({
85
+ hasAll,
86
+ isLoading,
87
+ error,
88
+ refetch: checkAllPermissions
89
+ }), [hasAll, isLoading, error, checkAllPermissions]);
90
+ }