@jmruthers/pace-core 0.5.188 → 0.5.190

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 (424) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -5
  38. package/dist/components.js +19 -23
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -12
  46. package/dist/index.js +79 -73
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +128 -0
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  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/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +155 -135
  214. package/docs/api-reference/components.md +72 -29
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -4
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +252 -226
  243. package/src/components/Avatar/Avatar.tsx +179 -53
  244. package/src/components/Avatar/index.ts +1 -1
  245. package/src/components/Button/Button.test.tsx +2 -1
  246. package/src/components/Button/Button.tsx +3 -3
  247. package/src/components/Calendar/Calendar.test.tsx +53 -37
  248. package/src/components/Calendar/Calendar.tsx +409 -82
  249. package/src/components/Card/Card.test.tsx +7 -4
  250. package/src/components/Card/Card.tsx +3 -6
  251. package/src/components/Checkbox/Checkbox.tsx +2 -2
  252. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  253. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  254. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  255. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  256. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  257. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  258. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  259. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  260. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  261. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  262. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  263. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  264. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  265. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  266. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  267. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  268. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  269. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  270. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  271. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  272. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  273. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  274. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  275. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  276. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  277. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  278. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  279. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  280. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  281. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  282. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  284. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  285. package/src/components/Dialog/Dialog.tsx +2 -2
  286. package/src/components/EventSelector/EventSelector.tsx +7 -7
  287. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  288. package/src/components/FileUpload/FileUpload.tsx +7 -4
  289. package/src/components/Header/Header.test.tsx +28 -0
  290. package/src/components/Header/Header.tsx +22 -9
  291. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  292. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  293. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  294. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  295. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  296. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  299. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  300. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  301. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  302. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  303. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  304. package/src/components/Progress/Progress.test.tsx +18 -19
  305. package/src/components/Progress/Progress.tsx +31 -32
  306. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  307. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  308. package/src/components/Select/Select.tsx +5 -5
  309. package/src/components/Switch/Switch.test.tsx +2 -1
  310. package/src/components/Switch/Switch.tsx +1 -1
  311. package/src/components/Toast/Toast.tsx +1 -1
  312. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  313. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  314. package/src/components/UserMenu/UserMenu.tsx +10 -8
  315. package/src/components/index.ts +2 -1
  316. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  317. package/src/eslint-rules/pace-core-compliance.js +0 -2
  318. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  319. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  320. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  321. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  322. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  323. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  324. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  325. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  326. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  327. package/src/hooks/index.ts +1 -1
  328. package/src/hooks/public/usePublicEvent.ts +2 -2
  329. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  330. package/src/hooks/useAppConfig.ts +24 -5
  331. package/src/hooks/useFileDisplay.ts +297 -34
  332. package/src/hooks/useFileReference.ts +56 -11
  333. package/src/hooks/useFileUrl.ts +1 -1
  334. package/src/hooks/useInactivityTracker.ts +16 -7
  335. package/src/hooks/usePermissionCache.test.ts +85 -8
  336. package/src/hooks/useQueryCache.ts +21 -0
  337. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  338. package/src/hooks/useSecureDataAccess.ts +80 -37
  339. package/src/index.ts +2 -1
  340. package/src/providers/services/EventServiceProvider.tsx +37 -17
  341. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  342. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  343. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  344. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  345. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  346. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  347. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  348. package/src/rbac/api.ts +240 -36
  349. package/src/rbac/cache-invalidation.ts +21 -7
  350. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  351. package/src/rbac/components/NavigationGuard.tsx +23 -63
  352. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  353. package/src/rbac/components/NavigationProvider.tsx +13 -11
  354. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  355. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  356. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  357. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  358. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  359. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  360. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  361. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  362. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  363. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  364. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  365. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  366. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  367. package/src/rbac/engine.ts +4 -2
  368. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  369. package/src/rbac/hooks/index.ts +3 -0
  370. package/src/rbac/hooks/useCan.test.ts +101 -53
  371. package/src/rbac/hooks/usePermissions.ts +108 -41
  372. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  373. package/src/rbac/hooks/useRBAC.ts +83 -40
  374. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  375. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  376. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  377. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  378. package/src/rbac/request-deduplication.ts +1 -1
  379. package/src/rbac/secureClient.ts +72 -12
  380. package/src/rbac/security.ts +29 -23
  381. package/src/rbac/types.ts +10 -0
  382. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  383. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  384. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  385. package/src/rbac/utils/contextValidator.ts +288 -0
  386. package/src/rbac/utils/eventContext.ts +48 -2
  387. package/src/services/EventService.ts +165 -21
  388. package/src/services/OrganisationService.ts +37 -2
  389. package/src/services/__tests__/EventService.test.ts +26 -21
  390. package/src/types/file-reference.ts +13 -10
  391. package/src/utils/app/appNameResolver.test.ts +346 -73
  392. package/src/utils/context/superAdminOverride.ts +58 -0
  393. package/src/utils/file-reference/index.ts +61 -33
  394. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  395. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  396. package/src/utils/storage/helpers.test.ts +1 -1
  397. package/src/utils/storage/helpers.ts +38 -19
  398. package/src/utils/storage/types.ts +15 -8
  399. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  400. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  401. package/src/vite-env.d.ts +2 -2
  402. package/dist/chunk-3GOZZZYH.js.map +0 -1
  403. package/dist/chunk-DDM4CCYT.js.map +0 -1
  404. package/dist/chunk-E7UAOUMY.js +0 -75
  405. package/dist/chunk-E7UAOUMY.js.map +0 -1
  406. package/dist/chunk-EFCLXK7F.js.map +0 -1
  407. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  408. package/dist/chunk-HEHYGYOX.js.map +0 -1
  409. package/dist/chunk-IM4QE42D.js.map +0 -1
  410. package/dist/chunk-IPCH26AG.js.map +0 -1
  411. package/dist/chunk-SAUPYVLF.js.map +0 -1
  412. package/dist/chunk-THRPYOFK.js.map +0 -1
  413. package/dist/chunk-UNOTYLQF.js.map +0 -1
  414. package/dist/chunk-VGZZXKBR.js.map +0 -1
  415. package/dist/chunk-YHCN776L.js.map +0 -1
  416. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  417. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  418. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  419. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  420. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  421. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  422. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  423. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  424. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
package/src/rbac/api.ts CHANGED
@@ -29,6 +29,8 @@ import { SecurityContext } from './security';
29
29
  import { createLogger } from '../utils/core/logger';
30
30
  import { enablePerformanceMonitoring } from './performance';
31
31
  import { getOrCreateRequest } from './request-deduplication';
32
+ import { ContextValidator } from './utils/contextValidator';
33
+ import type { AppConfig } from './utils/contextValidator';
32
34
 
33
35
  const log = createLogger('RBACAPI');
34
36
 
@@ -108,6 +110,8 @@ function getEngine(): RBACEngine {
108
110
  * Get user's access level in a scope
109
111
  *
110
112
  * @param input - Access level input
113
+ * @param appConfig - Optional app configuration
114
+ * @param appName - Optional app name
111
115
  * @returns Promise resolving to access level
112
116
  *
113
117
  * @example
@@ -118,18 +122,55 @@ function getEngine(): RBACEngine {
118
122
  * });
119
123
  * ```
120
124
  */
121
- export async function getAccessLevel(input: {
122
- userId: UUID;
123
- scope: Scope;
124
- }): Promise<AccessLevel> {
125
+ export async function getAccessLevel(
126
+ input: {
127
+ userId: UUID;
128
+ scope: Scope;
129
+ },
130
+ appConfig?: AppConfig | null,
131
+ appName?: string
132
+ ): Promise<AccessLevel> {
125
133
  const engine = getEngine();
126
- return engine.getAccessLevel(input);
134
+
135
+ // Check super admin status first - super admins bypass context requirements
136
+ const isSuperAdminUser = await engine['checkSuperAdmin'](input.userId);
137
+ if (isSuperAdminUser) {
138
+ return 'super';
139
+ }
140
+
141
+ // Fetch app config if not provided
142
+ let resolvedAppConfig: AppConfig | null = appConfig ?? null;
143
+ let resolvedAppName = appName;
144
+
145
+ if (!resolvedAppConfig && input.scope.appId) {
146
+ resolvedAppConfig = await getAppConfig(input.scope.appId);
147
+ }
148
+
149
+ // Validate context using ContextValidator
150
+ const validation = await ContextValidator.resolveRequiredContext(
151
+ input.scope,
152
+ resolvedAppConfig,
153
+ resolvedAppName,
154
+ engine['supabase']
155
+ );
156
+
157
+ if (!validation.isValid || !validation.resolvedScope) {
158
+ throw validation.error || new OrganisationContextRequiredError();
159
+ }
160
+
161
+ // Use resolved scope
162
+ return engine.getAccessLevel({
163
+ ...input,
164
+ scope: validation.resolvedScope
165
+ });
127
166
  }
128
167
 
129
168
  /**
130
169
  * Get user's permission map for a scope
131
170
  *
132
171
  * @param input - Permission map input
172
+ * @param appConfig - Optional app configuration
173
+ * @param appName - Optional app name
133
174
  * @returns Promise resolving to permission map
134
175
  *
135
176
  * @example
@@ -144,12 +185,41 @@ export async function getAccessLevel(input: {
144
185
  * });
145
186
  * ```
146
187
  */
147
- export async function getPermissionMap(input: {
148
- userId: UUID;
149
- scope: Scope;
150
- }): Promise<PermissionMap> {
188
+ export async function getPermissionMap(
189
+ input: {
190
+ userId: UUID;
191
+ scope: Scope;
192
+ },
193
+ appConfig?: AppConfig | null,
194
+ appName?: string
195
+ ): Promise<PermissionMap> {
151
196
  const engine = getEngine();
152
- return engine.getPermissionMap(input);
197
+
198
+ // Fetch app config if not provided
199
+ let resolvedAppConfig: AppConfig | null = appConfig ?? null;
200
+ let resolvedAppName = appName;
201
+
202
+ if (!resolvedAppConfig && input.scope.appId) {
203
+ resolvedAppConfig = await getAppConfig(input.scope.appId);
204
+ }
205
+
206
+ // Validate context using ContextValidator
207
+ const validation = await ContextValidator.resolveRequiredContext(
208
+ input.scope,
209
+ resolvedAppConfig,
210
+ resolvedAppName,
211
+ engine['supabase']
212
+ );
213
+
214
+ if (!validation.isValid || !validation.resolvedScope) {
215
+ throw validation.error || new OrganisationContextRequiredError();
216
+ }
217
+
218
+ // Use resolved scope
219
+ return engine.getPermissionMap({
220
+ ...input,
221
+ scope: validation.resolvedScope
222
+ });
153
223
  }
154
224
 
155
225
  export async function resolveAppContext(input: {
@@ -160,18 +230,49 @@ export async function resolveAppContext(input: {
160
230
  return engine.resolveAppContext(input);
161
231
  }
162
232
 
163
- export async function getRoleContext(input: {
164
- userId: UUID;
165
- scope: Scope;
166
- }): Promise<RBACRoleContext> {
233
+ export async function getRoleContext(
234
+ input: {
235
+ userId: UUID;
236
+ scope: Scope;
237
+ },
238
+ appConfig?: AppConfig | null,
239
+ appName?: string
240
+ ): Promise<RBACRoleContext> {
167
241
  const engine = getEngine();
168
- return engine.getRoleContext(input);
242
+
243
+ // Fetch app config if not provided
244
+ let resolvedAppConfig: AppConfig | null = appConfig ?? null;
245
+ let resolvedAppName = appName;
246
+
247
+ if (!resolvedAppConfig && input.scope.appId) {
248
+ resolvedAppConfig = await getAppConfig(input.scope.appId);
249
+ }
250
+
251
+ // Validate context using ContextValidator
252
+ const validation = await ContextValidator.resolveRequiredContext(
253
+ input.scope,
254
+ resolvedAppConfig,
255
+ resolvedAppName,
256
+ engine['supabase']
257
+ );
258
+
259
+ if (!validation.isValid || !validation.resolvedScope) {
260
+ throw validation.error || new OrganisationContextRequiredError();
261
+ }
262
+
263
+ // Use resolved scope
264
+ return engine.getRoleContext({
265
+ ...input,
266
+ scope: validation.resolvedScope
267
+ });
169
268
  }
170
269
 
171
270
  /**
172
271
  * Check if user has a specific permission
173
272
  *
174
273
  * @param input - Permission check input
274
+ * @param appConfig - Optional app configuration (if not provided, will be fetched)
275
+ * @param appName - Optional app name (for PORTAL/ADMIN special case and config lookup)
175
276
  * @returns Promise resolving to permission result
176
277
  *
177
278
  * @example
@@ -184,24 +285,68 @@ export async function getRoleContext(input: {
184
285
  * });
185
286
  * ```
186
287
  */
187
- export async function isPermitted(input: PermissionCheck): Promise<boolean> {
288
+ export async function isPermitted(
289
+ input: PermissionCheck,
290
+ appConfig?: AppConfig | null,
291
+ appName?: string
292
+ ): Promise<boolean> {
188
293
  const engine = getEngine();
189
294
 
190
- // Validate organisation context is required
191
- if (!input.scope.organisationId) {
192
- throw new OrganisationContextRequiredError();
295
+ // Fetch app config if not provided and we have appId
296
+ let resolvedAppConfig: AppConfig | null = appConfig ?? null;
297
+ let resolvedAppName = appName;
298
+
299
+ if (!resolvedAppConfig && input.scope.appId) {
300
+ resolvedAppConfig = await getAppConfig(input.scope.appId);
193
301
  }
194
302
 
195
- // Create security context from input
196
- // OrganisationId is required - it can always be derived from event context in event-based apps
303
+ // If we have appId but no appName, try to get it from the database
304
+ if (!resolvedAppName && input.scope.appId) {
305
+ try {
306
+ const { data } = await engine['supabase']
307
+ .from('rbac_apps')
308
+ .select('name')
309
+ .eq('id', input.scope.appId)
310
+ .eq('is_active', true)
311
+ .single() as { data: { name: string } | null };
312
+ if (data) {
313
+ resolvedAppName = data.name;
314
+ }
315
+ } catch (err) {
316
+ // Ignore errors - appName is optional
317
+ }
318
+ }
319
+
320
+ // Validate context using ContextValidator
321
+ const validation = await ContextValidator.resolveRequiredContext(
322
+ input.scope,
323
+ resolvedAppConfig,
324
+ resolvedAppName,
325
+ engine['supabase']
326
+ );
327
+
328
+ if (!validation.isValid || !validation.resolvedScope) {
329
+ throw validation.error || new OrganisationContextRequiredError();
330
+ }
331
+
332
+ // Use resolved scope for permission check
333
+ const validatedScope = validation.resolvedScope;
334
+
335
+ // Create security context from validated scope
197
336
  const securityContext: SecurityContext = {
198
337
  userId: input.userId,
199
- organisationId: input.scope.organisationId, // Required - no fallback
338
+ organisationId: validatedScope.organisationId || null,
200
339
  timestamp: new Date(),
201
340
  // Optional fields can be omitted
202
341
  };
203
342
 
204
- return engine.isPermitted(input, securityContext);
343
+ // Create new input with validated scope
344
+ const validatedInput: PermissionCheck = {
345
+ ...input,
346
+ scope: validatedScope
347
+ };
348
+
349
+ return engine.isPermitted(validatedInput, securityContext);
205
350
  }
206
351
 
207
352
  /**
@@ -211,15 +356,21 @@ export async function isPermitted(input: PermissionCheck): Promise<boolean> {
211
356
  * and checks cache before making new requests. Uses session cache for page-level checks.
212
357
  *
213
358
  * @param input - Permission check input
359
+ * @param appConfig - Optional app configuration
360
+ * @param appName - Optional app name
214
361
  * @returns Promise resolving to permission result
215
362
  */
216
- export async function isPermittedCached(input: PermissionCheck): Promise<boolean> {
363
+ export async function isPermittedCached(
364
+ input: PermissionCheck,
365
+ appConfig?: AppConfig | null,
366
+ appName?: string
367
+ ): Promise<boolean> {
217
368
  const { userId, scope, permission, pageId } = input;
218
369
 
219
370
  // Check cache first (checks both short-term and session cache)
220
371
  const cacheKey = RBACCache.generatePermissionKey({
221
372
  userId,
222
- organisationId: scope.organisationId!,
373
+ organisationId: scope.organisationId,
223
374
  eventId: scope.eventId,
224
375
  appId: scope.appId,
225
376
  permission,
@@ -233,8 +384,8 @@ export async function isPermittedCached(input: PermissionCheck): Promise<boolean
233
384
 
234
385
  // Use request deduplication - if same request is in-flight, share the promise
235
386
  return getOrCreateRequest(input, async (checkInput) => {
236
- // Check permission
237
- const result = await isPermitted(checkInput);
387
+ // Check permission with context validation
388
+ const result = await isPermitted(checkInput, appConfig, appName);
238
389
 
239
390
  // Determine if this is a page-level check (has pageId or permission contains 'page.')
240
391
  const isPageLevelCheck = !!pageId || permission.includes('page.');
@@ -329,33 +480,86 @@ export async function isSuperAdmin(userId: UUID): Promise<boolean> {
329
480
  * @param appId - App ID
330
481
  * @returns Promise resolving to app configuration
331
482
  */
332
- export async function getAppConfig(appId: UUID): Promise<{ requires_event: boolean } | null> {
333
- // This function requires a Supabase client to be provided
334
- // Callers should pass the client as a parameter
335
- log.warn('getAppConfig called without Supabase client - returning null');
336
- return null;
483
+ export async function getAppConfig(appId: UUID): Promise<AppConfig | null> {
484
+ try {
485
+ const engine = getEngine();
486
+ return getAppConfigWithClient(engine['supabase'], appId);
487
+ } catch (err) {
488
+ // RBAC not initialized - return null gracefully
489
+ if (err instanceof RBACNotInitializedError) {
490
+ return null;
491
+ }
492
+ throw err;
493
+ }
337
494
  }
338
495
 
339
- export async function getAppConfigWithClient(client: SupabaseClient, appId: UUID): Promise<{ requires_event: boolean } | null> {
496
+ export async function getAppConfigWithClient(client: SupabaseClient | null | undefined, appId: UUID): Promise<AppConfig | null> {
497
+ // Return null if client is not available
498
+ if (!client) {
499
+ return null;
500
+ }
501
+
502
+ // Cache key for app config - cache for 5 minutes (app config rarely changes)
503
+ const cacheKey = `app_config:${appId}`;
504
+
505
+ // Check cache first
506
+ const cached = rbacCache.get<AppConfig>(cacheKey, true);
507
+ if (cached !== null) {
508
+ return cached;
509
+ }
510
+
340
511
  try {
341
512
  const { data, error } = await client
342
513
  .from('rbac_apps')
343
- .select('requires_event')
514
+ .select('requires_event, name')
344
515
  .eq('id', appId)
345
516
  .eq('is_active', true)
346
- .single() as { data: { requires_event: boolean } | null; error: any };
517
+ .single() as { data: { requires_event: boolean; name: string } | null; error: any };
347
518
 
348
519
  if (error || !data) {
349
520
  return null;
350
521
  }
351
522
 
352
- return { requires_event: data.requires_event };
523
+ const appConfig: AppConfig = { requires_event: data.requires_event ?? false };
524
+
525
+ // Cache the result for 5 minutes (300000ms) with session cache enabled
526
+ // App config rarely changes, so we can cache it longer
527
+ rbacCache.set(cacheKey, appConfig, 5 * 60 * 1000, true);
528
+
529
+ return appConfig;
353
530
  } catch (err) {
354
531
  log.error('Error fetching app config:', err);
355
532
  return null;
356
533
  }
357
534
  }
358
535
 
536
+ /**
537
+ * Get app configuration by app name
538
+ *
539
+ * @param appName - App name
540
+ * @returns Promise resolving to app configuration
541
+ */
542
+ export async function getAppConfigByName(appName: string): Promise<AppConfig | null> {
543
+ const engine = getEngine();
544
+ try {
545
+ const { data, error } = await engine['supabase']
546
+ .from('rbac_apps')
547
+ .select('requires_event, name')
548
+ .eq('name', appName)
549
+ .eq('is_active', true)
550
+ .single() as { data: { requires_event: boolean; name: string } | null; error: any };
551
+
552
+ if (error || !data) {
553
+ return null;
554
+ }
555
+
556
+ return { requires_event: data.requires_event ?? false };
557
+ } catch (err) {
558
+ log.error('Error fetching app config by name:', err);
559
+ return null;
560
+ }
561
+ }
562
+
359
563
  /**
360
564
  * Check if user is organisation admin
361
565
  *
@@ -178,22 +178,36 @@ export class RBACCacheInvalidationManager {
178
178
  });
179
179
  }
180
180
 
181
+ /**
182
+ * Cleanup subscriptions only (not all callbacks)
183
+ * Used internally to cleanup before setting up new subscriptions
184
+ */
185
+ private cleanupSubscriptions(): void {
186
+ this.channels.forEach(channel => {
187
+ try {
188
+ if (channel && typeof channel.unsubscribe === 'function') {
189
+ channel.unsubscribe();
190
+ }
191
+ } catch (error) {
192
+ log.warn('Failed to unsubscribe from channel:', error);
193
+ }
194
+ });
195
+ this.channels = [];
196
+ }
197
+
181
198
  /**
182
199
  * Setup realtime subscriptions for automatic cache invalidation
183
- * Prevents duplicate subscriptions by checking if already set up
200
+ * Always cleans up existing subscriptions before setting up new ones to prevent duplicates
184
201
  */
185
202
  private setupRealtimeSubscriptions(): void {
203
+ // Always cleanup existing subscriptions first to prevent duplicates
204
+ this.cleanupSubscriptions();
205
+
186
206
  // Check if realtime is available (skip in test environments)
187
207
  if (!this.supabase.channel || typeof this.supabase.channel !== 'function') {
188
208
  log.debug('Realtime not available, skipping subscriptions');
189
209
  return;
190
210
  }
191
-
192
- // Prevent duplicate subscriptions - if channels already exist, skip setup
193
- if (this.channels.length > 0) {
194
- log.debug('Realtime subscriptions already set up, skipping duplicate setup');
195
- return;
196
- }
197
211
 
198
212
  // Subscribe to organisation role changes
199
213
  const orgRolesChannel = this.supabase
@@ -111,7 +111,7 @@ export const supabase = createClient(url, key);
111
111
  // src/lib/supabase.ts
112
112
  export const supabase = createClient(
113
113
  import.meta.env.VITE_SUPABASE_URL,
114
- import.meta.env.VITE_SUPABASE_ANON_KEY
114
+ import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
115
115
  );
116
116
 
117
117
  // src/utils/api.ts
@@ -67,8 +67,8 @@
67
67
  import React, { useMemo, useCallback, useEffect, useState } from 'react';
68
68
  import { useMultiplePermissions } from '../hooks/usePermissions';
69
69
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
70
+ import { useResolvedScope } from '../hooks/useResolvedScope';
70
71
  import { UUID, Permission, Scope } from '../types';
71
- import { createScopeFromEvent } from '../utils/eventContext';
72
72
  import { NavigationItem } from './NavigationProvider';
73
73
  import { getRBACLogger } from '../config';
74
74
 
@@ -124,66 +124,28 @@ export function NavigationGuard({
124
124
  }: NavigationGuardProps) {
125
125
  const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
126
126
  const [hasChecked, setHasChecked] = useState(false);
127
- const [checkError, setCheckError] = useState<Error | null>(null);
128
- const [resolvedScope, setResolvedScope] = useState<Scope | null>(null);
129
-
130
- // Resolve scope - either use provided scope or resolve from context
131
- useEffect(() => {
132
- const resolveScope = async () => {
133
- if (scope) {
134
- setResolvedScope(scope);
135
- return;
136
- }
137
-
138
- // If we have both organisation and event, use them directly
139
- if (selectedOrganisation && selectedEvent) {
140
- setResolvedScope({
141
- organisationId: selectedOrganisation.id,
142
- eventId: selectedEvent.event_id,
143
- appId: undefined
144
- });
145
- return;
146
- }
147
-
148
- // If we only have organisation, use it
149
- if (selectedOrganisation) {
150
- setResolvedScope({
151
- organisationId: selectedOrganisation.id,
152
- eventId: selectedEvent?.event_id || undefined,
153
- appId: undefined
154
- });
155
- return;
156
- }
157
-
158
- // If we only have event, resolve organisation from event
159
- if (selectedEvent && supabase) {
160
- try {
161
- const eventScope = await createScopeFromEvent(supabase, selectedEvent.event_id);
162
- if (!eventScope) {
163
- setCheckError(new Error('Could not resolve organization from event context'));
164
- return;
165
- }
166
- setResolvedScope(eventScope);
167
- } catch (error) {
168
- setCheckError(error as Error);
169
- }
170
- return;
171
- }
172
-
173
- // No context available
174
- setCheckError(new Error('Either organisation context or event context is required for navigation permission checking'));
175
- };
176
-
177
- resolveScope();
178
- }, [scope, selectedOrganisation, selectedEvent, supabase]);
127
+
128
+ // Use useResolvedScope hook for consistent scope resolution
129
+ const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
130
+ supabase,
131
+ selectedOrganisationId: selectedOrganisation?.id || null,
132
+ selectedEventId: selectedEvent?.event_id || null
133
+ });
134
+
135
+ // Use provided scope if available, otherwise use resolved scope
136
+ const effectiveScope = scope || hookResolvedScope;
137
+ const checkError = scopeError;
179
138
 
180
139
  // Check all permissions using useMultiplePermissions hook
181
- const { results: permissionResults, isLoading, error } = useMultiplePermissions(
140
+ const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
182
141
  user?.id || '',
183
- resolvedScope || { eventId: selectedEvent?.event_id || undefined },
142
+ effectiveScope || { eventId: selectedEvent?.event_id || undefined },
184
143
  navigationItem.permissions || [],
185
144
  true // Use cache
186
145
  );
146
+
147
+ const isLoading = scopeLoading || permissionsLoading;
148
+ const error = checkError || permissionsError;
187
149
 
188
150
  // Determine if user has required permissions based on requireAll prop
189
151
  const hasRequiredPermissions = useMemo((): boolean => {
@@ -202,13 +164,11 @@ export function NavigationGuard({
202
164
  useEffect(() => {
203
165
  if (!isLoading && !error) {
204
166
  setHasChecked(true);
205
- setCheckError(null);
206
167
 
207
168
  if (!hasRequiredPermissions && onDenied) {
208
169
  onDenied(navigationItem);
209
170
  }
210
171
  } else if (error) {
211
- setCheckError(error);
212
172
  setHasChecked(true);
213
173
  }
214
174
  }, [hasRequiredPermissions, isLoading, error, navigationItem, onDenied]);
@@ -221,13 +181,13 @@ export function NavigationGuard({
221
181
  navigationItem: navigationItem.id,
222
182
  permissions: navigationItem.permissions,
223
183
  userId: user?.id,
224
- scope: resolvedScope,
184
+ scope: effectiveScope,
225
185
  allowed: hasRequiredPermissions,
226
186
  requireAll,
227
187
  timestamp: new Date().toISOString()
228
188
  });
229
189
  }
230
- }, [auditLog, hasChecked, isLoading, navigationItem, user?.id, resolvedScope, hasRequiredPermissions, requireAll]);
190
+ }, [auditLog, hasChecked, isLoading, navigationItem, user?.id, effectiveScope, hasRequiredPermissions, requireAll]);
231
191
 
232
192
  // Handle strict mode violations
233
193
  useEffect(() => {
@@ -237,15 +197,15 @@ export function NavigationGuard({
237
197
  navigationItem: navigationItem.id,
238
198
  permissions: navigationItem.permissions,
239
199
  userId: user?.id,
240
- scope: resolvedScope,
200
+ scope: effectiveScope,
241
201
  requireAll,
242
202
  timestamp: new Date().toISOString()
243
203
  });
244
204
  }
245
- }, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id, resolvedScope, requireAll]);
205
+ }, [strictMode, hasChecked, isLoading, hasRequiredPermissions, navigationItem, user?.id, effectiveScope, requireAll]);
246
206
 
247
207
  // Show loading state
248
- if (isLoading || !resolvedScope || !hasChecked) {
208
+ if (isLoading || !effectiveScope || !hasChecked) {
249
209
  return <>{loading}</>;
250
210
  }
251
211
 
@@ -288,7 +248,7 @@ function DefaultLoading() {
288
248
  return (
289
249
  <div className="flex items-center justify-center p-2">
290
250
  <div className="flex items-center space-x-2">
291
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-main-600"></div>
251
+ <div className="animate-spin rounded-full size-4 border-b-2 border-main-600"></div>
292
252
  <span className="text-sm text-sec-600">Checking...</span>
293
253
  </div>
294
254
  </div>