@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -1,913 +1,218 @@
1
1
  import { renderHook, waitFor } from '@testing-library/react';
2
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
3
3
  import { useRBAC } from '../../rbac/hooks/useRBAC';
4
- import { createMockSupabaseClient, testDataGenerators } from '../../__tests__/helpers/test-utils';
5
4
 
6
- // Mock the providers
7
- const mockUseUnifiedAuth = vi.fn();
8
- const mockUseOrganisations = vi.fn();
9
- const mockUseEvents = vi.fn();
10
-
11
- vi.mock('../../providers', () => ({
12
- useUnifiedAuth: () => mockUseUnifiedAuth()
5
+ vi.mock('../../providers/UnifiedAuthProvider', () => ({
6
+ useUnifiedAuth: vi.fn(),
13
7
  }));
14
8
 
15
9
  vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
16
- useUnifiedAuth: () => mockUseUnifiedAuth()
10
+ useUnifiedAuth: vi.fn(),
17
11
  }));
18
12
 
19
13
  vi.mock('../../hooks/useOrganisations', () => ({
20
- useOrganisations: () => mockUseOrganisations()
14
+ useOrganisations: vi.fn(),
21
15
  }));
22
16
 
23
17
  vi.mock('../../hooks/useEvents', () => ({
24
- useEvents: () => mockUseEvents()
18
+ useEvents: vi.fn(),
25
19
  }));
26
20
 
27
- // Mock Supabase client
28
- const mockSupabase = createMockSupabaseClient();
29
-
30
- // Helper to setup RPC mocks for useRBAC hook
31
- // useRBAC makes TWO RPC calls: util_app_resolve and rbac_permissions_get
32
- const setupRBACMock = (permissions: any[] = []) => {
33
- mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
34
- if (functionName === 'util_app_resolve') {
35
- return Promise.resolve({
36
- data: [{
37
- app_id: 'test-app-id',
38
- app_name: 'test-app',
39
- has_access: true,
40
- is_active: true
41
- }],
42
- error: null
43
- });
44
- }
45
-
46
- if (functionName === 'rbac_permissions_get') {
47
- return Promise.resolve({
48
- data: permissions,
49
- error: null
50
- });
51
- }
52
-
53
- if (functionName === 'rbac_page_access_check') {
54
- return Promise.resolve({ data: true, error: null });
55
- }
56
-
57
- return Promise.resolve({ data: null, error: null });
58
- });
59
- };
60
-
61
- // Setup default RPC mock
62
- setupRBACMock();
21
+ vi.mock('../../rbac/api', () => ({
22
+ getPermissionMap: vi.fn(),
23
+ getAccessLevel: vi.fn(),
24
+ isPermittedCached: vi.fn(),
25
+ resolveAppContext: vi.fn(),
26
+ getRoleContext: vi.fn(),
27
+ }));
63
28
 
64
- describe('useRBAC', () => {
29
+ import { useUnifiedAuth } from '../../providers';
30
+ import { useOrganisations } from '../../hooks/useOrganisations';
31
+ import { useEvents } from '../../hooks/useEvents';
32
+ import {
33
+ getPermissionMap,
34
+ getAccessLevel,
35
+ isPermittedCached,
36
+ resolveAppContext,
37
+ getRoleContext,
38
+ } from '../../rbac/api';
39
+
40
+ const mockUseUnifiedAuth = vi.mocked(useUnifiedAuth);
41
+ const mockUseOrganisations = vi.mocked(useOrganisations);
42
+ const mockUseEvents = vi.mocked(useEvents);
43
+ const mockGetPermissionMap = vi.mocked(getPermissionMap);
44
+ const mockGetAccessLevel = vi.mocked(getAccessLevel);
45
+ const mockIsPermittedCached = vi.mocked(isPermittedCached);
46
+ const mockResolveAppContext = vi.mocked(resolveAppContext);
47
+ const mockGetRoleContext = vi.mocked(getRoleContext);
48
+
49
+ describe('useRBAC (unit)', () => {
65
50
  beforeEach(() => {
66
51
  vi.clearAllMocks();
67
-
68
- // Default mock implementations
52
+
69
53
  mockUseUnifiedAuth.mockReturnValue({
70
- user: { id: 'test-user-id' },
71
- session: { access_token: 'test-token' },
72
- supabase: mockSupabase,
73
- appName: 'test-app'
74
- });
75
-
76
- mockUseOrganisations.mockReturnValue({
77
- selectedOrganisation: { id: 'test-org-id' }
78
- });
79
-
80
- mockUseEvents.mockReturnValue({
81
- selectedEvent: { event_id: 'test-event-id' }
82
- });
54
+ user: null,
55
+ session: null,
56
+ appName: 'test-app',
57
+ });
58
+ mockUseOrganisations.mockReturnValue({ selectedOrganisation: null });
59
+ mockUseEvents.mockReturnValue({ selectedEvent: null });
60
+
61
+ mockResolveAppContext.mockResolvedValue({ appId: 'app-1' as any, hasAccess: true });
62
+ mockGetPermissionMap.mockResolvedValue({});
63
+ mockGetAccessLevel.mockResolvedValue('viewer');
64
+ mockGetRoleContext.mockResolvedValue({
65
+ globalRole: null,
66
+ organisationRole: null,
67
+ eventAppRole: null,
68
+ });
69
+ mockIsPermittedCached.mockResolvedValue(false);
83
70
  });
84
71
 
85
- describe('Initial State', () => {
86
- it('returns initial state when no user is provided', () => {
87
- mockUseUnifiedAuth.mockReturnValue({
88
- user: null,
89
- supabase: null,
90
- appName: null
91
- });
92
-
93
- const { result } = renderHook(() => useRBAC());
94
-
95
- expect(result.current.globalRole).toBeNull();
96
- expect(result.current.organisationRole).toBeNull();
97
- expect(result.current.eventAppRole).toBeNull();
98
- expect(result.current.isLoading).toBe(false);
99
- expect(result.current.error).toBeNull();
100
- });
101
-
102
- it('returns initial state when no supabase client is provided', () => {
103
- mockUseUnifiedAuth.mockReturnValue({
104
- user: { id: 'test-user-id' },
105
- supabase: null,
106
- appName: 'test-app'
107
- });
72
+ it('returns null roles when user is not authenticated', () => {
73
+ const { result } = renderHook(() => useRBAC());
108
74
 
109
- const { result } = renderHook(() => useRBAC());
110
-
111
- expect(result.current.globalRole).toBeNull();
112
- expect(result.current.organisationRole).toBeNull();
113
- expect(result.current.eventAppRole).toBeNull();
114
- expect(result.current.isLoading).toBe(false);
115
- expect(result.current.error).toBeNull();
116
- });
117
-
118
- it('returns initial state when no app name is provided', () => {
119
- mockUseUnifiedAuth.mockReturnValue({
120
- user: { id: 'test-user-id' },
121
- supabase: mockSupabase,
122
- appName: null
123
- });
124
-
125
- const { result } = renderHook(() => useRBAC());
126
-
127
- expect(result.current.globalRole).toBeNull();
128
- expect(result.current.organisationRole).toBeNull();
129
- expect(result.current.eventAppRole).toBeNull();
130
- expect(result.current.isLoading).toBe(false);
131
- expect(result.current.error).toBeNull();
132
- });
75
+ expect(result.current.user).toBeNull();
76
+ expect(result.current.globalRole).toBeNull();
77
+ expect(result.current.organisationRole).toBeNull();
78
+ expect(result.current.eventAppRole).toBeNull();
79
+ expect(result.current.isLoading).toBe(false);
133
80
  });
134
81
 
135
- describe('Role Detection', () => {
136
- it('detects global role from permissions', async () => {
137
- const mockPermissions = [
138
- {
139
- permission_type: 'all_permissions',
140
- role_name: 'super_admin',
141
- has_permission: true,
142
- granted_at: '2023-01-01T00:00:00Z'
143
- }
144
- ];
145
-
146
- setupRBACMock(mockPermissions);
147
-
148
- const { result } = renderHook(() => useRBAC());
149
-
150
- await waitFor(() => {
151
- expect(result.current.globalRole).toBe('super_admin');
152
- expect(result.current.organisationRole).toBeNull();
153
- expect(result.current.eventAppRole).toBeNull();
154
- }, { interval: 10, timeout: 3000 });
155
- });
156
-
157
- it('detects organisation role from permissions', async () => {
158
- const mockPermissions = [
159
- {
160
- permission_type: 'organisation_access',
161
- role_name: 'org_admin',
162
- has_permission: true,
163
- granted_at: '2023-01-01T00:00:00Z'
164
- }
165
- ];
166
-
167
- setupRBACMock(mockPermissions);
168
-
169
- const { result } = renderHook(() => useRBAC());
170
-
171
- await waitFor(() => {
172
- expect(result.current.globalRole).toBeNull();
173
- expect(result.current.organisationRole).toBe('org_admin');
174
- expect(result.current.eventAppRole).toBeNull();
175
- }, { interval: 10, timeout: 3000 });
176
- });
177
-
178
- it('detects event app role from permissions', async () => {
179
- const mockPermissions = [
180
- {
181
- permission_type: 'event_app_access',
182
- role_name: 'event_admin',
183
- has_permission: true,
184
- granted_at: '2023-01-01T00:00:00Z'
185
- }
186
- ];
187
-
188
-
189
- setupRBACMock(mockPermissions);
190
-
191
- const { result } = renderHook(() => useRBAC());
192
-
193
- await waitFor(() => {
194
- expect(result.current.globalRole).toBeNull();
195
- expect(result.current.organisationRole).toBeNull();
196
- expect(result.current.eventAppRole).toBe('event_admin');
197
- }, { interval: 10, timeout: 3000 });
82
+ it('loads role context and permissions when authenticated', async () => {
83
+ mockUseUnifiedAuth.mockReturnValue({
84
+ user: { id: 'user-1' },
85
+ session: { access_token: 'token' },
86
+ appName: 'console',
198
87
  });
88
+ mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
89
+ mockUseEvents.mockReturnValue({ selectedEvent: { event_id: 'event-1' } });
199
90
 
200
- it('detects multiple roles from permissions', async () => {
201
- const mockPermissions = [
202
- {
203
- permission_type: 'all_permissions',
204
- role_name: 'super_admin',
205
- has_permission: true,
206
- granted_at: '2023-01-01T00:00:00Z'
207
- },
208
- {
209
- permission_type: 'organisation_access',
210
- role_name: 'org_admin',
211
- has_permission: true,
212
- granted_at: '2023-01-01T00:00:00Z'
213
- },
214
- {
215
- permission_type: 'event_app_access',
216
- role_name: 'event_admin',
217
- has_permission: true,
218
- granted_at: '2023-01-01T00:00:00Z'
219
- }
220
- ];
221
-
222
-
223
- setupRBACMock(mockPermissions);
224
-
225
- const { result } = renderHook(() => useRBAC());
226
-
227
- await waitFor(() => {
228
- expect(result.current.globalRole).toBe('super_admin');
229
- expect(result.current.organisationRole).toBe('org_admin');
230
- expect(result.current.eventAppRole).toBe('event_admin');
231
- }, { interval: 10, timeout: 3000 });
91
+ mockGetPermissionMap.mockResolvedValue({ 'read:dashboard': true });
92
+ mockGetRoleContext.mockResolvedValue({
93
+ globalRole: 'super_admin',
94
+ organisationRole: 'org_admin',
95
+ eventAppRole: null,
232
96
  });
233
97
 
234
- it('handles empty permissions array', async () => {
235
- mockSupabase.rpc.mockResolvedValue({
236
- data: [],
237
- error: null
238
- });
98
+ const { result } = renderHook(() => useRBAC('dashboard'));
239
99
 
240
- const { result } = renderHook(() => useRBAC());
100
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
241
101
 
242
- await waitFor(() => {
243
- expect(result.current.globalRole).toBeNull();
244
- expect(result.current.organisationRole).toBeNull();
245
- expect(result.current.eventAppRole).toBeNull();
246
- });
247
- });
102
+ expect(result.current.globalRole).toBe('super_admin');
103
+ expect(result.current.organisationRole).toBe('org_admin');
104
+ expect(result.current.isSuperAdmin).toBe(true);
105
+ expect(mockGetPermissionMap).toHaveBeenCalled();
248
106
  });
249
107
 
250
- describe('Permission Checking', () => {
251
- it('returns true for super admin regardless of operation', async () => {
252
- const mockPermissions = [
253
- {
254
- permission_type: 'all_permissions',
255
- role_name: 'super_admin',
256
- has_permission: true,
257
- granted_at: '2023-01-01T00:00:00Z'
258
- }
259
- ];
260
-
261
-
262
- setupRBACMock(mockPermissions);
263
-
264
- const { result } = renderHook(() => useRBAC());
265
-
266
- await waitFor(() => {
267
- expect(result.current.globalRole).toBe('super_admin');
268
- }, { interval: 10, timeout: 3000 });
269
-
270
- const hasPermission = await result.current.hasPermission('read', 'dashboard');
271
- expect(hasPermission).toBe(true);
108
+ it('maps access level to event role when engine omits event role', async () => {
109
+ mockUseUnifiedAuth.mockReturnValue({
110
+ user: { id: 'user-1' },
111
+ session: { access_token: 'token' },
112
+ appName: 'console',
272
113
  });
273
-
274
- it('checks specific permission with database call', async () => {
275
- const mockPermissions = [
276
- {
277
- permission_type: 'organisation_access',
278
- role_name: 'org_admin',
279
- has_permission: true,
280
- granted_at: '2023-01-01T00:00:00Z'
281
- }
282
- ];
283
-
284
- // Fully mock RPC responses for this test
285
- mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
286
- if (functionName === 'util_app_resolve') {
287
- return Promise.resolve({
288
- data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
289
- error: null
290
- });
291
- }
292
- if (functionName === 'rbac_permissions_get') {
293
- return Promise.resolve({ data: mockPermissions, error: null });
294
- }
295
- if (functionName === 'rbac_page_access_check') {
296
- return Promise.resolve({ data: true, error: null });
297
- }
298
- return Promise.resolve({ data: null, error: null });
299
- });
300
-
301
- const { result } = renderHook(() => useRBAC());
302
-
303
- await waitFor(() => {
304
- expect(result.current.organisationRole).toBe('org_admin');
305
- }, { interval: 10, timeout: 3000 });
306
-
307
- const hasPermission = await result.current.hasPermission('read', 'dashboard');
308
- expect(hasPermission).toBe(true);
309
- expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_page_access_check', {
310
- p_user_id: 'test-user-id',
311
- p_app_id: 'test-app-id',
312
- p_page_id: 'dashboard',
313
- p_operation: 'read',
314
- p_event_id: 'test-event-id',
315
- p_organisation_id: 'test-org-id'
316
- });
114
+ mockGetRoleContext.mockResolvedValue({
115
+ globalRole: null,
116
+ organisationRole: null,
117
+ eventAppRole: null,
317
118
  });
119
+ mockGetAccessLevel.mockResolvedValue('planner');
318
120
 
319
- it('returns false when permission check fails', async () => {
320
- const mockPermissions = [
321
- {
322
- permission_type: 'organisation_access',
323
- role_name: 'org_admin',
324
- has_permission: true,
325
- granted_at: '2023-01-01T00:00:00Z'
326
- }
327
- ];
328
-
329
- // Fully mock RPC responses for this test
330
- mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
331
- if (functionName === 'util_app_resolve') {
332
- return Promise.resolve({
333
- data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
334
- error: null
335
- });
336
- }
337
- if (functionName === 'rbac_permissions_get') {
338
- return Promise.resolve({ data: mockPermissions, error: null });
339
- }
340
- if (functionName === 'rbac_page_access_check') {
341
- return Promise.resolve({ data: false, error: null });
342
- }
343
- return Promise.resolve({ data: null, error: null });
344
- });
345
-
346
- const { result } = renderHook(() => useRBAC());
347
-
348
- await waitFor(() => {
349
- expect(result.current.organisationRole).toBe('org_admin');
350
- }, { interval: 10, timeout: 3000 });
351
-
352
- const hasPermission = await result.current.hasPermission('write', 'admin');
353
- expect(hasPermission).toBe(false);
354
- });
121
+ const { result } = renderHook(() => useRBAC());
355
122
 
356
- it('returns false when permission check throws error', async () => {
357
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
358
-
359
- const mockPermissions = [
360
- {
361
- permission_type: 'organisation_access',
362
- role_name: 'org_admin',
363
- has_permission: true,
364
- granted_at: '2023-01-01T00:00:00Z'
365
- }
366
- ];
367
-
368
- // Fully mock RPC responses (reject on access check)
369
- mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
370
- if (functionName === 'util_app_resolve') {
371
- return Promise.resolve({
372
- data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
373
- error: null
374
- });
375
- }
376
- if (functionName === 'rbac_permissions_get') {
377
- return Promise.resolve({ data: mockPermissions, error: null });
378
- }
379
- if (functionName === 'rbac_page_access_check') {
380
- return Promise.reject(new Error('Database error'));
381
- }
382
- return Promise.resolve({ data: null, error: null });
383
- });
384
-
385
- const { result } = renderHook(() => useRBAC());
386
-
387
- await waitFor(() => {
388
- expect(result.current.organisationRole).toBe('org_admin');
389
- }, { interval: 10, timeout: 3000 });
390
-
391
- const hasPermission = await result.current.hasPermission('read', 'dashboard');
392
- expect(hasPermission).toBe(false);
393
-
394
- consoleSpy.mockRestore();
395
- });
123
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
396
124
 
397
- it('uses default pageId when not provided', async () => {
398
- const mockPermissions = [
399
- {
400
- permission_type: 'organisation_access',
401
- role_name: 'org_admin',
402
- has_permission: true,
403
- granted_at: '2023-01-01T00:00:00Z'
404
- }
405
- ];
406
-
407
- // Fully mock RPC responses
408
- mockSupabase.rpc.mockImplementation((functionName: string, params?: any) => {
409
- if (functionName === 'util_app_resolve') {
410
- return Promise.resolve({
411
- data: [{ app_id: 'test-app-id', app_name: 'test-app', has_access: true, is_active: true }],
412
- error: null
413
- });
414
- }
415
- if (functionName === 'rbac_permissions_get') {
416
- return Promise.resolve({ data: mockPermissions, error: null });
417
- }
418
- if (functionName === 'rbac_page_access_check') {
419
- return Promise.resolve({ data: true, error: null });
420
- }
421
- return Promise.resolve({ data: null, error: null });
422
- });
423
-
424
- const { result } = renderHook(() => useRBAC('default-page'));
425
-
426
- await waitFor(() => {
427
- expect(result.current.organisationRole).toBe('org_admin');
428
- }, { interval: 10, timeout: 3000 });
429
-
430
- await result.current.hasPermission('read');
431
-
432
- expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_page_access_check', {
433
- p_user_id: 'test-user-id',
434
- p_app_id: 'test-app-id',
435
- p_page_id: 'default-page',
436
- p_operation: 'read',
437
- p_event_id: 'test-event-id',
438
- p_organisation_id: 'test-org-id'
439
- });
440
- });
125
+ expect(result.current.eventAppRole).toBe('planner');
441
126
  });
442
127
 
443
- describe('Global Permission Checking', () => {
444
- it('returns true for super admin', async () => {
445
- const mockPermissions = [
446
- {
447
- permission_type: 'all_permissions',
448
- role_name: 'super_admin',
449
- has_permission: true,
450
- granted_at: '2023-01-01T00:00:00Z'
451
- }
452
- ];
453
-
454
-
455
- setupRBACMock(mockPermissions);
456
-
457
- const { result } = renderHook(() => useRBAC());
458
-
459
- await waitFor(() => {
460
- expect(result.current.isLoading).toBe(false);
461
- });
462
-
463
- expect(result.current.hasGlobalPermission('super_admin')).toBe(true);
464
- expect(result.current.hasGlobalPermission('org_admin')).toBe(true);
465
- expect(result.current.hasGlobalPermission('any_permission')).toBe(true);
466
- });
467
-
468
- it('returns true for org admin when checking org_admin permission', async () => {
469
- const mockPermissions = [
470
- {
471
- permission_type: 'organisation_access',
472
- role_name: 'org_admin',
473
- has_permission: true,
474
- granted_at: '2023-01-01T00:00:00Z'
475
- }
476
- ];
477
-
478
-
479
- setupRBACMock(mockPermissions);
480
-
481
- const { result } = renderHook(() => useRBAC());
482
-
483
- await waitFor(() => {
484
- expect(result.current.isLoading).toBe(false);
485
- });
486
-
487
- expect(result.current.hasGlobalPermission('org_admin')).toBe(true);
488
- expect(result.current.hasGlobalPermission('super_admin')).toBe(false);
489
- expect(result.current.hasGlobalPermission('other_permission')).toBe(false);
490
- });
491
-
492
- it('returns false for non-admin users', () => {
493
- const mockPermissions = [
494
- {
495
- permission_type: 'event_app_access',
496
- role_name: 'viewer',
497
- has_permission: true,
498
- granted_at: '2023-01-01T00:00:00Z'
499
- }
500
- ];
501
-
502
-
503
- setupRBACMock(mockPermissions);
504
-
505
- const { result } = renderHook(() => useRBAC());
506
-
507
- expect(result.current.hasGlobalPermission('super_admin')).toBe(false);
508
- expect(result.current.hasGlobalPermission('org_admin')).toBe(false);
509
- expect(result.current.hasGlobalPermission('any_permission')).toBe(false);
510
- });
511
- });
512
-
513
- describe('Computed Properties', () => {
514
- it('correctly computes isSuperAdmin', async () => {
515
- const mockPermissions = [
516
- {
517
- permission_type: 'all_permissions',
518
- role_name: 'super_admin',
519
- has_permission: true,
520
- granted_at: '2023-01-01T00:00:00Z'
521
- }
522
- ];
523
-
524
-
525
- setupRBACMock(mockPermissions);
526
-
527
- const { result } = renderHook(() => useRBAC());
528
-
529
- await waitFor(() => {
530
- expect(result.current.isSuperAdmin).toBe(true);
531
- });
532
- });
533
-
534
- it('correctly computes isOrgAdmin', async () => {
535
- const mockPermissions = [
536
- {
537
- permission_type: 'organisation_access',
538
- role_name: 'org_admin',
539
- has_permission: true,
540
- granted_at: '2023-01-01T00:00:00Z'
541
- }
542
- ];
543
-
544
-
545
- setupRBACMock(mockPermissions);
546
-
547
- const { result } = renderHook(() => useRBAC());
548
-
549
- await waitFor(() => {
550
- expect(result.current.isOrgAdmin).toBe(true);
551
- });
552
- });
553
-
554
- it('correctly computes isEventAdmin', async () => {
555
- const mockPermissions = [
556
- {
557
- permission_type: 'event_app_access',
558
- role_name: 'event_admin',
559
- has_permission: true,
560
- granted_at: '2023-01-01T00:00:00Z'
561
- }
562
- ];
563
-
564
-
565
- setupRBACMock(mockPermissions);
566
-
567
- const { result } = renderHook(() => useRBAC());
568
-
569
- await waitFor(() => {
570
- expect(result.current.isEventAdmin).toBe(true);
571
- });
128
+ it('treats wildcard permission map as super admin', async () => {
129
+ mockUseUnifiedAuth.mockReturnValue({
130
+ user: { id: 'user-1' },
131
+ session: { access_token: 'token' },
132
+ appName: 'console',
572
133
  });
134
+ mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
135
+ mockGetPermissionMap.mockResolvedValue({ '*': true });
573
136
 
574
- it('correctly computes canManageOrganisation', async () => {
575
- const mockPermissions = [
576
- {
577
- permission_type: 'organisation_access',
578
- role_name: 'org_admin',
579
- has_permission: true,
580
- granted_at: '2023-01-01T00:00:00Z'
581
- }
582
- ];
583
-
584
-
585
- setupRBACMock(mockPermissions);
586
-
587
- const { result } = renderHook(() => useRBAC());
588
-
589
- await waitFor(() => {
590
- expect(result.current.canManageOrganisation).toBe(true);
591
- });
592
- });
137
+ const { result } = renderHook(() => useRBAC());
593
138
 
594
- it('correctly computes canManageEvent', async () => {
595
- const mockPermissions = [
596
- {
597
- permission_type: 'event_app_access',
598
- role_name: 'event_admin',
599
- has_permission: true,
600
- granted_at: '2023-01-01T00:00:00Z'
601
- }
602
- ];
603
-
604
-
605
- setupRBACMock(mockPermissions);
606
-
607
- const { result } = renderHook(() => useRBAC());
608
-
609
- await waitFor(() => {
610
- expect(result.current.canManageEvent).toBe(true);
611
- });
612
- });
139
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
613
140
 
614
- it('super admin can manage both organisation and event', async () => {
615
- const mockPermissions = [
616
- {
617
- permission_type: 'all_permissions',
618
- role_name: 'super_admin',
619
- has_permission: true,
620
- granted_at: '2023-01-01T00:00:00Z'
621
- }
622
- ];
623
-
624
-
625
- setupRBACMock(mockPermissions);
626
-
627
- const { result } = renderHook(() => useRBAC());
628
-
629
- await waitFor(() => {
630
- expect(result.current.canManageOrganisation).toBe(true);
631
- expect(result.current.canManageEvent).toBe(true);
632
- });
633
- });
141
+ expect(result.current.isSuperAdmin).toBe(true);
142
+ expect(await result.current.hasPermission('delete', 'users')).toBe(true);
634
143
  });
635
144
 
636
- describe('Loading States', () => {
637
- it('sets loading state while fetching permissions', async () => {
638
- let resolvePermissions: (value: any) => void;
639
- const permissionsPromise = new Promise(resolve => {
640
- resolvePermissions = resolve;
641
- });
642
-
643
- mockSupabase.rpc.mockReturnValue(permissionsPromise);
644
-
645
- const { result } = renderHook(() => useRBAC());
646
-
647
- expect(result.current.isLoading).toBe(true);
648
-
649
- resolvePermissions!({
650
- data: [],
651
- error: null
652
- });
653
-
654
- await waitFor(() => {
655
- expect(result.current.isLoading).toBe(false);
656
- });
145
+ it('uses cached permission map when checking permissions', async () => {
146
+ mockUseUnifiedAuth.mockReturnValue({
147
+ user: { id: 'user-1' },
148
+ session: { access_token: 'token' },
149
+ appName: 'console',
657
150
  });
151
+ mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
152
+ mockGetPermissionMap.mockResolvedValue({ 'read:users': true, 'write:users': false });
658
153
 
659
- it('resets loading state on error', async () => {
660
- mockSupabase.rpc.mockRejectedValue(new Error('Network error'));
154
+ const { result } = renderHook(() => useRBAC());
661
155
 
662
- const { result } = renderHook(() => useRBAC());
156
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
663
157
 
664
- await waitFor(() => {
665
- expect(result.current.isLoading).toBe(false);
666
- expect(result.current.error).toBeInstanceOf(Error);
667
- });
668
- });
158
+ mockIsPermittedCached.mockClear();
159
+ expect(await result.current.hasPermission('read:users')).toBe(true);
160
+ expect(await result.current.hasPermission('write:users')).toBe(false);
161
+ expect(mockIsPermittedCached).not.toHaveBeenCalled();
669
162
  });
670
163
 
671
- describe('Error Handling', () => {
672
- it('handles RPC errors gracefully', async () => {
673
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
674
-
675
- // Mock the app resolve to succeed, but permissions to fail
676
- mockSupabase.rpc.mockImplementation((functionName: string) => {
677
- if (functionName === 'util_app_resolve') {
678
- return Promise.resolve({
679
- data: [{
680
- app_id: 'test-app-id',
681
- app_name: 'test-app',
682
- has_access: true,
683
- is_active: true
684
- }],
685
- error: null
686
- });
687
- }
688
- if (functionName === 'rbac_permissions_get') {
689
- return Promise.resolve({
690
- data: null,
691
- error: { message: 'Database connection failed' }
692
- });
693
- }
694
- return Promise.resolve({ data: null, error: null });
695
- });
696
-
697
- const { result } = renderHook(() => useRBAC());
698
-
699
- await waitFor(() => {
700
- expect(result.current.error).toBeInstanceOf(Error);
701
- expect(result.current.error?.message).toContain('Failed to load RBAC permissions');
702
- });
703
-
704
- consoleSpy.mockRestore();
705
- });
706
-
707
- it('handles network errors gracefully', async () => {
708
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
709
-
710
- // Mock the app resolve to succeed, but permissions to fail
711
- mockSupabase.rpc.mockImplementation((functionName: string) => {
712
- if (functionName === 'util_app_resolve') {
713
- return Promise.resolve({
714
- data: [{
715
- app_id: 'test-app-id',
716
- app_name: 'test-app',
717
- has_access: true,
718
- is_active: true
719
- }],
720
- error: null
721
- });
722
- }
723
- if (functionName === 'rbac_permissions_get') {
724
- return Promise.resolve({
725
- data: null,
726
- error: { message: 'Network timeout' }
727
- });
728
- }
729
- return Promise.resolve({ data: null, error: null });
730
- });
731
-
732
- const { result } = renderHook(() => useRBAC());
733
-
734
- await waitFor(() => {
735
- expect(result.current.error).toBeInstanceOf(Error);
736
- expect(result.current.error?.message).toContain('Failed to load RBAC permissions');
737
- });
738
-
739
- consoleSpy.mockRestore();
164
+ it('falls back to engine when permission not cached', async () => {
165
+ mockUseUnifiedAuth.mockReturnValue({
166
+ user: { id: 'user-1' },
167
+ session: { access_token: 'token' },
168
+ appName: 'console',
740
169
  });
170
+ mockUseOrganisations.mockReturnValue({ selectedOrganisation: { id: 'org-1' } });
171
+ mockGetPermissionMap.mockResolvedValue({});
172
+ mockIsPermittedCached.mockResolvedValue(true);
741
173
 
742
- it('handles unknown errors gracefully', async () => {
743
- const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
744
-
745
- mockSupabase.rpc.mockRejectedValue('Unknown error');
746
-
747
- const { result } = renderHook(() => useRBAC());
174
+ const { result } = renderHook(() => useRBAC());
748
175
 
749
- await waitFor(() => {
750
- expect(result.current.error).toBeInstanceOf(Error);
751
- expect(result.current.error?.message).toBe('Unknown error loading RBAC context');
752
- });
176
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
753
177
 
754
- consoleSpy.mockRestore();
755
- });
178
+ expect(await result.current.hasPermission('read', 'users')).toBe(true);
179
+ expect(mockIsPermittedCached).toHaveBeenCalled();
756
180
  });
757
181
 
758
- describe('Context Dependencies', () => {
759
- it('reloads when user changes', async () => {
760
- const mockPermissions = [
761
- {
762
- permission_type: 'all_permissions',
763
- role_name: 'super_admin',
764
- has_permission: true,
765
- granted_at: '2023-01-01T00:00:00Z'
766
- }
767
- ];
768
-
769
- setupRBACMock(mockPermissions);
770
-
771
- const { result, rerender } = renderHook(() => useRBAC());
772
-
773
- await waitFor(() => {
774
- expect(result.current.globalRole).toBe('super_admin');
775
- });
776
-
777
- // Change user
778
- mockUseUnifiedAuth.mockReturnValue({
779
- user: { id: 'different-user-id' },
780
- session: { access_token: 'test-token' },
781
- supabase: mockSupabase,
782
- appName: 'test-app'
783
- });
784
-
785
- rerender();
786
-
787
- await waitFor(() => {
788
- expect(mockSupabase.rpc).toHaveBeenCalledTimes(4);
789
- });
182
+ it('returns true for hasGlobalPermission when organisation admin', async () => {
183
+ mockUseUnifiedAuth.mockReturnValue({
184
+ user: { id: 'user-1' },
185
+ session: { access_token: 'token' },
186
+ appName: 'console',
790
187
  });
791
-
792
- it('reloads when organisation changes', async () => {
793
- const mockPermissions = [
794
- {
795
- permission_type: 'organisation_access',
796
- role_name: 'org_admin',
797
- has_permission: true,
798
- granted_at: '2023-01-01T00:00:00Z'
799
- }
800
- ];
801
-
802
- setupRBACMock(mockPermissions);
803
-
804
- const { result, rerender } = renderHook(() => useRBAC());
805
-
806
- await waitFor(() => {
807
- expect(result.current.organisationRole).toBe('org_admin');
808
- }, { interval: 10, timeout: 3000 });
809
-
810
- // Change organisation
811
- mockUseOrganisations.mockReturnValue({
812
- selectedOrganisation: { id: 'different-org-id' }
813
- });
814
-
815
- rerender();
816
-
817
- await waitFor(() => {
818
- expect(mockSupabase.rpc).toHaveBeenCalledTimes(4);
819
- });
188
+ mockGetRoleContext.mockResolvedValue({
189
+ globalRole: null,
190
+ organisationRole: 'org_admin',
191
+ eventAppRole: null,
820
192
  });
821
193
 
822
- it('reloads when event changes', async () => {
823
- const mockPermissions = [
824
- {
825
- permission_type: 'event_app_access',
826
- role_name: 'event_admin',
827
- has_permission: true,
828
- granted_at: '2023-01-01T00:00:00Z'
829
- }
830
- ];
831
-
832
- setupRBACMock(mockPermissions);
833
-
834
- const { result, rerender } = renderHook(() => useRBAC());
835
-
836
- await waitFor(() => {
837
- expect(result.current.eventAppRole).toBe('event_admin');
838
- });
839
-
840
- // Change event
841
- mockUseEvents.mockReturnValue({
842
- selectedEvent: { event_id: 'different-event-id' }
843
- });
194
+ const { result } = renderHook(() => useRBAC());
844
195
 
845
- rerender();
196
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
846
197
 
847
- await waitFor(() => {
848
- expect(mockSupabase.rpc).toHaveBeenCalledTimes(4);
849
- });
850
- });
851
-
852
- it('handles missing EventProvider gracefully', () => {
853
- const consoleSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
854
-
855
- // Mock EventProvider to throw error
856
- mockUseEvents.mockImplementation(() => {
857
- throw new Error('EventProvider not available');
858
- });
859
-
860
- const mockPermissions = [
861
- {
862
- permission_type: 'organisation_access',
863
- role_name: 'org_admin',
864
- has_permission: true,
865
- granted_at: '2023-01-01T00:00:00Z'
866
- }
867
- ];
868
-
869
-
870
- setupRBACMock(mockPermissions);
871
-
872
- const { result } = renderHook(() => useRBAC());
873
-
874
- expect(result.current.eventAppRole).toBeNull();
875
- expect(consoleSpy).toHaveBeenCalledWith('useRBAC: EventProvider not available, continuing without event context');
876
-
877
- consoleSpy.mockRestore();
878
- });
198
+ expect(result.current.hasGlobalPermission('org_admin')).toBe(true);
199
+ expect(result.current.hasGlobalPermission('super_admin')).toBe(false);
879
200
  });
880
201
 
881
- describe('User Context', () => {
882
- it('includes user in returned context', async () => {
883
- const mockUser = testDataGenerators.createUser();
884
-
885
- mockUseUnifiedAuth.mockReturnValue({
886
- user: mockUser,
887
- supabase: mockSupabase,
888
- appName: 'test-app'
889
- });
890
-
891
- const mockPermissions = [
892
- {
893
- permission_type: 'organisation_access',
894
- role_name: 'org_admin',
895
- has_permission: true,
896
- granted_at: '2023-01-01T00:00:00Z'
897
- }
898
- ];
899
-
900
-
901
-
902
- setupRBACMock(mockPermissions);
202
+ it('captures errors from resolveAppContext', async () => {
203
+ mockUseUnifiedAuth.mockReturnValue({
204
+ user: { id: 'user-1' },
205
+ session: { access_token: 'token' },
206
+ appName: 'console',
207
+ });
208
+ mockResolveAppContext.mockResolvedValue(null);
903
209
 
904
-
210
+ const { result } = renderHook(() => useRBAC());
905
211
 
906
- const { result } = renderHook(() => useRBAC());
212
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
907
213
 
908
- await waitFor(() => {
909
- expect(result.current.user).toEqual(mockUser);
910
- });
911
- });
214
+ expect(result.current.error).toBeInstanceOf(Error);
215
+ expect(result.current.globalRole).toBeNull();
216
+ expect(await result.current.hasPermission('read', 'users')).toBe(false);
912
217
  });
913
- });
218
+ });