@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
@@ -12,7 +12,8 @@ import {
12
12
  UUID,
13
13
  Scope,
14
14
  Permission,
15
- PermissionMap
15
+ PermissionMap,
16
+ OrganisationContextRequiredError
16
17
  } from '../types';
17
18
  import { AccessLevel as AccessLevelType } from '../types';
18
19
  import {
@@ -23,6 +24,7 @@ import {
23
24
  } from '../api';
24
25
  import { getRBACLogger } from '../config';
25
26
  import { scopeEqual } from '../utils/deep-equal';
27
+ import { useAppConfig } from '../../hooks/useAppConfig';
26
28
 
27
29
  /**
28
30
  * Hook to get user's permissions in a scope
@@ -97,28 +99,30 @@ export function usePermissions(
97
99
  if (error?.message === 'Organisation context is required for permission checks') {
98
100
  setError(null);
99
101
  }
100
- }, [userId, organisationId, error]);
101
-
102
- // CRITICAL: Detect parameter changes imperatively and trigger fetch
103
- // This bypasses React's useEffect dependency tracking which is failing to detect appId changes
104
- const paramsChanged =
105
- prevValuesRef.current.userId !== userId ||
106
- prevValuesRef.current.organisationId !== organisationId ||
107
- prevValuesRef.current.eventId !== eventId ||
108
- prevValuesRef.current.appId !== appId;
109
-
110
- if (paramsChanged) {
111
- // Only log significant changes (appId changes are most important)
112
- if (prevValuesRef.current.appId !== appId) {
113
- logger.debug('[usePermissions] AppId changed - triggering fetch', {
114
- prevAppId: prevValuesRef.current.appId,
115
- newAppId: appId
116
- });
102
+ }, [userId, organisationId, error, orgId]);
103
+
104
+ // CRITICAL: Detect parameter changes and trigger fetch
105
+ // Moved to useEffect to prevent render-time state updates that could cause render loops
106
+ useEffect(() => {
107
+ const paramsChanged =
108
+ prevValuesRef.current.userId !== userId ||
109
+ prevValuesRef.current.organisationId !== organisationId ||
110
+ prevValuesRef.current.eventId !== eventId ||
111
+ prevValuesRef.current.appId !== appId;
112
+
113
+ if (paramsChanged) {
114
+ // Only log significant changes (appId changes are most important)
115
+ if (prevValuesRef.current.appId !== appId) {
116
+ logger.debug('[usePermissions] AppId changed - triggering fetch', {
117
+ prevAppId: prevValuesRef.current.appId,
118
+ newAppId: appId
119
+ });
120
+ }
121
+ prevValuesRef.current = { userId, organisationId, eventId, appId };
122
+ // Increment counter to force fetch useEffect to run
123
+ setFetchTrigger(prev => prev + 1);
117
124
  }
118
- prevValuesRef.current = { userId, organisationId, eventId, appId };
119
- // Increment counter to force useEffect to run
120
- setFetchTrigger(prev => prev + 1);
121
- }
125
+ }, [userId, organisationId, eventId, appId, logger]);
122
126
 
123
127
  useEffect(() => {
124
128
  const fetchPermissions = async () => {
@@ -283,6 +287,7 @@ export function usePermissions(
283
287
  * @param permission - Permission to check
284
288
  * @param pageId - Optional page ID
285
289
  * @param useCache - Whether to use cached results
290
+ * @param appName - Optional app name (for PORTAL/ADMIN special case)
286
291
  * @returns Permission check state and methods
287
292
  *
288
293
  * @example
@@ -302,7 +307,8 @@ export function useCan(
302
307
  scope: Scope,
303
308
  permission: Permission,
304
309
  pageId?: UUID,
305
- useCache: boolean = true
310
+ useCache: boolean = true,
311
+ appName?: string
306
312
  ) {
307
313
  const [can, setCan] = useState<boolean>(false);
308
314
  const [isLoading, setIsLoading] = useState(true);
@@ -315,8 +321,12 @@ export function useCan(
315
321
  const appId = isValidScope ? scope.appId : undefined;
316
322
 
317
323
  // Add timeout for missing organisation context (3 seconds)
324
+ // Only apply timeout for resource-level permissions, not page-level (which can handle null orgs)
318
325
  useEffect(() => {
319
- if (!isValidScope || !organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {
326
+ const isPagePermission = permission.includes(':page.') || !!pageId;
327
+ const requiresOrgId = !isPagePermission;
328
+
329
+ if (requiresOrgId && (!isValidScope || !organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
320
330
  const timeoutId = setTimeout(() => {
321
331
  setError(new Error('Organisation context is required for permission checks'));
322
332
  setIsLoading(false);
@@ -329,7 +339,7 @@ export function useCan(
329
339
  if (error?.message === 'Organisation context is required for permission checks') {
330
340
  setError(null);
331
341
  }
332
- }, [isValidScope, organisationId, error]);
342
+ }, [isValidScope, organisationId, error, permission, pageId]);
333
343
 
334
344
  // Use refs to track the last values to prevent unnecessary re-runs
335
345
  const lastUserIdRef = useRef<UUID | null>(null);
@@ -388,30 +398,50 @@ export function useCan(
388
398
  return;
389
399
  }
390
400
 
391
- // Don't check permissions if scope is invalid (e.g., organisationId is null/empty)
392
- // Wait for organisation context to resolve
393
- if (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {
401
+ // For page-level permissions, allow undefined/null organisationId (database function handles it)
402
+ // For resource-level permissions, organisationId is required
403
+ const isPagePermission = permission.includes(':page.') || !!pageId;
404
+ const requiresOrgId = !isPagePermission;
405
+
406
+ // Check if pageId is a pageName (not a UUID) - if so, we need appId to resolve it
407
+ const isPageName = pageId && typeof pageId === 'string' && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(pageId);
408
+ const needsAppIdForPageName = isPagePermission && isPageName;
409
+
410
+ // Don't check permissions if scope is invalid and orgId is required
411
+ // Wait for organisation context to resolve (unless it's a page permission that can handle null orgs)
412
+ if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
394
413
  setIsLoading(true);
395
414
  setCan(false);
396
415
  setError(null);
397
416
  // Timeout is handled in separate useEffect (Phase 1.4)
398
417
  return;
399
418
  }
419
+
420
+ // For page-level permissions with pageName (not UUID), we need appId to resolve the pageName to pageId
421
+ // Wait for appId to be available before checking permissions
422
+ if (needsAppIdForPageName && (!appId || appId === null || (typeof appId === 'string' && appId.trim() === ''))) {
423
+ setIsLoading(true);
424
+ setCan(false);
425
+ setError(null);
426
+ // Will re-run when appId becomes available (via scope change detection)
427
+ return;
428
+ }
400
429
 
401
430
  try {
402
431
  setIsLoading(true);
403
432
  setError(null);
404
433
 
405
434
  // Create a valid scope object for the API call
435
+ // For page-level permissions, organisationId can be undefined (database handles it)
406
436
  const validScope: Scope = {
407
- organisationId,
437
+ ...(organisationId ? { organisationId } : {}),
408
438
  ...(eventId ? { eventId } : {}),
409
439
  ...(appId ? { appId } : {})
410
440
  };
411
441
 
412
442
  const result = useCache
413
- ? await isPermittedCached({ userId, scope: validScope, permission, pageId })
414
- : await isPermitted({ userId, scope: validScope, permission, pageId });
443
+ ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
444
+ : await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName);
415
445
 
416
446
  setCan(result);
417
447
  } catch (err) {
@@ -426,7 +456,7 @@ export function useCan(
426
456
 
427
457
  checkPermission();
428
458
  }
429
- }, [userId, stableScope, permission, pageId, useCache]);
459
+ }, [userId, stableScope, permission, pageId, useCache, appName]);
430
460
 
431
461
  const refetch = useCallback(async () => {
432
462
  if (!userId) {
@@ -443,8 +473,13 @@ export function useCan(
443
473
  return;
444
474
  }
445
475
 
446
- // Don't check permissions if scope is invalid (e.g., organisationId is null/empty)
447
- if (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {
476
+ // For page-level permissions, allow undefined/null organisationId (database function handles it)
477
+ // For resource-level permissions, organisationId is required
478
+ const isPagePermission = permission.includes(':page.') || !!pageId;
479
+ const requiresOrgId = !isPagePermission;
480
+
481
+ // Don't check permissions if scope is invalid and orgId is required
482
+ if (requiresOrgId && (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === ''))) {
448
483
  setCan(false);
449
484
  setIsLoading(true);
450
485
  setError(null);
@@ -456,15 +491,16 @@ export function useCan(
456
491
  setError(null);
457
492
 
458
493
  // Create a valid scope object for the API call
494
+ // For page-level permissions, organisationId can be undefined (database handles it)
459
495
  const validScope: Scope = {
460
- organisationId,
496
+ ...(organisationId ? { organisationId } : {}),
461
497
  ...(eventId ? { eventId } : {}),
462
498
  ...(appId ? { appId } : {})
463
499
  };
464
500
 
465
501
  const result = useCache
466
- ? await isPermittedCached({ userId, scope: validScope, permission, pageId })
467
- : await isPermitted({ userId, scope: validScope, permission, pageId });
502
+ ? await isPermittedCached({ userId, scope: validScope, permission, pageId }, undefined, appName)
503
+ : await isPermitted({ userId, scope: validScope, permission, pageId }, undefined, appName);
468
504
 
469
505
  setCan(result);
470
506
  } catch (err) {
@@ -473,7 +509,7 @@ export function useCan(
473
509
  } finally {
474
510
  setIsLoading(false);
475
511
  }
476
- }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache]);
512
+ }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache, appName]);
477
513
 
478
514
  // Memoize the return object to prevent unnecessary re-renders
479
515
  return useMemo(() => ({
@@ -518,6 +554,15 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
518
554
  const [isLoading, setIsLoading] = useState(true);
519
555
  const [error, setError] = useState<Error | null>(null);
520
556
 
557
+ // Get appName from context if available (safely handles missing context)
558
+ let appName: string | undefined;
559
+ try {
560
+ const { appName: contextAppName } = useAppConfig();
561
+ appName = contextAppName;
562
+ } catch {
563
+ // Not available, will use undefined
564
+ }
565
+
521
566
  const fetchAccessLevel = useCallback(async () => {
522
567
  if (!userId) {
523
568
  setAccessLevel('viewer');
@@ -529,15 +574,37 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
529
574
  setIsLoading(true);
530
575
  setError(null);
531
576
 
532
- const level = await getAccessLevel({ userId, scope });
577
+ // Check super admin status first - super admins bypass context requirements
578
+ // This allows super admins to check their access level without organisation context
579
+ const { isSuperAdmin: checkSuperAdmin } = await import('../api');
580
+ const isSuperAdminUser = await checkSuperAdmin(userId);
581
+
582
+ if (isSuperAdminUser) {
583
+ setAccessLevel('super');
584
+ setIsLoading(false);
585
+ return;
586
+ }
587
+
588
+ // Early validation: check if scope has required context
589
+ // PORTAL/ADMIN apps allow both contexts to be optional
590
+ if (appName !== 'PORTAL' && appName !== 'ADMIN' && !scope.organisationId && !scope.eventId) {
591
+ const orgError = new OrganisationContextRequiredError();
592
+ setError(orgError);
593
+ setAccessLevel('viewer');
594
+ setIsLoading(false);
595
+ return;
596
+ }
597
+
598
+ const level = await getAccessLevel({ userId, scope }, null, appName);
533
599
  setAccessLevel(level);
534
600
  } catch (err) {
535
- setError(err instanceof Error ? err : new Error('Failed to fetch access level'));
601
+ const error = err instanceof Error ? err : new Error('Failed to fetch access level');
602
+ setError(error);
536
603
  setAccessLevel('viewer');
537
604
  } finally {
538
605
  setIsLoading(false);
539
606
  }
540
- }, [userId, scope.organisationId, scope.eventId, scope.appId]);
607
+ }, [userId, scope.organisationId, scope.eventId, scope.appId, appName]);
541
608
 
542
609
  useEffect(() => {
543
610
  fetchAccessLevel();
@@ -108,10 +108,18 @@ describe('useRBAC', () => {
108
108
 
109
109
  expect(mockResolveAppContext).toHaveBeenCalledWith({ userId: 'user-1', appName: 'test-app' });
110
110
  expect(mockGetPermissionMap).toHaveBeenCalledWith(
111
- expect.objectContaining({
111
+ {
112
112
  userId: 'user-1',
113
- scope: expect.objectContaining({ organisationId: 'org-1' }),
114
- }),
113
+ scope: {
114
+ organisationId: 'org-1',
115
+ eventId: undefined,
116
+ appId: 'app-123'
117
+ }
118
+ },
119
+ {
120
+ requires_event: false,
121
+ },
122
+ 'test-app'
115
123
  );
116
124
  });
117
125
 
@@ -21,6 +21,7 @@ import {
21
21
  getRoleContext,
22
22
  } from '../api';
23
23
  import { getRBACLogger } from '../config';
24
+ import { ContextValidator } from '../utils/contextValidator';
24
25
  import type {
25
26
  UserRBACContext,
26
27
  GlobalRole,
@@ -55,8 +56,10 @@ export function useRBAC(pageId?: string): UserRBACContext {
55
56
  const {
56
57
  user,
57
58
  session,
59
+ supabase,
58
60
  appName,
59
61
  appConfig,
62
+ appId: contextAppId,
60
63
  selectedOrganisation,
61
64
  isContextReady: orgContextReady,
62
65
  organisationLoading: orgLoading,
@@ -64,11 +67,6 @@ export function useRBAC(pageId?: string): UserRBACContext {
64
67
  eventLoading
65
68
  } = useUnifiedAuth();
66
69
 
67
- // Check if app requires event context
68
- // IMPORTANT: If appConfig is null initially, default to true (safer for event-based apps)
69
- // This prevents premature loading when appConfig hasn't loaded yet
70
- const requiresEvent = appConfig?.requires_event ?? (appConfig === null ? true : false);
71
-
72
70
  // Removed excessive logging - hook initialization logged only on first mount or significant changes
73
71
 
74
72
  const [globalRole, setGlobalRole] = useState<GlobalRole | null>(null);
@@ -95,55 +93,79 @@ export function useRBAC(pageId?: string): UserRBACContext {
95
93
  return;
96
94
  }
97
95
 
98
- // Wait for organisation context to be ready before loading RBAC
99
- // This is critical - without organisation ID, RPC calls can't resolve permissions
100
- if (orgLoading || !orgContextReady || !selectedOrganisation?.id) {
96
+ // Build initial scope from available context
97
+ // For event-required apps: use organisation_id from selectedEvent if available (faster than deriving)
98
+ // For org-required apps: use selectedOrganisation.id
99
+ const initialScope: Scope = {
100
+ organisationId: appConfig?.requires_event
101
+ ? (selectedEvent?.organisation_id || selectedOrganisation?.id)
102
+ : selectedOrganisation?.id,
103
+ eventId: selectedEvent?.event_id || undefined,
104
+ appId: undefined
105
+ };
106
+
107
+ // Check if context is ready using ContextValidator
108
+ const contextReady = ContextValidator.isContextReady(
109
+ initialScope,
110
+ appConfig,
111
+ appName,
112
+ !!selectedEvent,
113
+ !!selectedOrganisation
114
+ );
115
+
116
+ // PORTAL/ADMIN special case: context is always ready
117
+ if (appName !== 'PORTAL' && appName !== 'ADMIN' && !contextReady) {
118
+ // Wait for appropriate context based on app config
101
119
  setIsLoading(true);
102
120
  return;
103
121
  }
104
122
 
105
- // For event-based apps, wait for event context to be ready before loading RBAC
106
- // This prevents NetworkError when RPC calls are made before event context is available
107
- if (requiresEvent) {
108
- if (eventLoading || !selectedEvent) {
109
- // Event context not ready yet - don't load RBAC yet
110
- // This prevents premature RPC calls that can cause NetworkError
111
- // Set loading state so React knows we're waiting
112
- setIsLoading(true);
113
- return;
114
- }
115
- }
116
-
117
123
  setIsLoading(true);
118
124
  setError(null);
119
125
 
120
126
  // Only log at debug level - loading RBAC context is normal operation
121
- // Changed from warn to debug to reduce console noise
122
127
  logger.debug('[useRBAC] Loading RBAC context', {
123
128
  appName,
124
- requiresEvent,
129
+ appConfig,
125
130
  hasSelectedEvent: !!selectedEvent,
126
131
  selectedEventId: selectedEvent?.event_id,
127
132
  organisationId: selectedOrganisation?.id
128
133
  });
129
134
 
130
135
  try {
131
- let appId: UUID | undefined;
132
- if (appName) {
136
+ let appId: UUID | undefined = contextAppId; // Use contextAppId as default (already resolved in UnifiedAuthProvider)
137
+
138
+ if (appName && !appId) {
133
139
  // Wrap RPC call in try-catch to handle NetworkError gracefully
134
140
  try {
135
141
  const resolved = await resolveAppContext({ userId: user.id as UUID, appName });
136
- if (!resolved || !resolved.hasAccess) {
142
+ // For PORTAL/ADMIN apps, allow access even if hasAccess is false (users can view their own profile, super admins have global access)
143
+ if (!resolved) {
144
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
145
+ // For PORTAL/ADMIN, try to get appId directly from database
146
+ logger.debug(`[useRBAC] ${appName} app context not resolved, attempting direct lookup`);
147
+ try {
148
+ const { getAppConfigByName } = await import('../api');
149
+ const config = await getAppConfigByName(appName);
150
+ // We can't get appId from config, but that's OK - use contextAppId or proceed without
151
+ logger.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
152
+ } catch (err) {
153
+ logger.debug(`[useRBAC] ${appName} app - proceeding without appId for page-level permissions`);
154
+ }
155
+ } else {
156
+ throw new Error(`User does not have access to app "${appName}"`);
157
+ }
158
+ } else if (!resolved.hasAccess && appName !== 'PORTAL' && appName !== 'ADMIN') {
137
159
  throw new Error(`User does not have access to app "${appName}"`);
160
+ } else {
161
+ appId = resolved.appId;
138
162
  }
139
- appId = resolved.appId;
140
163
  } catch (rpcError: any) {
141
164
  // Handle NetworkError - might be due to timing issue
142
165
  if (rpcError?.message?.includes('NetworkError') || rpcError?.message?.includes('fetch')) {
143
166
  logger.warn('[useRBAC] NetworkError resolving app context - may be timing issue, will retry when context is ready', {
144
167
  appName,
145
168
  error: rpcError.message,
146
- requiresEvent,
147
169
  eventLoading,
148
170
  hasSelectedEvent: !!selectedEvent
149
171
  });
@@ -151,23 +173,44 @@ export function useRBAC(pageId?: string): UserRBACContext {
151
173
  setIsLoading(false);
152
174
  return;
153
175
  }
154
- // Re-throw other errors
155
- throw rpcError;
176
+ // For PORTAL/ADMIN, allow proceeding without app access check (for profile pages, super admin access)
177
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
178
+ logger.debug(`[useRBAC] ${appName} app - allowing access despite app context resolution failure`);
179
+ // appId will use contextAppId or be undefined, which is OK for PORTAL/ADMIN page-level permissions
180
+ } else {
181
+ // Re-throw other errors for non-PORTAL apps
182
+ throw rpcError;
183
+ }
156
184
  }
157
185
  }
158
186
 
187
+ // Update scope with appId (use contextAppId as fallback for PORTAL)
159
188
  const scope: Scope = {
160
- organisationId: selectedOrganisation?.id,
161
- eventId: selectedEvent?.event_id || undefined,
162
- appId,
189
+ ...initialScope,
190
+ appId: appId || contextAppId,
163
191
  };
164
192
 
165
- setCurrentScope(scope);
193
+ // Resolve required context using ContextValidator
194
+ // Pass supabase client to allow deriving organisation from event for event-required apps
195
+ const validation = await ContextValidator.resolveRequiredContext(
196
+ scope,
197
+ appConfig,
198
+ appName,
199
+ supabase || null
200
+ );
201
+
202
+ if (!validation.isValid || !validation.resolvedScope) {
203
+ throw validation.error || new Error('Context validation failed');
204
+ }
205
+
206
+ const resolvedScope = validation.resolvedScope;
207
+ setCurrentScope(resolvedScope);
166
208
 
209
+ // Pass appConfig and appName to API calls for context validation
167
210
  const [map, roleContext, accessLevel] = await Promise.all([
168
- getPermissionMap({ userId: user.id as UUID, scope }),
169
- getRoleContext({ userId: user.id as UUID, scope }),
170
- getAccessLevel({ userId: user.id as UUID, scope }),
211
+ getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
212
+ getRoleContext({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
213
+ getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }, appConfig, appName),
171
214
  ]);
172
215
 
173
216
  setPermissionMap(map);
@@ -180,8 +223,8 @@ export function useRBAC(pageId?: string): UserRBACContext {
180
223
  if (permissionCount === 0) {
181
224
  logger.warn('[useRBAC] RBAC context loaded but returned 0 permissions', {
182
225
  appName,
183
- organisationId: selectedOrganisation.id,
184
- eventId: selectedEvent?.event_id
226
+ organisationId: resolvedScope.organisationId,
227
+ eventId: resolvedScope.eventId
185
228
  });
186
229
  }
187
230
  } catch (err) {
@@ -192,7 +235,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
192
235
  } finally {
193
236
  setIsLoading(false);
194
237
  }
195
- }, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, requiresEvent, eventLoading, appConfig, orgContextReady, orgLoading]);
238
+ }, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, appConfig, orgContextReady, orgLoading]);
196
239
 
197
240
  const hasGlobalPermission = useCallback(
198
241
  (permission: string): boolean => {
@@ -221,7 +264,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
221
264
 
222
265
  useEffect(() => {
223
266
  loadRBACContext();
224
- }, [loadRBACContext, appName, requiresEvent, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
267
+ }, [loadRBACContext, appName, appConfig, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
225
268
 
226
269
  return {
227
270
  user,