@jmruthers/pace-core 0.5.189 → 0.5.191

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 (438) 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-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
  4. package/dist/{DataTable-GUFUNZ3N.js → DataTable-WKRZD47S.js} +8 -8
  5. package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +84 -25
  6. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  7. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-FTSG5XH7.js} +4 -2
  8. package/dist/{api-YP7XD5L6.js → api-IHKALJZD.js} +4 -2
  9. package/dist/{chunk-VGZZXKBR.js → chunk-6LTQQAT6.js} +351 -157
  10. package/dist/chunk-6LTQQAT6.js.map +1 -0
  11. package/dist/{chunk-MX64ZF6I.js → chunk-6TQDD426.js} +15 -15
  12. package/dist/chunk-6TQDD426.js.map +1 -0
  13. package/dist/{chunk-YHCN776L.js → chunk-G37KK66H.js} +2 -75
  14. package/dist/chunk-G37KK66H.js.map +1 -0
  15. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  16. package/dist/chunk-HW3OVDUF.js.map +1 -0
  17. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  18. package/dist/chunk-I7PSE6JW.js.map +1 -0
  19. package/dist/{chunk-IM4QE42D.js → chunk-LOMZXPSN.js} +141 -326
  20. package/dist/chunk-LOMZXPSN.js.map +1 -0
  21. package/dist/chunk-OETXORNB.js +614 -0
  22. package/dist/chunk-OETXORNB.js.map +1 -0
  23. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  24. package/dist/{chunk-HEHYGYOX.js → chunk-ROXMHMY2.js} +403 -46
  25. package/dist/chunk-ROXMHMY2.js.map +1 -0
  26. package/dist/{chunk-2UUZZJFT.js → chunk-ULHIJK66.js} +228 -177
  27. package/dist/{chunk-2UUZZJFT.js.map → chunk-ULHIJK66.js.map} +1 -1
  28. package/dist/{chunk-YGPFYGA6.js → chunk-VKB2CO4Z.js} +838 -503
  29. package/dist/chunk-VKB2CO4Z.js.map +1 -0
  30. package/dist/{chunk-3GOZZZYH.js → chunk-VRGWKHDB.js} +238 -301
  31. package/dist/chunk-VRGWKHDB.js.map +1 -0
  32. package/dist/{chunk-UCQSRW7Z.js → chunk-XNYQOL3Z.js} +431 -384
  33. package/dist/chunk-XNYQOL3Z.js.map +1 -0
  34. package/dist/{chunk-DDM4CCYT.js → chunk-XYXSXPUK.js} +79 -59
  35. package/dist/chunk-XYXSXPUK.js.map +1 -0
  36. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  37. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  38. package/dist/components.d.ts +5 -6
  39. package/dist/components.js +19 -19
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
  42. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  43. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  44. package/dist/hooks.d.ts +20 -15
  45. package/dist/hooks.js +14 -8
  46. package/dist/hooks.js.map +1 -1
  47. package/dist/index.d.ts +17 -15
  48. package/dist/index.js +86 -81
  49. package/dist/index.js.map +1 -1
  50. package/dist/providers.d.ts +3 -3
  51. package/dist/providers.js +3 -1
  52. package/dist/rbac/index.d.ts +77 -13
  53. package/dist/rbac/index.js +12 -9
  54. package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
  55. package/dist/types.d.ts +3 -3
  56. package/dist/types.js +1 -1
  57. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +17 -10
  58. package/dist/utils.d.ts +8 -8
  59. package/dist/utils.js +16 -16
  60. package/docs/README.md +2 -2
  61. package/docs/api/classes/ColumnFactory.md +1 -1
  62. package/docs/api/classes/ErrorBoundary.md +1 -1
  63. package/docs/api/classes/InvalidScopeError.md +2 -2
  64. package/docs/api/classes/Logger.md +1 -1
  65. package/docs/api/classes/MissingUserContextError.md +2 -2
  66. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  67. package/docs/api/classes/PermissionDeniedError.md +1 -1
  68. package/docs/api/classes/RBACAuditManager.md +2 -2
  69. package/docs/api/classes/RBACCache.md +1 -1
  70. package/docs/api/classes/RBACEngine.md +5 -5
  71. package/docs/api/classes/RBACError.md +1 -1
  72. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  73. package/docs/api/classes/SecureSupabaseClient.md +25 -20
  74. package/docs/api/classes/StorageUtils.md +7 -4
  75. package/docs/api/enums/FileCategory.md +1 -1
  76. package/docs/api/enums/LogLevel.md +1 -1
  77. package/docs/api/enums/RBACErrorCode.md +1 -1
  78. package/docs/api/enums/RPCFunction.md +1 -1
  79. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  80. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +1 -1
  82. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  83. package/docs/api/interfaces/AvatarProps.md +1 -1
  84. package/docs/api/interfaces/BadgeProps.md +1 -1
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CalendarProps.md +20 -6
  87. package/docs/api/interfaces/CardProps.md +1 -1
  88. package/docs/api/interfaces/ColorPalette.md +1 -1
  89. package/docs/api/interfaces/ColorShade.md +1 -1
  90. package/docs/api/interfaces/ComplianceResult.md +1 -1
  91. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  92. package/docs/api/interfaces/DataRecord.md +1 -1
  93. package/docs/api/interfaces/DataTableAction.md +1 -1
  94. package/docs/api/interfaces/DataTableColumn.md +1 -1
  95. package/docs/api/interfaces/DataTableProps.md +1 -1
  96. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  97. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  98. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  99. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  100. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  101. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  102. package/docs/api/interfaces/ExportColumn.md +1 -1
  103. package/docs/api/interfaces/ExportOptions.md +1 -1
  104. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  105. package/docs/api/interfaces/FileMetadata.md +1 -1
  106. package/docs/api/interfaces/FileReference.md +2 -2
  107. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  108. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  109. package/docs/api/interfaces/FileUploadProps.md +30 -19
  110. package/docs/api/interfaces/FooterProps.md +1 -1
  111. package/docs/api/interfaces/FormFieldProps.md +1 -1
  112. package/docs/api/interfaces/FormProps.md +1 -1
  113. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  114. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  115. package/docs/api/interfaces/InputProps.md +1 -1
  116. package/docs/api/interfaces/LabelProps.md +1 -1
  117. package/docs/api/interfaces/LoggerConfig.md +1 -1
  118. package/docs/api/interfaces/LoginFormProps.md +1 -1
  119. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  120. package/docs/api/interfaces/NavigationContextType.md +9 -9
  121. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  122. package/docs/api/interfaces/NavigationItem.md +1 -1
  123. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  124. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  125. package/docs/api/interfaces/Organisation.md +1 -1
  126. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  127. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  128. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  129. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  130. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  131. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  132. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  133. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  134. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  135. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  136. package/docs/api/interfaces/PaletteData.md +1 -1
  137. package/docs/api/interfaces/ParsedAddress.md +2 -2
  138. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  139. package/docs/api/interfaces/ProgressProps.md +3 -11
  140. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  141. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  144. package/docs/api/interfaces/QuickFix.md +1 -1
  145. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  147. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  149. package/docs/api/interfaces/RBACConfig.md +2 -2
  150. package/docs/api/interfaces/RBACContext.md +1 -1
  151. package/docs/api/interfaces/RBACLogger.md +1 -1
  152. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  153. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  154. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  156. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  157. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  158. package/docs/api/interfaces/RBACResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  161. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  162. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  163. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  164. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  165. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  167. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  168. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  169. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  170. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  171. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  172. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  173. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  174. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  175. package/docs/api/interfaces/RouteConfig.md +10 -10
  176. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  177. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  178. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  179. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  180. package/docs/api/interfaces/SetupIssue.md +1 -1
  181. package/docs/api/interfaces/StorageConfig.md +4 -4
  182. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  183. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  184. package/docs/api/interfaces/StorageListOptions.md +22 -9
  185. package/docs/api/interfaces/StorageListResult.md +4 -4
  186. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  187. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  188. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  189. package/docs/api/interfaces/StyleImport.md +1 -1
  190. package/docs/api/interfaces/SwitchProps.md +1 -1
  191. package/docs/api/interfaces/TabsContentProps.md +1 -1
  192. package/docs/api/interfaces/TabsListProps.md +1 -1
  193. package/docs/api/interfaces/TabsProps.md +1 -1
  194. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  195. package/docs/api/interfaces/TextareaProps.md +1 -1
  196. package/docs/api/interfaces/ToastActionElement.md +1 -1
  197. package/docs/api/interfaces/ToastProps.md +1 -1
  198. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  199. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  200. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  201. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  202. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  205. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  208. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  209. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  210. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  211. package/docs/api/interfaces/UseResolvedScopeOptions.md +5 -5
  212. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  213. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  214. package/docs/api/interfaces/UserEventAccess.md +11 -11
  215. package/docs/api/interfaces/UserMenuProps.md +1 -1
  216. package/docs/api/interfaces/UserProfile.md +1 -1
  217. package/docs/api/modules.md +165 -106
  218. package/docs/api-reference/components.md +15 -7
  219. package/docs/api-reference/providers.md +2 -2
  220. package/docs/api-reference/rpc-functions.md +1 -0
  221. package/docs/best-practices/README.md +1 -1
  222. package/docs/best-practices/deployment.md +8 -8
  223. package/docs/getting-started/examples/README.md +2 -2
  224. package/docs/getting-started/installation-guide.md +4 -4
  225. package/docs/getting-started/quick-start.md +3 -3
  226. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  227. package/docs/migration/README.md +18 -0
  228. package/docs/migration/database-changes-december-2025.md +767 -0
  229. package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
  230. package/docs/rbac/compliance/compliance-guide.md +2 -2
  231. package/docs/rbac/event-based-apps.md +2 -2
  232. package/docs/rbac/getting-started.md +2 -2
  233. package/docs/rbac/quick-start.md +2 -2
  234. package/docs/security/README.md +4 -4
  235. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  236. package/docs/troubleshooting/README.md +2 -2
  237. package/docs/troubleshooting/migration.md +3 -3
  238. package/package.json +1 -3
  239. package/scripts/check-pace-core-compliance.cjs +1 -1
  240. package/scripts/check-pace-core-compliance.js +1 -1
  241. package/src/__tests__/fixtures/supabase.ts +301 -0
  242. package/src/__tests__/public-recipe-view.test.ts +19 -19
  243. package/src/__tests__/rls-policies.test.ts +210 -74
  244. package/src/components/AddressField/AddressField.test.tsx +42 -0
  245. package/src/components/AddressField/AddressField.tsx +71 -60
  246. package/src/components/AddressField/README.md +7 -6
  247. package/src/components/Alert/Alert.test.tsx +50 -10
  248. package/src/components/Alert/Alert.tsx +5 -3
  249. package/src/components/Avatar/Avatar.test.tsx +95 -43
  250. package/src/components/Avatar/Avatar.tsx +16 -16
  251. package/src/components/Button/Button.test.tsx +2 -1
  252. package/src/components/Button/Button.tsx +3 -3
  253. package/src/components/Calendar/Calendar.test.tsx +53 -37
  254. package/src/components/Calendar/Calendar.tsx +409 -82
  255. package/src/components/Card/Card.test.tsx +7 -4
  256. package/src/components/Card/Card.tsx +3 -6
  257. package/src/components/Checkbox/Checkbox.tsx +2 -2
  258. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  259. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  260. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  261. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  262. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  263. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  264. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  265. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  266. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  267. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  268. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  269. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  270. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  271. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  272. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  273. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  274. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  275. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  276. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  277. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  278. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  279. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  280. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  281. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  282. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  283. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  284. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  285. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  286. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  287. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  288. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  289. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  290. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  291. package/src/components/Dialog/Dialog.tsx +2 -2
  292. package/src/components/EventSelector/EventSelector.tsx +7 -7
  293. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  294. package/src/components/FileUpload/FileUpload.tsx +7 -4
  295. package/src/components/Header/Header.test.tsx +28 -0
  296. package/src/components/Header/Header.tsx +22 -9
  297. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  298. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  299. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  301. package/src/components/OrganisationSelector/OrganisationSelector.tsx +42 -22
  302. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  303. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  304. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  305. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  306. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  307. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  308. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  309. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  310. package/src/components/Progress/Progress.test.tsx +18 -19
  311. package/src/components/Progress/Progress.tsx +31 -32
  312. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  313. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  314. package/src/components/Select/Select.test.tsx +4 -1
  315. package/src/components/Select/Select.tsx +65 -20
  316. package/src/components/Switch/Switch.test.tsx +2 -1
  317. package/src/components/Switch/Switch.tsx +1 -1
  318. package/src/components/Toast/Toast.tsx +1 -1
  319. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  320. package/src/components/UserMenu/UserMenu.tsx +3 -3
  321. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  322. package/src/eslint-rules/pace-core-compliance.js +0 -2
  323. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  324. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  325. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  326. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  327. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  328. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  329. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +1 -1
  330. package/src/hooks/__tests__/usePublicEvent.test.ts +608 -0
  331. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  332. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +67 -24
  333. package/src/hooks/index.ts +1 -1
  334. package/src/hooks/public/usePublicEvent.ts +10 -10
  335. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  336. package/src/hooks/useAppConfig.ts +24 -5
  337. package/src/hooks/useFileDisplay.ts +298 -36
  338. package/src/hooks/useFileReference.ts +56 -11
  339. package/src/hooks/useFileUrl.ts +1 -1
  340. package/src/hooks/useInactivityTracker.ts +16 -7
  341. package/src/hooks/usePermissionCache.test.ts +85 -8
  342. package/src/hooks/useQueryCache.ts +27 -6
  343. package/src/hooks/useSecureDataAccess.test.ts +87 -42
  344. package/src/hooks/useSecureDataAccess.ts +95 -48
  345. package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
  346. package/src/providers/services/EventServiceProvider.tsx +37 -17
  347. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  348. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  349. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  350. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  351. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  352. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  353. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  354. package/src/rbac/api.ts +240 -36
  355. package/src/rbac/cache-invalidation.ts +21 -7
  356. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  357. package/src/rbac/components/NavigationGuard.tsx +23 -63
  358. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  359. package/src/rbac/components/NavigationProvider.tsx +13 -11
  360. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  361. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  362. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  363. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  364. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  365. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  366. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  367. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  368. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  369. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  370. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  371. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  372. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  373. package/src/rbac/engine.ts +4 -2
  374. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  375. package/src/rbac/hooks/index.ts +3 -0
  376. package/src/rbac/hooks/useCan.test.ts +101 -53
  377. package/src/rbac/hooks/usePermissions.ts +108 -41
  378. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  379. package/src/rbac/hooks/useRBAC.ts +83 -40
  380. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  381. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  382. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  383. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  384. package/src/rbac/request-deduplication.ts +1 -1
  385. package/src/rbac/secureClient.ts +72 -12
  386. package/src/rbac/security.ts +29 -23
  387. package/src/rbac/types.ts +10 -0
  388. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  389. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  390. package/src/rbac/utils/__tests__/eventContext.test.ts +8 -3
  391. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +74 -12
  392. package/src/rbac/utils/contextValidator.ts +288 -0
  393. package/src/rbac/utils/eventContext.ts +52 -3
  394. package/src/services/AuthService.ts +37 -8
  395. package/src/services/EventService.ts +165 -21
  396. package/src/services/OrganisationService.ts +125 -137
  397. package/src/services/__tests__/EventService.test.ts +26 -21
  398. package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
  399. package/src/services/__tests__/OrganisationService.test.ts +218 -86
  400. package/src/types/database.generated.ts +166 -201
  401. package/src/types/file-reference.ts +13 -10
  402. package/src/types/supabase.ts +2 -2
  403. package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
  404. package/src/utils/app/appNameResolver.test.ts +346 -73
  405. package/src/utils/context/superAdminOverride.ts +58 -0
  406. package/src/utils/file-reference/index.ts +65 -37
  407. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  408. package/src/utils/google-places/googlePlacesUtils.ts +1 -1
  409. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  410. package/src/utils/google-places/types.ts +1 -1
  411. package/src/utils/request-deduplication.ts +4 -4
  412. package/src/utils/security/secureDataAccess.test.ts +1 -1
  413. package/src/utils/security/secureDataAccess.ts +7 -4
  414. package/src/utils/storage/README.md +1 -1
  415. package/src/utils/storage/helpers.test.ts +1 -1
  416. package/src/utils/storage/helpers.ts +38 -19
  417. package/src/utils/storage/types.ts +15 -8
  418. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  419. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  420. package/src/vite-env.d.ts +2 -2
  421. package/dist/chunk-3GOZZZYH.js.map +0 -1
  422. package/dist/chunk-DDM4CCYT.js.map +0 -1
  423. package/dist/chunk-E7UAOUMY.js +0 -75
  424. package/dist/chunk-E7UAOUMY.js.map +0 -1
  425. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  426. package/dist/chunk-HEHYGYOX.js.map +0 -1
  427. package/dist/chunk-IM4QE42D.js.map +0 -1
  428. package/dist/chunk-MX64ZF6I.js.map +0 -1
  429. package/dist/chunk-SAUPYVLF.js.map +0 -1
  430. package/dist/chunk-THRPYOFK.js.map +0 -1
  431. package/dist/chunk-UCQSRW7Z.js.map +0 -1
  432. package/dist/chunk-VGZZXKBR.js.map +0 -1
  433. package/dist/chunk-YGPFYGA6.js.map +0 -1
  434. package/dist/chunk-YHCN776L.js.map +0 -1
  435. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-WKRZD47S.js.map} +0 -0
  436. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
  437. /package/dist/{api-YP7XD5L6.js.map → api-IHKALJZD.js.map} +0 -0
  438. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -9,16 +9,27 @@
9
9
  * consistent scope resolution logic.
10
10
  */
11
11
 
12
- import { useEffect, useState, useRef } from 'react';
12
+ import { useEffect, useState, useRef, useMemo } from 'react';
13
13
  import { SupabaseClient } from '@supabase/supabase-js';
14
14
  import type { Database } from '../../types/database';
15
15
  import type { Scope } from '../types';
16
- import { createScopeFromEvent } from '../utils/eventContext';
16
+ import { ContextValidator } from '../utils/contextValidator';
17
+ import type { AppConfig } from '../utils/contextValidator';
17
18
  import { getCurrentAppName } from '../../utils/app/appNameResolver';
18
19
  import { createLogger } from '../../utils/core/logger';
19
20
 
20
21
  const log = createLogger('useResolvedScope');
21
22
 
23
+ // Cache app config to avoid repeated database queries
24
+ // App config rarely changes during a session, so we can cache it
25
+ const appConfigCache = new Map<string, { appId: string; appConfig: AppConfig; timestamp: number }>();
26
+ const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
27
+
28
+ // Export function to clear cache (for testing)
29
+ export function clearAppConfigCache(): void {
30
+ appConfigCache.clear();
31
+ }
32
+
22
33
  export interface UseResolvedScopeOptions {
23
34
  /** Supabase client instance */
24
35
  supabase: SupabaseClient<Database> | null;
@@ -78,11 +89,12 @@ export function useResolvedScope({
78
89
  });
79
90
 
80
91
  // Update stable scope ref in useEffect to avoid updates during render
92
+ // For PORTAL, allow scopes without organisationId
81
93
  useEffect(() => {
82
- if (resolvedScope && resolvedScope.organisationId) {
94
+ if (resolvedScope) {
83
95
  const newScope = {
84
- organisationId: resolvedScope.organisationId,
85
- appId: resolvedScope.appId,
96
+ organisationId: resolvedScope.organisationId || '',
97
+ appId: resolvedScope.appId || '',
86
98
  eventId: resolvedScope.eventId
87
99
  };
88
100
 
@@ -92,16 +104,19 @@ export function useResolvedScope({
92
104
  stableScopeRef.current.appId !== newScope.appId) {
93
105
  stableScopeRef.current = {
94
106
  organisationId: newScope.organisationId,
95
- appId: newScope.appId || '',
107
+ appId: newScope.appId,
96
108
  eventId: newScope.eventId
97
109
  };
98
110
  }
99
- } else if (!resolvedScope) {
111
+ } else {
100
112
  // Reset to empty scope when no resolved scope
101
113
  stableScopeRef.current = { organisationId: '', appId: '', eventId: undefined };
102
114
  }
103
115
  }, [resolvedScope]);
104
116
 
117
+ // Get app name to check if it's PORTAL (needed for return logic)
118
+ const appName = getCurrentAppName();
119
+
105
120
  const stableScope = stableScopeRef.current;
106
121
 
107
122
  useEffect(() => {
@@ -123,20 +138,28 @@ export function useResolvedScope({
123
138
  setError(null);
124
139
 
125
140
  try {
126
- // Get app ID from package.json or environment
141
+ // Get app name and config
142
+ const appName = getCurrentAppName();
127
143
  let appId: string | undefined = undefined;
144
+ let appConfig: AppConfig | null = null;
128
145
 
129
- // Try to resolve from database
130
- if (supabase) {
131
- const appName = getCurrentAppName();
132
- if (appName) {
133
- try {
146
+ // Try to resolve app config from database (with caching)
147
+ if (supabase && appName) {
148
+ try {
149
+ // Check cache first
150
+ const cached = appConfigCache.get(appName);
151
+ const now = Date.now();
152
+ if (cached && (now - cached.timestamp) < CACHE_TTL) {
153
+ appId = cached.appId;
154
+ appConfig = cached.appConfig;
155
+ } else {
156
+ // Cache miss or expired - fetch from database
134
157
  const { data: app, error } = await supabase
135
158
  .from('rbac_apps')
136
- .select('id, name, is_active')
159
+ .select('id, name, requires_event, is_active')
137
160
  .eq('name', appName)
138
161
  .eq('is_active', true)
139
- .single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
162
+ .single() as { data: { id: string; name: string; requires_event: boolean; is_active: boolean } | null; error: any };
140
163
 
141
164
  if (error) {
142
165
  // Check if app exists but is inactive
@@ -148,83 +171,89 @@ export function useResolvedScope({
148
171
 
149
172
  if (inactiveApp) {
150
173
  log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
174
+ // Don't cache inactive apps - set appId to undefined
175
+ appId = undefined;
151
176
  } else {
152
177
  log.error(`App "${appName}" not found in rbac_apps table`);
178
+ // Don't cache missing apps - set appId to undefined
179
+ appId = undefined;
153
180
  }
154
181
  } else if (app) {
155
182
  appId = app.id;
183
+ appConfig = { requires_event: app.requires_event ?? false };
184
+ // Only cache successful lookups of active apps
185
+ appConfigCache.set(appName, { appId, appConfig, timestamp: now });
156
186
  }
157
- } catch (error) {
158
- log.error('Unexpected error resolving app ID:', error);
159
187
  }
188
+ } catch (error) {
189
+ log.error('Unexpected error resolving app config:', error);
160
190
  }
161
191
  }
162
192
 
163
- // Resolve scope based on available context
164
-
165
- // If we have both organisation and event, use them directly
166
- if (selectedOrganisationId && selectedEventId) {
167
- if (!cancelled) {
168
- setResolvedScope({
169
- organisationId: selectedOrganisationId,
170
- eventId: selectedEventId,
171
- appId: appId
172
- });
173
- setIsLoading(false);
174
- }
175
- return;
176
- }
193
+ // Build initial scope from available context
194
+ // For event-required apps: Only use eventId (org will be derived)
195
+ // For org-required apps: Only use organisationId (event is optional)
196
+ const initialScope: Scope = {
197
+ organisationId: appConfig?.requires_event ? undefined : (selectedOrganisationId || undefined),
198
+ eventId: selectedEventId || undefined,
199
+ appId: appId
200
+ };
177
201
 
178
- // If we only have organisation, use it
179
- if (selectedOrganisationId) {
180
- if (!cancelled) {
181
- setResolvedScope({
182
- organisationId: selectedOrganisationId,
183
- eventId: selectedEventId || undefined,
184
- appId: appId
185
- });
186
- setIsLoading(false);
187
- }
188
- return;
189
- }
202
+ // Use ContextValidator to resolve required context
203
+ // For PORTAL, always allow scope resolution even if appConfig is null
204
+ const validation = await ContextValidator.resolveRequiredContext(
205
+ initialScope,
206
+ appConfig,
207
+ appName || undefined,
208
+ supabase
209
+ );
190
210
 
191
- // If we only have event, resolve organisation from event
192
- if (selectedEventId && supabase) {
193
- try {
194
- const eventScope = await createScopeFromEvent(supabase, selectedEventId, appId);
195
- if (!eventScope) {
196
- log.error('Could not resolve organization from event context');
197
- if (!cancelled) {
198
- setResolvedScope(null);
199
- setError(new Error('Could not resolve organisation from event context'));
200
- setIsLoading(false);
201
- }
202
- return;
203
- }
204
- // Preserve the resolved app ID
211
+ if (!validation.isValid) {
212
+ // For PORTAL/ADMIN apps, allow scope without org/event even if validation fails
213
+ if (appName === 'PORTAL' || appName === 'ADMIN') {
205
214
  if (!cancelled) {
206
- setResolvedScope({
207
- ...eventScope,
208
- appId: appId || eventScope.appId
209
- });
215
+ // For PORTAL/ADMIN, we need at least an appId. If we don't have it from the query,
216
+ // we'll set it to undefined and let the component handle it (it can use contextAppId)
217
+ const optionalContextScope: Scope = {
218
+ organisationId: undefined,
219
+ eventId: undefined,
220
+ appId: appId || undefined // appId might be undefined if query failed, that's OK
221
+ };
222
+ setResolvedScope(optionalContextScope);
223
+ setError(null);
210
224
  setIsLoading(false);
211
225
  }
212
- } catch (err) {
213
- log.error('Error resolving scope from event:', err);
226
+ return;
227
+ }
228
+
229
+ // For event-required apps: if validation fails but we have an eventId, return scope with eventId
230
+ // The organisation will be derived later during permission checks
231
+ if (appConfig?.requires_event && selectedEventId) {
214
232
  if (!cancelled) {
215
- setResolvedScope(null);
216
- setError(err as Error);
233
+ const eventScope: Scope = {
234
+ organisationId: undefined, // Will be derived from event during permission check
235
+ eventId: selectedEventId,
236
+ appId: appId || undefined
237
+ };
238
+ setResolvedScope(eventScope);
239
+ setError(null); // Don't set error - let permission check handle derivation
217
240
  setIsLoading(false);
218
241
  }
242
+ return;
243
+ }
244
+
245
+ if (!cancelled) {
246
+ setResolvedScope(null);
247
+ setError(validation.error || new Error('Context validation failed'));
248
+ setIsLoading(false);
219
249
  }
220
250
  return;
221
251
  }
222
252
 
223
- // No context available
224
- log.error('No organisation or event context available');
253
+ // Set resolved scope
225
254
  if (!cancelled) {
226
- setResolvedScope(null);
227
- setError(new Error('No organisation or event context available'));
255
+ setResolvedScope(validation.resolvedScope);
256
+ setError(null);
228
257
  setIsLoading(false);
229
258
  }
230
259
  } catch (err) {
@@ -242,8 +271,37 @@ export function useResolvedScope({
242
271
  };
243
272
  }, [selectedOrganisationId, selectedEventId, supabase]);
244
273
 
274
+ // Return scope if it has appId (for PORTAL/ADMIN) or organisationId (for other apps)
275
+ // For PORTAL/ADMIN, always return a scope (even if empty) so components can use contextAppId
276
+ const allowsOptionalContexts = appName === 'PORTAL' || appName === 'ADMIN';
277
+ const hasValidScope = allowsOptionalContexts
278
+ ? true // PORTAL/ADMIN always have valid scope (even without org/event/appId)
279
+ : (stableScope.appId || stableScope.organisationId);
280
+
281
+ // CRITICAL FIX: Memoize finalScope to prevent creating new object references on every render
282
+ // This prevents infinite loops in useCan and other hooks that depend on scope equality
283
+ const finalScope: Scope | null = useMemo(() => {
284
+ if (!hasValidScope) {
285
+ return allowsOptionalContexts ? {} : null;
286
+ }
287
+
288
+ // Build scope object only with defined values to ensure stable reference
289
+ const scope: Scope = {};
290
+ if (stableScope.organisationId) {
291
+ scope.organisationId = stableScope.organisationId;
292
+ }
293
+ if (stableScope.eventId) {
294
+ scope.eventId = stableScope.eventId;
295
+ }
296
+ if (stableScope.appId) {
297
+ scope.appId = stableScope.appId;
298
+ }
299
+
300
+ return scope;
301
+ }, [hasValidScope, allowsOptionalContexts, stableScope.organisationId, stableScope.eventId, stableScope.appId]);
302
+
245
303
  return {
246
- resolvedScope: stableScope.organisationId ? stableScope as Scope : null,
304
+ resolvedScope: finalScope,
247
305
  isLoading,
248
306
  error
249
307
  };
@@ -95,7 +95,7 @@
95
95
  *
96
96
  * - Must be used within `UnifiedAuthProvider` context
97
97
  * - Requires `useOrganisations` and `useEvents` hooks to be available
98
- * - Environment variables `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` must be set
98
+ * - Environment variables `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY` must be set
99
99
  * (or `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` for Next.js)
100
100
  *
101
101
  * ## See Also
@@ -120,6 +120,7 @@ import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
120
120
  import { useOrganisations } from '../../hooks/useOrganisations';
121
121
  import { useEvents } from '../../hooks/useEvents';
122
122
  import { useResolvedScope } from './useResolvedScope';
123
+ import { useSuperAdminBypass } from './useSuperAdminBypass';
123
124
  import { createSecureClient, SecureSupabaseClient } from '../secureClient';
124
125
  import type { Database } from '../../types/database';
125
126
  import type { SupabaseClient } from '@supabase/supabase-js';
@@ -134,13 +135,15 @@ const MAX_CACHE_SIZE = 5;
134
135
 
135
136
  /**
136
137
  * Get cache key for secure client based on context
138
+ * CRITICAL: Must include isSuperAdmin in cache key to ensure correct filtering behavior
137
139
  */
138
140
  function getCacheKey(
139
141
  organisationId: string,
140
142
  eventId: string | undefined,
141
- appId: string | undefined
143
+ appId: string | undefined,
144
+ isSuperAdmin: boolean
142
145
  ): string {
143
- return `${organisationId}-${eventId || 'no-event'}-${appId || 'no-app'}`;
146
+ return `${organisationId}-${eventId || 'no-event'}-${appId || 'no-app'}-${isSuperAdmin ? 'super' : 'regular'}`;
144
147
  }
145
148
 
146
149
  /**
@@ -162,8 +165,8 @@ function getSupabaseConfig(): { url: string; key: string } | null {
162
165
  getEnvVar('NEXT_PUBLIC_SUPABASE_URL') ||
163
166
  null;
164
167
 
165
- const supabaseKey = getEnvVar('VITE_SUPABASE_ANON_KEY') ||
166
- getEnvVar('NEXT_PUBLIC_SUPABASE_ANON_KEY') ||
168
+ const supabaseKey = getEnvVar('VITE_SUPABASE_PUBLISHABLE_KEY') ||
169
+ getEnvVar('NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY') ||
167
170
  null;
168
171
 
169
172
  if (!supabaseUrl || !supabaseKey) {
@@ -210,6 +213,16 @@ export function useSecureSupabase(
210
213
  const eventsContext = useEvents();
211
214
  const { selectedEvent } = eventsContext;
212
215
  const eventLoading = 'eventLoading' in eventsContext ? eventsContext.eventLoading : false;
216
+
217
+ // Check super admin status for conditional filtering
218
+ // Use both verified status and metadata hint to avoid race conditions
219
+ // Strategy: Use verified status if available, otherwise use metadata hint during verification
220
+ // This prevents creating clients with wrong super admin status before verification completes
221
+ const { isSuperAdmin: verifiedIsSuperAdmin, isLoading: isVerifyingSuperAdmin } = useSuperAdminBypass();
222
+ const metadataHint = Boolean(user?.app_metadata?.is_super_admin) || Boolean(user?.user_metadata?.is_super_admin);
223
+ // If verified as super admin, use that. If verification in progress, use metadata hint optimistically.
224
+ // Once verification completes and user is not super admin, verifiedIsSuperAdmin will be false.
225
+ const isSuperAdmin = verifiedIsSuperAdmin || (isVerifyingSuperAdmin && metadataHint);
213
226
 
214
227
  // Resolve scope to get appId
215
228
  const { resolvedScope } = useResolvedScope({
@@ -235,19 +248,20 @@ export function useSecureSupabase(
235
248
  return baseClient || authSupabase || null;
236
249
  }
237
250
 
238
- // If we have organisation context, create or reuse a secure client
239
- if (selectedOrganisation?.id && user?.id) {
240
- const organisationId = selectedOrganisation.id;
241
- const eventId = selectedEvent?.event_id;
242
-
243
- // Get appId from resolved scope if available
244
- const appId = resolvedScope?.appId;
245
-
251
+ // Use resolved scope to get organisationId (derived from event if needed)
252
+ // For event-required apps, resolvedScope.organisationId is derived from event
253
+ // For org-required apps, resolvedScope.organisationId comes from selectedOrganisation
254
+ const organisationId = resolvedScope?.organisationId;
255
+ const eventId = resolvedScope?.eventId || selectedEvent?.event_id;
256
+ const appId = resolvedScope?.appId;
257
+
258
+ // If we have organisation context (from resolved scope), create or reuse a secure client
259
+ if (organisationId && user?.id) {
246
260
  // Update previous context
247
261
  prevContextRef.current = { organisationId, eventId, appId };
248
262
 
249
- // Check cache first
250
- const cacheKey = getCacheKey(organisationId, eventId, appId);
263
+ // Check cache first (must include isSuperAdmin in key for correct filtering)
264
+ const cacheKey = getCacheKey(organisationId, eventId, appId, isSuperAdmin);
251
265
  const cachedClient = secureClientCache.get(cacheKey);
252
266
 
253
267
  if (cachedClient) {
@@ -259,7 +273,7 @@ export function useSecureSupabase(
259
273
  const config = getSupabaseConfig();
260
274
  if (!config || !config.url || !config.key) {
261
275
  logger.warn('useSecureSupabase', 'Missing Supabase environment variables. Falling back to base client.', {
262
- note: 'Ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment.'
276
+ note: 'Ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.'
263
277
  });
264
278
  return baseClient || authSupabase || null;
265
279
  }
@@ -270,7 +284,8 @@ export function useSecureSupabase(
270
284
  config.key,
271
285
  organisationId as any, // organisationId is string, UUID is string alias
272
286
  eventId,
273
- appId as any // appId is string | undefined, UUID is string alias
287
+ appId as any, // appId is string | undefined, UUID is string alias
288
+ isSuperAdmin // Pass super admin status for conditional filtering
274
289
  );
275
290
 
276
291
  // Cache the client for reuse
@@ -296,11 +311,13 @@ export function useSecureSupabase(
296
311
  // Fallback to base client when context is not available
297
312
  return baseClient || authSupabase || null;
298
313
  }, [
299
- selectedOrganisation?.id,
314
+ resolvedScope?.organisationId,
315
+ resolvedScope?.eventId,
316
+ resolvedScope?.appId,
300
317
  selectedEvent?.event_id,
301
318
  user?.id,
302
319
  eventLoading,
303
- resolvedScope?.appId,
320
+ isSuperAdmin,
304
321
  baseClient,
305
322
  authSupabase
306
323
  ]);
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @file useSuperAdminBypass
3
+ * @package @jmruthers/pace-core
4
+ *
5
+ * Detects whether the current user is a super admin, keeps the
6
+ * server session override flag in sync, and exposes a boolean
7
+ * that downstream hooks can use to bypass organisation scoping.
8
+ */
9
+
10
+ import { useEffect, useMemo, useRef, useState } from 'react';
11
+ import { useUnifiedAuth, type UnifiedAuthContextType } from '../../providers/services/UnifiedAuthProvider';
12
+ import { isSuperAdmin as fetchIsSuperAdmin } from '../api';
13
+ import { setSuperAdminOverrideFlag } from '../../utils/context/superAdminOverride';
14
+ import { createLogger } from '../../utils/core/logger';
15
+
16
+ const log = createLogger('useSuperAdminBypass');
17
+
18
+ export interface SuperAdminBypassState {
19
+ /** True when the user has been verified as a super admin */
20
+ isSuperAdmin: boolean;
21
+ /** True while the hook is checking the server */
22
+ isLoading: boolean;
23
+ /** Error returned by the verification request, if any */
24
+ error: Error | null;
25
+ }
26
+
27
+ function useSafeUnifiedAuth(): UnifiedAuthContextType | null {
28
+ try {
29
+ return useUnifiedAuth();
30
+ } catch (error) {
31
+ log.debug('useSuperAdminBypass', 'UnifiedAuthProvider not available, falling back to defaults', {
32
+ error: error instanceof Error ? error.message : error
33
+ });
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export function useSuperAdminBypass(): SuperAdminBypassState {
39
+ const authContext = useSafeUnifiedAuth();
40
+ const user = authContext?.user ?? null;
41
+ const supabase = authContext?.supabase ?? null;
42
+ const metadataHint =
43
+ Boolean(user?.app_metadata?.is_super_admin) ||
44
+ Boolean(user?.user_metadata?.is_super_admin);
45
+
46
+ const [isSuperAdminState, setIsSuperAdminState] = useState<boolean>(metadataHint);
47
+ const [hasVerified, setHasVerified] = useState<boolean>(!user?.id);
48
+ const [isLoading, setIsLoading] = useState<boolean>(!!user?.id);
49
+ const [error, setError] = useState<Error | null>(null);
50
+ const lastOverrideValueRef = useRef<boolean | null>(null);
51
+
52
+ // Verify against the RBAC engine whenever the user changes
53
+ useEffect(() => {
54
+ if (!user?.id) {
55
+ setIsSuperAdminState(false);
56
+ setHasVerified(true);
57
+ setIsLoading(false);
58
+ setError(null);
59
+ return;
60
+ }
61
+
62
+ let cancelled = false;
63
+ setIsLoading(true);
64
+ setError(null);
65
+
66
+ fetchIsSuperAdmin(user.id)
67
+ .then((result) => {
68
+ if (cancelled) {
69
+ return;
70
+ }
71
+ setIsSuperAdminState(result);
72
+ setHasVerified(true);
73
+ })
74
+ .catch((err) => {
75
+ if (cancelled) {
76
+ return;
77
+ }
78
+ const normalisedError =
79
+ err instanceof Error ? err : new Error('Failed to resolve super admin status');
80
+ setError(normalisedError);
81
+ setIsSuperAdminState(false);
82
+ setHasVerified(false);
83
+ log.error('Unable to verify super admin status', normalisedError);
84
+ })
85
+ .finally(() => {
86
+ if (!cancelled) {
87
+ setIsLoading(false);
88
+ }
89
+ });
90
+
91
+ return () => {
92
+ cancelled = true;
93
+ };
94
+ }, [user?.id]);
95
+
96
+ const shouldBypass = hasVerified && isSuperAdminState;
97
+
98
+ // Keep the database session flag in sync for auditing/RLS helpers
99
+ useEffect(() => {
100
+ if (!supabase) {
101
+ return;
102
+ }
103
+ if (lastOverrideValueRef.current === shouldBypass) {
104
+ return;
105
+ }
106
+ lastOverrideValueRef.current = shouldBypass;
107
+
108
+ setSuperAdminOverrideFlag({
109
+ supabase,
110
+ enabled: shouldBypass,
111
+ reason: 'pace-core-super-admin-bypass'
112
+ }).catch(() => {
113
+ // Errors are logged inside the helper
114
+ });
115
+ }, [supabase, shouldBypass]);
116
+
117
+ return useMemo(
118
+ () => ({
119
+ isSuperAdmin: shouldBypass,
120
+ isLoading,
121
+ error
122
+ }),
123
+ [shouldBypass, isLoading, error]
124
+ );
125
+ }
126
+
@@ -27,7 +27,7 @@ const inFlightRequests = new Map<string, Promise<boolean>>();
27
27
  function generateDeduplicationKey(input: PermissionCheck): string {
28
28
  return RBACCache.generatePermissionKey({
29
29
  userId: input.userId,
30
- organisationId: input.scope.organisationId!,
30
+ organisationId: input.scope.organisationId, // Can be undefined for page-level permissions
31
31
  eventId: input.scope.eventId,
32
32
  appId: input.scope.appId,
33
33
  permission: input.permission,