@jmruthers/pace-core 0.5.189 → 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 (420) 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-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
  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-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
  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-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
  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 -4
  38. package/dist/components.js +19 -19
  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 -11
  46. package/dist/index.js +79 -69
  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 +1 -1
  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 +151 -92
  214. package/docs/api-reference/components.md +15 -7
  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 -3
  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 +95 -43
  243. package/src/components/Avatar/Avatar.tsx +16 -16
  244. package/src/components/Button/Button.test.tsx +2 -1
  245. package/src/components/Button/Button.tsx +3 -3
  246. package/src/components/Calendar/Calendar.test.tsx +53 -37
  247. package/src/components/Calendar/Calendar.tsx +409 -82
  248. package/src/components/Card/Card.test.tsx +7 -4
  249. package/src/components/Card/Card.tsx +3 -6
  250. package/src/components/Checkbox/Checkbox.tsx +2 -2
  251. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  252. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  253. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  254. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  255. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  256. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  257. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  258. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  259. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  260. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  261. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  262. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  264. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  265. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  266. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  267. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  268. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  269. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  270. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  271. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  272. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  273. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  274. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  275. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  276. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  277. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  278. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  280. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  281. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  282. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  284. package/src/components/Dialog/Dialog.tsx +2 -2
  285. package/src/components/EventSelector/EventSelector.tsx +7 -7
  286. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  287. package/src/components/FileUpload/FileUpload.tsx +7 -4
  288. package/src/components/Header/Header.test.tsx +28 -0
  289. package/src/components/Header/Header.tsx +22 -9
  290. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  291. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  292. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  293. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  294. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  295. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  296. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  299. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  300. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  301. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  302. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  303. package/src/components/Progress/Progress.test.tsx +18 -19
  304. package/src/components/Progress/Progress.tsx +31 -32
  305. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  306. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  307. package/src/components/Select/Select.tsx +5 -5
  308. package/src/components/Switch/Switch.test.tsx +2 -1
  309. package/src/components/Switch/Switch.tsx +1 -1
  310. package/src/components/Toast/Toast.tsx +1 -1
  311. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  312. package/src/components/UserMenu/UserMenu.tsx +3 -3
  313. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  314. package/src/eslint-rules/pace-core-compliance.js +0 -2
  315. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  316. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  317. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  318. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  319. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  320. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  321. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  322. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  323. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  324. package/src/hooks/index.ts +1 -1
  325. package/src/hooks/public/usePublicEvent.ts +2 -2
  326. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  327. package/src/hooks/useAppConfig.ts +24 -5
  328. package/src/hooks/useFileDisplay.ts +297 -34
  329. package/src/hooks/useFileReference.ts +56 -11
  330. package/src/hooks/useFileUrl.ts +1 -1
  331. package/src/hooks/useInactivityTracker.ts +16 -7
  332. package/src/hooks/usePermissionCache.test.ts +85 -8
  333. package/src/hooks/useQueryCache.ts +21 -0
  334. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  335. package/src/hooks/useSecureDataAccess.ts +80 -37
  336. package/src/providers/services/EventServiceProvider.tsx +37 -17
  337. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  338. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  339. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  340. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  341. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  342. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  343. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  344. package/src/rbac/api.ts +240 -36
  345. package/src/rbac/cache-invalidation.ts +21 -7
  346. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  347. package/src/rbac/components/NavigationGuard.tsx +23 -63
  348. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  349. package/src/rbac/components/NavigationProvider.tsx +13 -11
  350. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  351. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  352. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  353. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  354. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  355. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  356. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  357. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  358. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  359. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  360. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  361. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  362. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  363. package/src/rbac/engine.ts +4 -2
  364. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  365. package/src/rbac/hooks/index.ts +3 -0
  366. package/src/rbac/hooks/useCan.test.ts +101 -53
  367. package/src/rbac/hooks/usePermissions.ts +108 -41
  368. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  369. package/src/rbac/hooks/useRBAC.ts +83 -40
  370. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  371. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  372. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  373. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  374. package/src/rbac/request-deduplication.ts +1 -1
  375. package/src/rbac/secureClient.ts +72 -12
  376. package/src/rbac/security.ts +29 -23
  377. package/src/rbac/types.ts +10 -0
  378. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  379. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  380. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  381. package/src/rbac/utils/contextValidator.ts +288 -0
  382. package/src/rbac/utils/eventContext.ts +48 -2
  383. package/src/services/EventService.ts +165 -21
  384. package/src/services/OrganisationService.ts +37 -2
  385. package/src/services/__tests__/EventService.test.ts +26 -21
  386. package/src/types/file-reference.ts +13 -10
  387. package/src/utils/app/appNameResolver.test.ts +346 -73
  388. package/src/utils/context/superAdminOverride.ts +58 -0
  389. package/src/utils/file-reference/index.ts +61 -33
  390. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  391. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  392. package/src/utils/storage/helpers.test.ts +1 -1
  393. package/src/utils/storage/helpers.ts +38 -19
  394. package/src/utils/storage/types.ts +15 -8
  395. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  396. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  397. package/src/vite-env.d.ts +2 -2
  398. package/dist/chunk-3GOZZZYH.js.map +0 -1
  399. package/dist/chunk-DDM4CCYT.js.map +0 -1
  400. package/dist/chunk-E7UAOUMY.js +0 -75
  401. package/dist/chunk-E7UAOUMY.js.map +0 -1
  402. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  403. package/dist/chunk-HEHYGYOX.js.map +0 -1
  404. package/dist/chunk-IM4QE42D.js.map +0 -1
  405. package/dist/chunk-MX64ZF6I.js.map +0 -1
  406. package/dist/chunk-SAUPYVLF.js.map +0 -1
  407. package/dist/chunk-THRPYOFK.js.map +0 -1
  408. package/dist/chunk-UCQSRW7Z.js.map +0 -1
  409. package/dist/chunk-VGZZXKBR.js.map +0 -1
  410. package/dist/chunk-YGPFYGA6.js.map +0 -1
  411. package/dist/chunk-YHCN776L.js.map +0 -1
  412. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  413. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  414. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  415. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  416. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  417. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  418. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  419. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  420. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -69,8 +69,8 @@
69
69
  import React, { useMemo, useCallback, useEffect, useState } from 'react';
70
70
  import { useMultiplePermissions } from '../hooks/usePermissions';
71
71
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
72
+ import { useResolvedScope } from '../hooks/useResolvedScope';
72
73
  import { UUID, Permission, Scope } from '../types';
73
- import { createScopeFromEvent } from '../utils/eventContext';
74
74
  import { getRBACLogger } from '../config';
75
75
  import { createLogger } from '../../utils/core/logger';
76
76
 
@@ -132,66 +132,30 @@ export function PermissionEnforcer({
132
132
  }: PermissionEnforcerProps) {
133
133
  const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
134
134
  const [hasChecked, setHasChecked] = useState(false);
135
- const [checkError, setCheckError] = useState<Error | null>(null);
136
- const [resolvedScope, setResolvedScope] = useState<Scope | null>(null);
137
-
138
- // Resolve scope - either use provided scope or resolve from context
139
- useEffect(() => {
140
- const resolveScope = async () => {
141
- if (scope) {
142
- setResolvedScope(scope);
143
- return;
144
- }
145
-
146
- // If we have both organisation and event, use them directly
147
- if (selectedOrganisation && selectedEvent) {
148
- setResolvedScope({
149
- organisationId: selectedOrganisation.id,
150
- eventId: selectedEvent.event_id,
151
- appId: undefined
152
- });
153
- return;
154
- }
155
-
156
- // If we only have organisation, use it
157
- if (selectedOrganisation) {
158
- setResolvedScope({
159
- organisationId: selectedOrganisation.id,
160
- eventId: selectedEvent?.event_id || undefined,
161
- appId: undefined
162
- });
163
- return;
164
- }
165
-
166
- // If we only have event, resolve organisation from event
167
- if (selectedEvent && supabase) {
168
- try {
169
- const eventScope = await createScopeFromEvent(supabase, selectedEvent.event_id);
170
- if (!eventScope) {
171
- setCheckError(new Error('Could not resolve organization from event context'));
172
- return;
173
- }
174
- setResolvedScope(eventScope);
175
- } catch (error) {
176
- setCheckError(error as Error);
177
- }
178
- return;
179
- }
180
-
181
- // No context available
182
- setCheckError(new Error('Either organisation context or event context is required for permission checking'));
183
- };
184
-
185
- resolveScope();
186
- }, [scope, selectedOrganisation, selectedEvent, supabase]);
135
+
136
+ // Use useResolvedScope hook for consistent scope resolution
137
+ // For event-required apps: selectedOrganisation is null, org derived from event
138
+ // For org-required apps: selectedOrganisation is available, event optional
139
+ const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
140
+ supabase,
141
+ selectedOrganisationId: selectedOrganisation?.id || null,
142
+ selectedEventId: selectedEvent?.event_id || null
143
+ });
144
+
145
+ // Use provided scope if available, otherwise use resolved scope
146
+ const effectiveScope = scope || resolvedScope;
147
+ const checkError = scopeError;
187
148
 
188
149
  // Check all permissions using useMultiplePermissions hook
189
- const { results: permissionResults, isLoading, error } = useMultiplePermissions(
150
+ const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
190
151
  user?.id || '',
191
- resolvedScope || { eventId: selectedEvent?.event_id || undefined },
152
+ effectiveScope || { eventId: selectedEvent?.event_id || undefined },
192
153
  permissions,
193
154
  true // Use cache
194
155
  );
156
+
157
+ const isLoading = scopeLoading || permissionsLoading;
158
+ const error = checkError || permissionsError;
195
159
 
196
160
  // Determine if user has required permissions based on requireAll prop
197
161
  const hasRequiredPermissions = useMemo((): boolean => {
@@ -215,13 +179,11 @@ export function PermissionEnforcer({
215
179
  useEffect(() => {
216
180
  if (!isLoading && !error) {
217
181
  setHasChecked(true);
218
- setCheckError(null);
219
182
 
220
183
  if (!hasRequiredPermissions && onDenied) {
221
184
  onDenied(permissions, operation);
222
185
  }
223
186
  } else if (error) {
224
- setCheckError(error);
225
187
  setHasChecked(true);
226
188
  }
227
189
  }, [hasRequiredPermissions, isLoading, error, permissions, operation, onDenied]);
@@ -233,13 +195,13 @@ export function PermissionEnforcer({
233
195
  permissions,
234
196
  operation,
235
197
  userId: user?.id,
236
- scope: resolvedScope,
198
+ scope: effectiveScope,
237
199
  allowed: hasRequiredPermissions,
238
200
  requireAll,
239
201
  timestamp: new Date().toISOString()
240
202
  });
241
203
  }
242
- }, [auditLog, hasChecked, isLoading, permissions, operation, user?.id, resolvedScope, hasRequiredPermissions, requireAll]);
204
+ }, [auditLog, hasChecked, isLoading, permissions, operation, user?.id, effectiveScope, hasRequiredPermissions, requireAll]);
243
205
 
244
206
  // Handle strict mode violations
245
207
  useEffect(() => {
@@ -249,12 +211,12 @@ export function PermissionEnforcer({
249
211
  permissions,
250
212
  operation,
251
213
  userId: user?.id,
252
- scope: resolvedScope,
214
+ scope: effectiveScope,
253
215
  requireAll,
254
216
  timestamp: new Date().toISOString()
255
217
  });
256
218
  }
257
- }, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, resolvedScope, requireAll]);
219
+ }, [strictMode, hasChecked, isLoading, hasRequiredPermissions, permissions, operation, user?.id, effectiveScope, requireAll]);
258
220
 
259
221
  // Show loading state
260
222
  if (isLoading || !hasChecked) {
@@ -307,7 +269,7 @@ function DefaultLoading() {
307
269
  return (
308
270
  <div className="flex items-center justify-center min-h-[200px] p-8">
309
271
  <div className="flex items-center space-x-2">
310
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-main-600"></div>
272
+ <div className="animate-spin rounded-full size-8 border-b-2 border-main-600"></div>
311
273
  <span className="text-sec-600">Checking permissions...</span>
312
274
  </div>
313
275
  </div>
@@ -66,6 +66,7 @@
66
66
  import React, { useMemo, useCallback, useEffect, useState, createContext, useContext } from 'react';
67
67
  import { useLocation, useNavigate, Outlet } from 'react-router-dom';
68
68
  import { useCan } from '../hooks';
69
+ import { useResolvedScope } from '../hooks/useResolvedScope';
69
70
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
70
71
  import { UUID, Permission, Scope, AccessLevel } from '../types';
71
72
  import { getRBACLogger } from '../config';
@@ -190,22 +191,23 @@ export function RoleBasedRouter({
190
191
  maxHistorySize = 1000,
191
192
  unauthorizedComponent: UnauthorizedComponent = DefaultUnauthorizedComponent
192
193
  }: RoleBasedRouterProps) {
193
- const { user, selectedOrganisation, selectedEvent } = useUnifiedAuth();
194
+ const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
194
195
  const location = useLocation();
195
196
  const navigate = useNavigate();
196
197
  const [routeAccessHistory, setRouteAccessHistory] = useState<RouteAccessRecord[]>([]);
197
198
  const [currentRoute, setCurrentRoute] = useState<string>('');
198
199
 
199
- // Get current scope
200
- const currentScope = useMemo((): Scope | null => {
201
- if (!selectedOrganisation) return null;
202
-
203
- return {
204
- organisationId: selectedOrganisation.id,
205
- eventId: selectedEvent?.event_id || undefined,
206
- appId: undefined
207
- };
208
- }, [selectedOrganisation, selectedEvent]);
200
+ // Use useResolvedScope to get proper scope (org derived from event if needed)
201
+ const { resolvedScope } = useResolvedScope({
202
+ supabase,
203
+ selectedOrganisationId: selectedOrganisation?.id || null,
204
+ selectedEventId: selectedEvent?.event_id || null
205
+ });
206
+
207
+ // Get current scope from resolved scope
208
+ // For event-required apps: org is derived from event
209
+ // For org-required apps: org comes from selectedOrganisation
210
+ const currentScope = resolvedScope;
209
211
 
210
212
  // Get route configuration for current path
211
213
  const currentRouteConfig = useMemo((): RouteConfig | null => {
@@ -364,7 +366,7 @@ export function RoleBasedRouter({
364
366
  return (
365
367
  <div className="flex items-center justify-center min-h-screen">
366
368
  <div className="text-center">
367
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-main-600 mx-auto mb-4"></div>
369
+ <div className="animate-spin rounded-full size-8 border-b-2 border-main-600 mx-auto mb-4"></div>
368
370
  <p className="text-sec-600">Checking permissions...</p>
369
371
  </div>
370
372
  </div>
@@ -59,6 +59,7 @@
59
59
  import React, { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
60
60
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
61
61
  import { useSecureDataAccess } from '../../hooks/useSecureDataAccess';
62
+ import { useResolvedScope } from '../hooks/useResolvedScope';
62
63
  import { UUID, Scope, Permission } from '../types';
63
64
  import { getRBACLogger } from '../config';
64
65
 
@@ -142,21 +143,22 @@ export function SecureDataProvider({
142
143
  maxHistorySize = 1000,
143
144
  enforceRLS = true
144
145
  }: SecureDataProviderProps) {
145
- const { user, selectedOrganisation, selectedEvent } = useUnifiedAuth();
146
+ const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
146
147
  const { validateContext } = useSecureDataAccess();
147
148
  const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);
148
149
  const [isEnabled, setIsEnabled] = useState(true);
149
150
 
150
- // Get current scope
151
- const currentScope = useMemo((): Scope | null => {
152
- if (!selectedOrganisation) return null;
153
-
154
- return {
155
- organisationId: selectedOrganisation.id,
156
- eventId: selectedEvent?.event_id || undefined,
157
- appId: undefined
158
- };
159
- }, [selectedOrganisation, selectedEvent]);
151
+ // Use useResolvedScope to get proper scope (org derived from event if needed)
152
+ const { resolvedScope } = useResolvedScope({
153
+ supabase,
154
+ selectedOrganisationId: selectedOrganisation?.id || null,
155
+ selectedEventId: selectedEvent?.event_id || null
156
+ });
157
+
158
+ // Get current scope from resolved scope
159
+ // For event-required apps: org is derived from event
160
+ // For org-required apps: org comes from selectedOrganisation
161
+ const currentScope = resolvedScope;
160
162
 
161
163
  // Check if data access is allowed for a table and operation
162
164
  const isDataAccessAllowed = useCallback((
@@ -55,7 +55,13 @@ vi.mock('../../utils/eventContext', () => ({
55
55
  createScopeFromEvent: vi.fn()
56
56
  }));
57
57
 
58
+ // Mock useResolvedScope hook
59
+ vi.mock('../../hooks/useResolvedScope', () => ({
60
+ useResolvedScope: vi.fn()
61
+ }));
62
+
58
63
  import { createScopeFromEvent } from '../../utils/eventContext';
64
+ import { useResolvedScope } from '../../hooks/useResolvedScope';
59
65
 
60
66
  // Mock data
61
67
  const mockUser = {
@@ -96,6 +102,7 @@ const TestLoading = () => (
96
102
  describe('NavigationGuard Component', () => {
97
103
  const mockUseMultiplePermissions = vi.mocked(useMultiplePermissions);
98
104
  const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
105
+ const mockUseResolvedScope = vi.mocked(useResolvedScope);
99
106
 
100
107
  beforeEach(() => {
101
108
  vi.clearAllMocks();
@@ -108,6 +115,17 @@ describe('NavigationGuard Component', () => {
108
115
  supabase: {} as any
109
116
  });
110
117
 
118
+ // Mock useResolvedScope to return resolved scope immediately
119
+ mockUseResolvedScope.mockReturnValue({
120
+ resolvedScope: {
121
+ organisationId: 'org-123',
122
+ eventId: 'event-123',
123
+ appId: 'app-123'
124
+ },
125
+ isLoading: false,
126
+ error: null
127
+ });
128
+
111
129
  mockUseMultiplePermissions.mockReturnValue({
112
130
  results: { 'read:dashboard': true } as Record<string, boolean>,
113
131
  isLoading: false,
@@ -239,10 +257,11 @@ describe('NavigationGuard Component', () => {
239
257
 
240
258
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
241
259
  'user-123',
242
- expect.objectContaining({
260
+ {
243
261
  organisationId: 'org-123',
244
- eventId: 'event-123'
245
- }),
262
+ eventId: 'event-123',
263
+ appId: 'app-123'
264
+ },
246
265
  ['read:dashboard'],
247
266
  true
248
267
  );
@@ -367,7 +386,8 @@ describe('NavigationGuard Component', () => {
367
386
  );
368
387
  });
369
388
 
370
- it('resolves scope from organisation and event context', async () => {
389
+ it('resolves scope from organisation and event context (org-required app)', async () => {
390
+ // For org-required apps, organisation is primary, event is optional
371
391
  mockUseMultiplePermissions.mockReturnValue({
372
392
  results: { 'read:dashboard': true } as Record<string, boolean>,
373
393
  isLoading: false,
@@ -387,16 +407,18 @@ describe('NavigationGuard Component', () => {
387
407
 
388
408
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
389
409
  'user-123',
390
- expect.objectContaining({
410
+ {
391
411
  organisationId: 'org-123',
392
- eventId: 'event-123'
393
- }),
412
+ eventId: 'event-123',
413
+ appId: 'app-123'
414
+ },
394
415
  ['read:dashboard'],
395
416
  true
396
417
  );
397
418
  });
398
419
 
399
- it('resolves scope from organisation only', async () => {
420
+ it('resolves scope from organisation only (org-required app)', async () => {
421
+ // For org-required apps, organisation is primary context, event is optional
400
422
  mockUseUnifiedAuthFn.mockReturnValue({
401
423
  user: mockUser,
402
424
  selectedOrganisation: { id: 'org-123' },
@@ -404,6 +426,17 @@ describe('NavigationGuard Component', () => {
404
426
  supabase: {} as any
405
427
  });
406
428
 
429
+ // Mock useResolvedScope to return scope without event
430
+ mockUseResolvedScope.mockReturnValue({
431
+ resolvedScope: {
432
+ organisationId: 'org-123',
433
+ eventId: undefined,
434
+ appId: 'app-123'
435
+ },
436
+ isLoading: false,
437
+ error: null
438
+ });
439
+
407
440
  mockUseMultiplePermissions.mockReturnValue({
408
441
  results: { 'read:dashboard': true } as Record<string, boolean>,
409
442
  isLoading: false,
@@ -423,27 +456,34 @@ describe('NavigationGuard Component', () => {
423
456
 
424
457
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
425
458
  'user-123',
426
- expect.objectContaining({
459
+ {
427
460
  organisationId: 'org-123',
428
- eventId: undefined
429
- }),
461
+ eventId: undefined,
462
+ appId: 'app-123'
463
+ },
430
464
  ['read:dashboard'],
431
465
  true
432
466
  );
433
467
  });
434
468
 
435
- it('resolves scope from event context when organisation not available', async () => {
469
+ it('resolves scope from event context when organisation not available (event-required app)', async () => {
470
+ // For event-required apps, selectedOrganisation is null, org is derived from event
436
471
  mockUseUnifiedAuthFn.mockReturnValue({
437
472
  user: mockUser,
438
- selectedOrganisation: null,
473
+ selectedOrganisation: null, // Not available for event-required apps
439
474
  selectedEvent: { event_id: 'event-123' },
440
475
  supabase: {} as any
441
476
  });
442
477
 
443
- mockCreateScopeFromEvent.mockResolvedValue({
444
- organisationId: 'resolved-org',
445
- eventId: 'event-123',
446
- appId: 'resolved-app'
478
+ // Mock useResolvedScope to return scope resolved from event
479
+ mockUseResolvedScope.mockReturnValue({
480
+ resolvedScope: {
481
+ organisationId: 'resolved-org',
482
+ eventId: 'event-123',
483
+ appId: 'resolved-app'
484
+ },
485
+ isLoading: false,
486
+ error: null
447
487
  });
448
488
 
449
489
  mockUseMultiplePermissions.mockReturnValue({
@@ -463,13 +503,13 @@ describe('NavigationGuard Component', () => {
463
503
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
464
504
  }, { interval: 10 });
465
505
 
466
- expect(mockCreateScopeFromEvent).toHaveBeenCalledWith({}, 'event-123');
467
506
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
468
507
  'user-123',
469
- expect.objectContaining({
508
+ {
470
509
  organisationId: 'resolved-org',
471
- eventId: 'event-123'
472
- }),
510
+ eventId: 'event-123',
511
+ appId: 'resolved-app'
512
+ },
473
513
  ['read:dashboard'],
474
514
  true
475
515
  );
@@ -478,13 +518,29 @@ describe('NavigationGuard Component', () => {
478
518
  it('handles scope resolution errors', async () => {
479
519
  mockUseUnifiedAuthFn.mockReturnValue({
480
520
  user: mockUser,
481
- selectedOrganisationId: null,
482
- selectedEventId: 'event-123',
521
+ selectedOrganisation: null,
522
+ selectedEvent: { event_id: 'event-123' },
483
523
  supabase: {} as any
484
524
  });
485
525
 
486
526
  const error = new Error('Could not resolve organisation from event');
487
- mockCreateScopeFromEvent.mockRejectedValue(error);
527
+ // Component checks !effectiveScope first, which causes loading state
528
+ // When there's an error, effectiveScope is null, so component shows loading
529
+ // The component logic: if (isLoading || !effectiveScope || !hasChecked) return loading
530
+ // So even with an error, if effectiveScope is null, it shows loading
531
+ mockUseResolvedScope.mockReturnValue({
532
+ resolvedScope: null,
533
+ isLoading: false,
534
+ error
535
+ });
536
+
537
+ // Mock permissions to return quickly
538
+ mockUseMultiplePermissions.mockReturnValue({
539
+ results: {} as Record<string, boolean>,
540
+ isLoading: false,
541
+ error: null,
542
+ refetch: vi.fn()
543
+ });
488
544
 
489
545
  render(
490
546
  <NavigationGuard
@@ -495,19 +551,25 @@ describe('NavigationGuard Component', () => {
495
551
  </NavigationGuard>
496
552
  );
497
553
 
498
- await waitFor(() => {
499
- expect(screen.getByText('Checking...')).toBeInTheDocument();
500
- }, { interval: 10 });
554
+ // Component shows loading when effectiveScope is null (even with error)
555
+ // The component checks !effectiveScope before checking checkError
556
+ expect(screen.getByText('Checking...')).toBeInTheDocument();
501
557
  });
502
558
 
503
559
  it('handles missing context gracefully', async () => {
504
560
  mockUseUnifiedAuthFn.mockReturnValue({
505
561
  user: mockUser,
506
- selectedOrganisationId: null,
507
- selectedEventId: null,
562
+ selectedOrganisation: null,
563
+ selectedEvent: null,
508
564
  supabase: null
509
565
  });
510
566
 
567
+ mockUseResolvedScope.mockReturnValue({
568
+ resolvedScope: null,
569
+ isLoading: true,
570
+ error: null
571
+ });
572
+
511
573
  render(
512
574
  <NavigationGuard
513
575
  navigationItem={mockNavigationItem}
@@ -517,9 +579,7 @@ describe('NavigationGuard Component', () => {
517
579
  </NavigationGuard>
518
580
  );
519
581
 
520
- await waitFor(() => {
521
- expect(screen.getByText('Checking...')).toBeInTheDocument();
522
- }, { interval: 10 });
582
+ expect(screen.getByText('Checking...')).toBeInTheDocument();
523
583
  });
524
584
  });
525
585
 
@@ -764,10 +824,11 @@ describe('NavigationGuard Component', () => {
764
824
  // Should still check the first permission as representative
765
825
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
766
826
  'user-123',
767
- expect.objectContaining({
827
+ {
768
828
  organisationId: 'org-123',
769
- eventId: 'event-123'
770
- }),
829
+ eventId: 'event-123',
830
+ appId: 'app-123'
831
+ },
771
832
  ['read:dashboard'],
772
833
  true
773
834
  );
@@ -805,10 +866,11 @@ describe('NavigationGuard Component', () => {
805
866
 
806
867
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
807
868
  '',
808
- expect.objectContaining({
869
+ {
809
870
  organisationId: 'org-123',
810
- eventId: 'event-123'
811
- }),
871
+ eventId: 'event-123',
872
+ appId: 'app-123'
873
+ },
812
874
  ['read:dashboard'],
813
875
  true
814
876
  );
@@ -870,10 +932,11 @@ describe('NavigationGuard Component', () => {
870
932
 
871
933
  expect(mockUseMultiplePermissions).toHaveBeenCalledWith(
872
934
  'user-123',
873
- expect.objectContaining({
935
+ {
874
936
  organisationId: 'org-123',
875
- eventId: 'event-123'
876
- }),
937
+ eventId: 'event-123',
938
+ appId: 'app-123'
939
+ },
877
940
  ['read:settings'],
878
941
  true
879
942
  );
@@ -54,6 +54,18 @@ vi.mock('../../../providers/services/UnifiedAuthProvider', () => ({
54
54
  UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <div data-testid="unified-auth-provider">{children}</div>
55
55
  }));
56
56
 
57
+ // Mock useResolvedScope
58
+ const mockUseResolvedScopeFn = vi.fn();
59
+ vi.mock('../../hooks/useResolvedScope', () => ({
60
+ useResolvedScope: () => mockUseResolvedScopeFn(),
61
+ }));
62
+
63
+ // Mock useCan hook
64
+ const mockUseCanFn = vi.fn();
65
+ vi.mock('../../hooks', () => ({
66
+ useCan: (...args: any[]) => mockUseCanFn(...args),
67
+ }));
68
+
57
69
  // Test data
58
70
  const mockUser = {
59
71
  id: 'user-123',
@@ -144,10 +156,29 @@ describe('NavigationProvider', () => {
144
156
 
145
157
  // Set up default mock with organisation context
146
158
  mockUseUnifiedAuthFn.mockReturnValue({
147
- ...mockUser,
148
- selectedOrganisationId: 'org-456',
149
- selectedEventId: 'event-789'
159
+ user: {
160
+ id: 'user-123',
161
+ email: 'test@example.com',
162
+ },
163
+ selectedOrganisation: { id: 'org-456' },
164
+ selectedEvent: { event_id: 'event-789' },
165
+ supabase: {} as any,
166
+ });
167
+
168
+ // Mock useResolvedScope to return resolved scope
169
+ mockUseResolvedScopeFn.mockReturnValue({
170
+ resolvedScope: mockScope,
171
+ isLoading: false,
172
+ error: null,
150
173
  });
174
+
175
+ // Mock useCan to return true by default
176
+ // Note: useCan is called inside hasNavigationPermission callback, so it needs to return a value every time
177
+ mockUseCanFn.mockImplementation(() => ({
178
+ can: true,
179
+ isLoading: false,
180
+ error: null,
181
+ }));
151
182
  });
152
183
 
153
184
  afterEach(() => {
@@ -184,7 +215,8 @@ describe('NavigationProvider', () => {
184
215
  </TestWrapper>
185
216
  );
186
217
 
187
- expect(screen.getByTestId('navigation-permission')).toHaveTextContent('false');
218
+ // When enabled and useCan returns true, permission should be granted
219
+ expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
188
220
  });
189
221
 
190
222
  it('should return empty permissions initially', () => {
@@ -214,7 +246,8 @@ describe('NavigationProvider', () => {
214
246
  </TestWrapper>
215
247
  );
216
248
 
217
- expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('0');
249
+ // When useCan returns true for all items, all items should pass the filter
250
+ expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('3');
218
251
  });
219
252
  });
220
253
 
@@ -269,7 +302,8 @@ describe('NavigationProvider', () => {
269
302
  </TestWrapper>
270
303
  );
271
304
 
272
- expect(screen.getByTestId('navigation-permission')).toHaveTextContent('false');
305
+ // When user is authenticated and useCan returns true, permission should be granted
306
+ expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
273
307
  });
274
308
 
275
309
  it('should deny navigation permission when user is not authenticated', () => {
@@ -319,7 +353,8 @@ describe('NavigationProvider', () => {
319
353
  </TestWrapper>
320
354
  );
321
355
 
322
- expect(screen.getByTestId('navigation-permission')).toHaveTextContent('false');
356
+ // When disabled, hasNavigationPermission returns true (allows all)
357
+ expect(screen.getByTestId('navigation-permission')).toHaveTextContent('true');
323
358
  });
324
359
  });
325
360
 
@@ -335,6 +370,7 @@ describe('NavigationProvider', () => {
335
370
  {item.label}
336
371
  </div>
337
372
  ))}
373
+ <div data-testid="filtered-count">{filteredItems.length}</div>
338
374
  </div>
339
375
  );
340
376
  };
@@ -345,9 +381,8 @@ describe('NavigationProvider', () => {
345
381
  </TestWrapper>
346
382
  );
347
383
 
348
- // The component doesn't render individual items when mock is not working
349
- // This test verifies the component renders without crashing
350
- expect(screen.getByTestId('filtered-items')).toBeInTheDocument();
384
+ // When useCan returns true for all items, all items should pass the filter
385
+ expect(screen.getByTestId('filtered-count')).toHaveTextContent('3');
351
386
  });
352
387
 
353
388
  it('should return all items when disabled', () => {
@@ -371,7 +406,8 @@ describe('NavigationProvider', () => {
371
406
  </TestWrapper>
372
407
  );
373
408
 
374
- expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('0');
409
+ // When disabled, all items should be returned (no filtering)
410
+ expect(screen.getByTestId('filtered-items-count')).toHaveTextContent('3');
375
411
  });
376
412
  });
377
413
 
@@ -683,7 +719,8 @@ describe('NavigationProvider', () => {
683
719
  </TestWrapper>
684
720
  );
685
721
 
686
- expect(screen.getByTestId('no-meta-permission')).toHaveTextContent('false');
722
+ // Item has permissions defined, so useCan is called and returns true (mocked)
723
+ expect(screen.getByTestId('no-meta-permission')).toHaveTextContent('true');
687
724
  });
688
725
  });
689
726
  });