@jmruthers/pace-core 0.5.193 → 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 (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -535,22 +535,46 @@ export class RBACEngine {
535
535
  return cached;
536
536
  }
537
537
 
538
+ const startTime = Date.now();
538
539
  const now = new Date().toISOString();
539
- const { data, error } = await this.supabase
540
- .from('rbac_global_roles')
541
- .select('role')
542
- .eq('user_id', userId)
543
- .eq('role', 'super_admin')
544
- .lte('valid_from', now)
545
- .or(`valid_to.is.null,valid_to.gte.${now}`)
546
- .limit(1) as { data: Array<{ role: string }> | null; error: any };
547
-
548
- const isSuperAdmin = !error && data && data.length > 0;
549
-
550
- // Cache for 60 seconds
551
- rbacCache.set(cacheKey, isSuperAdmin, 60000);
552
540
 
553
- return Boolean(isSuperAdmin);
541
+ try {
542
+ const { data, error } = await this.supabase
543
+ .from('rbac_global_roles')
544
+ .select('role')
545
+ .eq('user_id', userId)
546
+ .eq('role', 'super_admin')
547
+ .lte('valid_from', now)
548
+ .or(`valid_to.is.null,valid_to.gte.${now}`)
549
+ .limit(1) as { data: Array<{ role: string }> | null; error: any };
550
+
551
+ const elapsed = Date.now() - startTime;
552
+
553
+ // Log warning if query takes too long
554
+ if (elapsed > 2000) {
555
+ console.warn('[RBACEngine] Super admin check took longer than expected', {
556
+ userId,
557
+ elapsedMs: elapsed,
558
+ error: error?.message,
559
+ });
560
+ }
561
+
562
+ const isSuperAdmin = !error && data && data.length > 0;
563
+
564
+ // Cache for 60 seconds
565
+ rbacCache.set(cacheKey, isSuperAdmin, 60000);
566
+
567
+ return Boolean(isSuperAdmin);
568
+ } catch (err) {
569
+ const elapsed = Date.now() - startTime;
570
+ console.error('[RBACEngine] Error checking super admin', {
571
+ userId,
572
+ error: err,
573
+ elapsedMs: elapsed,
574
+ });
575
+ // Return false on error (fail secure)
576
+ return false;
577
+ }
554
578
  }
555
579
 
556
580
  /**
@@ -0,0 +1,7 @@
1
+ export { useAccessLevel } from './useAccessLevel';
2
+ export { useCachedPermissions } from './useCachedPermissions';
3
+ export { useCan } from './useCan';
4
+ export { useHasAllPermissions } from './useHasAllPermissions';
5
+ export { useHasAnyPermission } from './useHasAnyPermission';
6
+ export { useMultiplePermissions } from './useMultiplePermissions';
7
+ export { usePermissions } from './usePermissions';
@@ -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
+ }