@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  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-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.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-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -0,0 +1,390 @@
1
+ import * as React from "react";
2
+ import { useUnifiedAuth } from "../../providers/services/UnifiedAuthProvider";
3
+ import { useRBAC } from "../../rbac/hooks/useRBAC";
4
+ import { useResolvedScope } from "../../rbac/hooks";
5
+ import { usePermissions } from "../../rbac/hooks/usePermissions";
6
+ import type {
7
+ Permission,
8
+ AccessLevel as RBACAccessLevel,
9
+ UUID,
10
+ PermissionMap,
11
+ } from "../../rbac/types";
12
+ import { logger } from "../../utils/core/logger";
13
+ import type { NavigationItem } from "./types";
14
+
15
+ /**
16
+ * Options for the useNavigationFiltering hook.
17
+ */
18
+ interface UseNavigationFilteringOptions {
19
+ items: NavigationItem[];
20
+ itemsPreFiltered?: boolean;
21
+ auditLog?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Return value of the useNavigationFiltering hook.
26
+ */
27
+ interface UseNavigationFilteringResult {
28
+ authContext: ReturnType<typeof useUnifiedAuth> | null;
29
+ rbacContext: ReturnType<typeof useRBAC> | null;
30
+ filteredItems: NavigationItem[];
31
+ permissionMap: PermissionMap;
32
+ hasAnyPermission: ((permissions: Permission[]) => boolean) | null;
33
+ }
34
+
35
+ /**
36
+ * Hook for filtering navigation items based on RBAC permissions.
37
+ * Filters navigation items to show only those the user has access to.
38
+ *
39
+ * @param options - Navigation filtering configuration
40
+ * @returns Filtered navigation items and permission context
41
+ */
42
+ export function useNavigationFiltering({
43
+ items,
44
+ itemsPreFiltered = false,
45
+ auditLog = true,
46
+ }: UseNavigationFilteringOptions): UseNavigationFilteringResult {
47
+ const [resolvedAppId, setResolvedAppId] = React.useState<string | undefined>(undefined);
48
+ const previousFilteredItemsRef = React.useRef<NavigationItem[]>([]);
49
+
50
+ let authContext = null;
51
+ try {
52
+ authContext = useUnifiedAuth();
53
+ } catch (error) {
54
+ logger.warn(
55
+ "NavigationMenu",
56
+ "useUnifiedAuth not available, running in unauthenticated mode",
57
+ );
58
+ }
59
+
60
+ let rbacContext = null;
61
+ try {
62
+ rbacContext = useRBAC();
63
+ } catch (error) {
64
+ logger.warn(
65
+ "NavigationMenu",
66
+ "useRBAC not available, permission filtering disabled",
67
+ );
68
+ }
69
+
70
+ const eventLoadingRaw = authContext?.eventLoading;
71
+ const eventLoading = eventLoadingRaw ?? false;
72
+ const selectedEvent = authContext?.selectedEvent || null;
73
+ const orgContextReady =
74
+ authContext?.isContextReady ?? (authContext?.selectedOrganisation?.id ? true : false);
75
+
76
+ const { supabase } = authContext || {};
77
+ const { selectedOrganisation } = authContext || {};
78
+ const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
79
+ supabase: itemsPreFiltered ? null : supabase || null,
80
+ selectedOrganisationId: itemsPreFiltered ? null : selectedOrganisation?.id || null,
81
+ selectedEventId: itemsPreFiltered ? null : selectedEvent?.event_id || null,
82
+ });
83
+
84
+ React.useEffect(() => {
85
+ if (
86
+ !scopeLoading &&
87
+ !resolvedScope?.appId &&
88
+ selectedOrganisation?.id &&
89
+ authContext?.appName &&
90
+ authContext?.user?.id &&
91
+ !resolvedAppId
92
+ ) {
93
+ if (!authContext.user || !authContext.appName) {
94
+ return;
95
+ }
96
+ const userId = authContext.user.id;
97
+ const appName = authContext.appName;
98
+ import("../../rbac/api")
99
+ .then(({ resolveAppContext }) => {
100
+ resolveAppContext({
101
+ userId,
102
+ appName,
103
+ })
104
+ .then((result) => {
105
+ if (result?.appId) {
106
+ setResolvedAppId(result.appId);
107
+ }
108
+ })
109
+ .catch(() => {});
110
+ })
111
+ .catch(() => {});
112
+ }
113
+ }, [
114
+ scopeLoading,
115
+ resolvedScope?.appId,
116
+ selectedOrganisation?.id,
117
+ authContext?.appName,
118
+ authContext?.user?.id,
119
+ resolvedAppId,
120
+ ]);
121
+
122
+ const effectiveScope = React.useMemo(() => {
123
+ if (resolvedScope?.organisationId) {
124
+ return resolvedScope;
125
+ }
126
+
127
+ if (selectedOrganisation?.id) {
128
+ const fallbackScope = {
129
+ organisationId: selectedOrganisation.id,
130
+ eventId: selectedEvent?.event_id || undefined,
131
+ appId: resolvedAppId,
132
+ };
133
+ return fallbackScope;
134
+ }
135
+
136
+ return null;
137
+ }, [resolvedScope, selectedOrganisation?.id, selectedEvent?.event_id, resolvedAppId]);
138
+
139
+ const scopeKey = effectiveScope
140
+ ? `${effectiveScope.organisationId || ""}-${effectiveScope.eventId || ""}-${
141
+ effectiveScope.appId || ""
142
+ }`
143
+ : "empty";
144
+
145
+ const stableScope = React.useMemo(() => {
146
+ if (effectiveScope?.organisationId) {
147
+ return {
148
+ organisationId: effectiveScope.organisationId,
149
+ eventId: effectiveScope.eventId,
150
+ appId: effectiveScope.appId,
151
+ };
152
+ }
153
+ return {
154
+ organisationId: "",
155
+ eventId: undefined,
156
+ appId: undefined,
157
+ };
158
+ }, [scopeKey, effectiveScope]);
159
+
160
+ const userId = (authContext?.user?.id || "") as UUID;
161
+ const {
162
+ permissions: permissionMap,
163
+ hasAnyPermission,
164
+ isLoading: permissionsLoading,
165
+ error: permissionsError,
166
+ } = usePermissions(
167
+ itemsPreFiltered ? (null as any) : userId,
168
+ itemsPreFiltered ? undefined : stableScope.organisationId,
169
+ itemsPreFiltered ? undefined : stableScope.eventId,
170
+ itemsPreFiltered ? undefined : stableScope.appId,
171
+ );
172
+
173
+ const filteredItems = React.useMemo(() => {
174
+ if (itemsPreFiltered && items && items.length > 0) {
175
+ const visibleItems = (items || []).filter((item) => !item.meta?.hidden);
176
+ previousFilteredItemsRef.current = visibleItems;
177
+ return visibleItems;
178
+ }
179
+
180
+ const isOrgContextReady = orgContextReady && selectedOrganisation?.id;
181
+ const isEventContextReady = eventLoadingRaw === undefined ? true : !eventLoading;
182
+ const hasValidContext = isOrgContextReady && isEventContextReady;
183
+ const shouldWaitForScope = scopeLoading || !hasValidContext;
184
+ const shouldRetryAfterError = scopeError && hasValidContext && !scopeLoading;
185
+
186
+ if (items && items.length > 0 && selectedOrganisation?.id) {
187
+ const visibleItems = (items || []).filter((item) => !item.meta?.hidden);
188
+ previousFilteredItemsRef.current = visibleItems;
189
+ return visibleItems;
190
+ }
191
+
192
+ if (!authContext || !rbacContext || (shouldWaitForScope && !shouldRetryAfterError)) {
193
+ return [];
194
+ }
195
+
196
+ if (permissionsLoading) {
197
+ if (previousFilteredItemsRef.current.length > 0) {
198
+ return previousFilteredItemsRef.current;
199
+ }
200
+ if (items && items.length > 0 && stableScope.organisationId) {
201
+ return (items || []).filter((item) => !item.meta?.hidden);
202
+ }
203
+ return [];
204
+ }
205
+
206
+ if (permissionsError) {
207
+ logger.warn(
208
+ "NavigationMenu",
209
+ "Permission check error - showing no items for security",
210
+ {
211
+ permissionsError: permissionsError?.message,
212
+ },
213
+ );
214
+ return [];
215
+ }
216
+
217
+ if (!permissionMap || Object.keys(permissionMap).length === 0) {
218
+ if (stableScope.organisationId && items && items.length > 0) {
219
+ return (items || []).filter((item) => !item.meta?.hidden);
220
+ }
221
+
222
+ if (stableScope.organisationId) {
223
+ logger.warn("NavigationMenu", "Permission map is empty and no items provided - showing nothing", {
224
+ permissionMapSize: 0,
225
+ organisationId: stableScope.organisationId,
226
+ eventId: stableScope.eventId,
227
+ appId: stableScope.appId,
228
+ });
229
+ }
230
+ return [];
231
+ }
232
+
233
+ const getPageIdFromHref = (href?: string): string | null => {
234
+ if (!href) return null;
235
+ const path = href.split("?")[0].split("#")[0].replace(/^\//, "");
236
+ return path || "home";
237
+ };
238
+
239
+ const hasItemPermission = (item: NavigationItem): boolean => {
240
+ if (item.permissions && item.permissions.length > 0 && !item.href) {
241
+ const permissions = item.permissions
242
+ .filter((p): p is string => typeof p === "string")
243
+ .map((p) => p as Permission);
244
+
245
+ if (permissions.length > 0) {
246
+ const hasPermission = hasAnyPermission?.(permissions);
247
+ if (!hasPermission) {
248
+ return false;
249
+ }
250
+ }
251
+ }
252
+
253
+ if (item.roles && item.roles.length > 0) {
254
+ const hasRole = item.roles.some((role) => {
255
+ if (typeof role !== "string") return true;
256
+
257
+ switch (role.toLowerCase()) {
258
+ case "super_admin":
259
+ case "super admin":
260
+ return rbacContext.isSuperAdmin;
261
+ case "org_admin":
262
+ case "org admin":
263
+ case "admin":
264
+ return rbacContext.isOrgAdmin || rbacContext.isSuperAdmin;
265
+ case "event_admin":
266
+ case "event admin":
267
+ return rbacContext.isEventAdmin || rbacContext.isSuperAdmin;
268
+ default:
269
+ return (
270
+ rbacContext.organisationRole === role ||
271
+ rbacContext.eventAppRole === role ||
272
+ rbacContext.isSuperAdmin
273
+ );
274
+ }
275
+ });
276
+ if (!hasRole) {
277
+ return false;
278
+ }
279
+ }
280
+
281
+ if (item.accessLevel) {
282
+ if (typeof item.accessLevel === "string") {
283
+ const accessLevel = item.accessLevel.toLowerCase() as RBACAccessLevel;
284
+ const userEventRole = rbacContext.eventAppRole;
285
+
286
+ if (!rbacContext.isSuperAdmin) {
287
+ const roleToAccessLevel: Record<string, RBACAccessLevel> = {
288
+ viewer: "viewer",
289
+ participant: "participant",
290
+ planner: "planner",
291
+ event_admin: "admin",
292
+ };
293
+ const userAccessLevel = userEventRole ? roleToAccessLevel[userEventRole] || "viewer" : null;
294
+
295
+ const levelHierarchy: Record<RBACAccessLevel, number> = {
296
+ viewer: 1,
297
+ participant: 2,
298
+ planner: 3,
299
+ admin: 4,
300
+ super: 5,
301
+ };
302
+ const requiredLevel = levelHierarchy[accessLevel] || 0;
303
+ const userLevel = userAccessLevel ? levelHierarchy[userAccessLevel] || 0 : 0;
304
+
305
+ if (userLevel < requiredLevel) {
306
+ return false;
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ if (item.href) {
313
+ const pageId = item.pageId || getPageIdFromHref(item.href);
314
+ if (pageId) {
315
+ const pagePermission: Permission = `read:page.${pageId}` as Permission;
316
+
317
+ const isSuperAdmin = permissionMap["*"] === true;
318
+ const hasPagePermission = permissionMap[pagePermission] === true;
319
+ const finalHasPermission = isSuperAdmin || hasPagePermission;
320
+
321
+ if (!finalHasPermission) {
322
+ return false;
323
+ }
324
+ }
325
+ }
326
+
327
+ return true;
328
+ };
329
+
330
+ const filterItem = (item: NavigationItem): NavigationItem | null => {
331
+ if (item.meta?.hidden) return null;
332
+
333
+ if (!hasItemPermission(item)) return null;
334
+
335
+ let filteredChildren: NavigationItem[] | undefined;
336
+ if (item.children && item.children.length > 0) {
337
+ filteredChildren = item.children
338
+ .map((child) => filterItem(child))
339
+ .filter((child): child is NavigationItem => child !== null);
340
+
341
+ if (filteredChildren.length === 0 && !item.href) {
342
+ return null;
343
+ }
344
+ }
345
+
346
+ return {
347
+ ...item,
348
+ children: filteredChildren,
349
+ };
350
+ };
351
+
352
+ const filtered = (items || [])
353
+ .map((item) => filterItem(item))
354
+ .filter((item): item is NavigationItem => item !== null);
355
+
356
+ previousFilteredItemsRef.current = filtered;
357
+
358
+ return filtered;
359
+ }, [
360
+ items,
361
+ itemsPreFiltered,
362
+ authContext,
363
+ rbacContext,
364
+ permissionMap,
365
+ hasAnyPermission,
366
+ scopeLoading,
367
+ scopeError,
368
+ permissionsLoading,
369
+ resolvedScope,
370
+ effectiveScope,
371
+ auditLog,
372
+ eventLoadingRaw,
373
+ eventLoading,
374
+ selectedEvent,
375
+ orgContextReady,
376
+ selectedOrganisation?.id,
377
+ permissionsError,
378
+ stableScope.organisationId,
379
+ stableScope.eventId,
380
+ stableScope.appId,
381
+ ]);
382
+
383
+ return {
384
+ authContext,
385
+ rbacContext,
386
+ filteredItems,
387
+ permissionMap,
388
+ hasAnyPermission: hasAnyPermission || null,
389
+ };
390
+ }
@@ -162,7 +162,7 @@ vi.mock('../../hooks/services/useEventService', () => ({
162
162
  useEventService: mockUseEventService,
163
163
  }));
164
164
 
165
- // Mock useEvents hook (used by useEventTheme and EventSelector)
165
+ // Mock useEvents hook (used by useEventTheme and ContextSelector)
166
166
  // Use the hoisted mockUseEventService so useEvents can work properly
167
167
  // But also provide a direct mock for components that import useEvents directly
168
168
  const mockUseEvents = vi.hoisted(() => vi.fn(() => ({
@@ -302,20 +302,11 @@ vi.mock('../../rbac/hooks', () => ({
302
302
  })),
303
303
  }));
304
304
 
305
- // Mock EventSelector to avoid useEventService requirement
306
- // Mock both the component file and the index file
307
- vi.mock('../../EventSelector/EventSelector', () => ({
308
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
309
- <div data-testid={testId || 'event-selector'} className={className}>
310
- {placeholder || 'Select event'}
311
- </div>
312
- ))
313
- }));
314
-
315
- vi.mock('../../EventSelector', () => ({
316
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
317
- <div data-testid={testId || 'event-selector'} className={className}>
318
- {placeholder || 'Select event'}
305
+ // Mock ContextSelector to avoid useEventService requirement
306
+ vi.mock('../ContextSelector', () => ({
307
+ ContextSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
308
+ <div data-testid={testId || 'context-selector'} className={className}>
309
+ {placeholder || 'Select organisation or event'}
319
310
  </div>
320
311
  ))
321
312
  }));
@@ -334,7 +325,7 @@ vi.mock('../Header', () => ({
334
325
  userMenu,
335
326
  logo,
336
327
  logoUrl,
337
- showEventSelector,
328
+ showContextSelector,
338
329
  showUserMenu,
339
330
  className
340
331
  }) => (
@@ -407,7 +398,7 @@ vi.mock('../Header', () => ({
407
398
  Change Password
408
399
  </button>
409
400
  <div data-testid="current-path">{currentPath}</div>
410
- <div data-testid="show-event-selector">{showEventSelector !== false ? 'true' : 'false'}</div>
401
+ <div data-testid="show-context-selector">{showContextSelector !== false ? 'true' : 'false'}</div>
411
402
  </header>
412
403
  ))
413
404
  }));
@@ -1054,7 +1045,7 @@ describe('PaceAppLayout Integration', () => {
1054
1045
  headerActions={<HeaderActions />}
1055
1046
  customUserMenu={<CustomUserMenu />}
1056
1047
  customLogo={<CustomLogo />}
1057
- showEventSelector={false}
1048
+ showContextSelector={false}
1058
1049
  showUserMenu={false}
1059
1050
  headerClassName="complex-header-class"
1060
1051
  enforcePermissions={false}
@@ -1073,7 +1064,7 @@ describe('PaceAppLayout Integration', () => {
1073
1064
  // Verify configuration is applied
1074
1065
  expect(screen.getByTestId('app-name')).toHaveTextContent('Complex App');
1075
1066
  expect(screen.getByTestId('nav-items-count')).toHaveTextContent('2');
1076
- expect(screen.getByTestId('show-event-selector')).toHaveTextContent('false');
1067
+ expect(screen.getByTestId('show-context-selector')).toHaveTextContent('false');
1077
1068
  expect(screen.getByTestId('show-user-menu')).toHaveTextContent('false');
1078
1069
  expect(screen.getByTestId('mock-header')).toHaveClass('complex-header-class');
1079
1070
  });
@@ -626,7 +626,7 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
626
626
  <TestWrapper>
627
627
  <PaceAppLayout
628
628
  appName={`Test App ${i}`}
629
- showEventSelector={i % 2 === 0}
629
+ showContextSelector={i % 2 === 0}
630
630
  showUserMenu={i % 2 === 1}
631
631
  />
632
632
  </TestWrapper>
@@ -916,7 +916,7 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
916
916
  navItems={customNavItems}
917
917
  headerActions={<HeaderActions />}
918
918
  customUserMenu={<CustomUserMenu />}
919
- showEventSelector={false}
919
+ showContextSelector={false}
920
920
  showUserMenu={true}
921
921
  headerClassName="complex-header-class"
922
922
  enforcePermissions={false}
@@ -296,11 +296,11 @@ vi.mock('../../rbac/hooks', () => ({
296
296
  })),
297
297
  }));
298
298
 
299
- // Mock EventSelector to avoid useEventService requirement
300
- vi.mock('../EventSelector', () => ({
301
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
302
- <div data-testid={testId || 'event-selector'} className={className}>
303
- {placeholder || 'Select event'}
299
+ // Mock ContextSelector to avoid useEventService requirement
300
+ vi.mock('../ContextSelector', () => ({
301
+ ContextSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
302
+ <div data-testid={testId || 'context-selector'} className={className}>
303
+ {placeholder || 'Select organisation or event'}
304
304
  </div>
305
305
  ))
306
306
  }));
@@ -205,14 +205,14 @@ vi.mock('../Header', () => ({
205
205
  onChangePassword,
206
206
  currentPath,
207
207
  onNavigate,
208
- showEventSelector,
208
+ showContextSelector,
209
209
  showUserMenu,
210
210
  className
211
211
  }: any) => (
212
212
  <header
213
213
  data-testid="header"
214
214
  className={className}
215
- data-show-event-selector={showEventSelector !== false ? 'true' : 'false'}
215
+ data-show-context-selector={showContextSelector !== false ? 'true' : 'false'}
216
216
  data-show-user-menu={showUserMenu !== false ? 'true' : 'false'}
217
217
  >
218
218
  <div data-testid="header-logo" data-logo-url={logoUrl} data-logo-alt={logoAlt}>
@@ -421,29 +421,29 @@ describe('PaceAppLayout Component', () => {
421
421
  { withRouter: false }
422
422
  );
423
423
 
424
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'true');
424
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'true');
425
425
  });
426
426
 
427
- it('hides event selector when showEventSelector is false', () => {
427
+ it('hides context selector when showContextSelector is false', () => {
428
428
  renderWithProviders(
429
429
  <TestWrapper>
430
- <PaceAppLayout {...baseProps} showEventSelector={false} />
430
+ <PaceAppLayout {...baseProps} showContextSelector={false} />
431
431
  </TestWrapper>,
432
432
  { withRouter: false }
433
433
  );
434
434
 
435
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'false');
435
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'false');
436
436
  });
437
437
 
438
- it('shows event selector when showEventSelector is explicitly true', () => {
438
+ it('shows context selector when showContextSelector is explicitly true', () => {
439
439
  renderWithProviders(
440
440
  <TestWrapper>
441
- <PaceAppLayout {...baseProps} showEventSelector={true} />
441
+ <PaceAppLayout {...baseProps} showContextSelector={true} />
442
442
  </TestWrapper>,
443
443
  { withRouter: false }
444
444
  );
445
445
 
446
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'true');
446
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'true');
447
447
  });
448
448
  });
449
449
 
@@ -889,7 +889,7 @@ describe('PaceAppLayout Component', () => {
889
889
  );
890
890
 
891
891
  await waitFor(() => {
892
- // useCan is called with userId, scope, permission, pageId, useCache, appName
892
+ // useCan is called with userId, scope, permission, pageId, useCache, precomputedSuperAdmin, appName
893
893
  expect(mockUseCan).toHaveBeenCalledWith(
894
894
  'user-123',
895
895
  expect.objectContaining({
@@ -900,6 +900,7 @@ describe('PaceAppLayout Component', () => {
900
900
  'update:page.dashboard-page',
901
901
  'dashboard-page',
902
902
  true,
903
+ expect.any(Boolean), // precomputedSuperAdmin - can be false, null, or undefined during checks
903
904
  'Test App'
904
905
  );
905
906
  }, { timeout: 2000 });
@@ -921,7 +922,7 @@ describe('PaceAppLayout Component', () => {
921
922
  );
922
923
 
923
924
  await waitFor(() => {
924
- // useCan is called with userId, scope, permission, pageId, useCache, appName
925
+ // useCan is called with userId, scope, permission, pageId, useCache, precomputedSuperAdmin, appName
925
926
  // Uses defaultPermission "create" since /dashboard is not in routePermissions
926
927
  expect(mockUseCan).toHaveBeenCalledWith(
927
928
  'user-123',
@@ -933,6 +934,7 @@ describe('PaceAppLayout Component', () => {
933
934
  'create:page.dashboard',
934
935
  'dashboard',
935
936
  true,
937
+ expect.any(Boolean), // precomputedSuperAdmin - can be false, null, or undefined during checks
936
938
  'Test App'
937
939
  );
938
940
  }, { timeout: 2000 });