@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
@@ -22,6 +22,8 @@ const mockUseUnifiedAuthFn = vi.fn(() => ({
22
22
  selectedEvent: null,
23
23
  selectedOrganisationId: 'org-123',
24
24
  selectedEventId: undefined,
25
+ appId: 'app-123',
26
+ appName: 'test-app',
25
27
  supabase: {
26
28
  from: vi.fn(() => ({
27
29
  select: vi.fn(() => ({
@@ -41,17 +43,36 @@ vi.mock('../../hooks', () => ({
41
43
  useCan: vi.fn()
42
44
  }));
43
45
 
46
+ // Mock useResolvedScope hook
47
+ vi.mock('../../hooks/useResolvedScope', () => ({
48
+ useResolvedScope: vi.fn()
49
+ }));
50
+
44
51
  // Mock the app name resolver
45
- vi.mock('../../../utils/appNameResolver', () => ({
52
+ vi.mock('../../../utils/app/appNameResolver', () => ({
46
53
  getCurrentAppName: () => 'test-app'
47
54
  }));
48
55
 
56
+ import { useResolvedScope } from '../../hooks/useResolvedScope';
57
+
49
58
  describe('PagePermissionGuard Simplified Tests', () => {
50
59
  const mockUseCan = vi.mocked(useCan);
60
+ const mockUseResolvedScope = vi.mocked(useResolvedScope);
51
61
 
52
62
  beforeEach(() => {
53
63
  vi.clearAllMocks();
54
64
 
65
+ // Mock useResolvedScope to return resolved scope immediately
66
+ mockUseResolvedScope.mockReturnValue({
67
+ resolvedScope: {
68
+ organisationId: 'org-123',
69
+ eventId: undefined,
70
+ appId: 'app-123'
71
+ },
72
+ isLoading: false,
73
+ error: null
74
+ });
75
+
55
76
  // Set up default mock behavior
56
77
  mockUseCan.mockReturnValue({
57
78
  can: false,
@@ -31,12 +31,18 @@ vi.mock('../../utils/eventContext', () => ({
31
31
  createScopeFromEvent: vi.fn()
32
32
  }));
33
33
 
34
+ // Mock useResolvedScope hook
35
+ vi.mock('../../hooks/useResolvedScope', () => ({
36
+ useResolvedScope: vi.fn()
37
+ }));
38
+
34
39
  // Mock the app name resolver
35
40
  vi.mock('../../../utils/app/appNameResolver', () => ({
36
41
  getCurrentAppName: vi.fn()
37
42
  }));
38
43
 
39
44
  import { createScopeFromEvent } from '../../utils/eventContext';
45
+ import { useResolvedScope } from '../../hooks/useResolvedScope';
40
46
  import { getCurrentAppName } from '../../../utils/app/appNameResolver';
41
47
 
42
48
  // Mock data
@@ -71,23 +77,28 @@ describe('PagePermissionGuard Component', () => {
71
77
  const mockUseCan = vi.mocked(useCan);
72
78
  const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
73
79
  const mockGetCurrentAppName = vi.mocked(getCurrentAppName);
80
+ const mockUseResolvedScope = vi.mocked(useResolvedScope);
74
81
 
75
82
  beforeEach(() => {
76
83
  vi.clearAllMocks();
77
84
 
78
85
  // Default mock implementations
86
+ // For org-required apps: selectedOrganisation is available
87
+ // For event-required apps: selectedOrganisation is null, org derived from event
79
88
  mockUseUnifiedAuthFn.mockReturnValue({
80
89
  user: mockUser,
81
- selectedOrganisation: { id: 'org-123' },
82
- selectedEvent: { event_id: 'event-123' },
90
+ selectedOrganisation: { id: 'org-123' }, // Available for org-required apps, null for event-required
91
+ selectedEvent: { event_id: 'event-123', organisation_id: 'org-123' },
83
92
  appId: 'app-123', // Required for scope resolution
93
+ appName: 'test-app',
94
+ appConfig: { requires_event: false }, // Default to org-required for tests
84
95
  supabase: {
85
96
  from: vi.fn().mockReturnValue({
86
97
  select: vi.fn().mockReturnValue({
87
98
  eq: vi.fn().mockReturnValue({
88
99
  eq: vi.fn().mockReturnValue({
89
100
  single: vi.fn().mockResolvedValue({
90
- data: { id: 'app-123', name: 'test-app', is_active: true },
101
+ data: { id: 'app-123', name: 'test-app', is_active: true, requires_event: false },
91
102
  error: null
92
103
  })
93
104
  })
@@ -99,6 +110,17 @@ describe('PagePermissionGuard Component', () => {
99
110
 
100
111
  mockGetCurrentAppName.mockReturnValue('test-app');
101
112
 
113
+ // Mock useResolvedScope to return resolved scope immediately
114
+ mockUseResolvedScope.mockReturnValue({
115
+ resolvedScope: {
116
+ organisationId: 'org-123',
117
+ eventId: 'event-123',
118
+ appId: 'app-123'
119
+ },
120
+ isLoading: false,
121
+ error: null
122
+ });
123
+
102
124
  mockUseCan.mockReturnValue({
103
125
  can: true,
104
126
  isLoading: false,
@@ -238,14 +260,15 @@ describe('PagePermissionGuard Component', () => {
238
260
 
239
261
  expect(mockUseCan).toHaveBeenCalledWith(
240
262
  'user-123',
241
- expect.objectContaining({
263
+ {
242
264
  organisationId: 'org-123',
243
265
  eventId: 'event-123',
244
266
  appId: 'app-123'
245
- }),
267
+ },
246
268
  'read:page.dashboard',
247
269
  'dashboard',
248
- true
270
+ true,
271
+ 'test-app'
249
272
  );
250
273
  });
251
274
 
@@ -274,14 +297,15 @@ describe('PagePermissionGuard Component', () => {
274
297
 
275
298
  expect(mockUseCan).toHaveBeenCalledWith(
276
299
  'user-123',
277
- expect.objectContaining({
300
+ {
278
301
  organisationId: 'org-123',
279
302
  eventId: 'event-123',
280
303
  appId: 'app-123'
281
- }),
304
+ },
282
305
  'read:page.dashboard',
283
306
  customPageId,
284
- true
307
+ true,
308
+ 'test-app'
285
309
  );
286
310
  });
287
311
 
@@ -312,14 +336,15 @@ describe('PagePermissionGuard Component', () => {
312
336
 
313
337
  expect(mockUseCan).toHaveBeenCalledWith(
314
338
  'user-123',
315
- expect.objectContaining({
339
+ {
316
340
  organisationId: 'org-123',
317
341
  eventId: 'event-123',
318
342
  appId: 'app-123'
319
- }),
343
+ },
320
344
  `${operation}:page.dashboard`,
321
345
  'dashboard',
322
- true
346
+ true,
347
+ 'test-app'
323
348
  );
324
349
 
325
350
  unmount();
@@ -353,8 +378,8 @@ describe('PagePermissionGuard Component', () => {
353
378
 
354
379
  describe('App ID Resolution', () => {
355
380
  it('resolves app ID from database', async () => {
356
- // When appId is already provided from useUnifiedAuth, getCurrentAppName is not called
357
- // The component uses appId from context directly
381
+ // useResolvedScope will call getCurrentAppName to resolve app config
382
+ // The component uses appId from context when available
358
383
  mockUseCan.mockReturnValue({
359
384
  can: true,
360
385
  isLoading: false,
@@ -374,9 +399,11 @@ describe('PagePermissionGuard Component', () => {
374
399
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
375
400
  }, { interval: 10, timeout: 2000 });
376
401
 
377
- // appId is provided from useUnifiedAuth context, so getCurrentAppName is not called
378
- // The component uses contextAppId directly (line 183 of PagePermissionGuard)
379
- expect(mockGetCurrentAppName).not.toHaveBeenCalled();
402
+ // useResolvedScope calls getCurrentAppName to resolve app config
403
+ // The component uses contextAppId when available, but useResolvedScope still needs app name
404
+ // So getCurrentAppName may be called by useResolvedScope
405
+ // The important thing is that the component works correctly
406
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
380
407
  });
381
408
 
382
409
  it('handles app resolution errors in test environment', async () => {
@@ -413,6 +440,19 @@ describe('PagePermissionGuard Component', () => {
413
440
  it('handles app resolution errors in production', async () => {
414
441
  mockGetCurrentAppName.mockReturnValue(null);
415
442
 
443
+ // When app resolution fails, useResolvedScope should return null scope or error
444
+ mockUseResolvedScope.mockReturnValue({
445
+ resolvedScope: null,
446
+ isLoading: true, // Still loading when app resolution fails
447
+ error: null
448
+ });
449
+
450
+ mockUseCan.mockReturnValue({
451
+ can: false,
452
+ isLoading: true,
453
+ error: null
454
+ });
455
+
416
456
  // Mock import.meta.env.MODE for production using vi.stubEnv
417
457
  vi.stubEnv('MODE', 'production');
418
458
 
@@ -426,9 +466,8 @@ describe('PagePermissionGuard Component', () => {
426
466
  </PagePermissionGuard>
427
467
  );
428
468
 
429
- await waitFor(() => {
430
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
431
- }, { interval: 10 });
469
+ // Component should show loading when scope is still resolving
470
+ expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
432
471
 
433
472
  // Restore environment
434
473
  vi.unstubAllEnvs();
@@ -516,11 +555,13 @@ describe('PagePermissionGuard Component', () => {
516
555
  customScope,
517
556
  'read:page.dashboard',
518
557
  'dashboard',
519
- true
558
+ true,
559
+ 'test-app'
520
560
  );
521
561
  });
522
562
 
523
- it('resolves scope from organisation and event context', async () => {
563
+ it('resolves scope from organisation and event context (org-required app)', async () => {
564
+ // For org-required apps, organisation is primary, event is optional
524
565
  mockUseCan.mockReturnValue({
525
566
  can: true,
526
567
  isLoading: false,
@@ -542,23 +583,26 @@ describe('PagePermissionGuard Component', () => {
542
583
 
543
584
  expect(mockUseCan).toHaveBeenCalledWith(
544
585
  'user-123',
545
- expect.objectContaining({
586
+ {
546
587
  organisationId: 'org-123',
547
588
  eventId: 'event-123',
548
589
  appId: 'app-123'
549
- }),
590
+ },
550
591
  'read:page.dashboard',
551
592
  'dashboard',
552
- true
593
+ true,
594
+ 'test-app'
553
595
  );
554
596
  });
555
597
 
556
- it('resolves scope from organisation only', async () => {
598
+ it('resolves scope from organisation only (org-required app)', async () => {
599
+ // For org-required apps, organisation is primary context, event is optional
557
600
  mockUseUnifiedAuthFn.mockReturnValue({
558
601
  user: mockUser,
559
602
  selectedOrganisation: { id: 'org-123' },
560
603
  selectedEvent: null,
561
604
  appId: 'app-123',
605
+ appName: 'test-app',
562
606
  supabase: {
563
607
  from: vi.fn().mockReturnValue({
564
608
  select: vi.fn().mockReturnValue({
@@ -575,6 +619,16 @@ describe('PagePermissionGuard Component', () => {
575
619
  } as any
576
620
  });
577
621
 
622
+ mockUseResolvedScope.mockReturnValue({
623
+ resolvedScope: {
624
+ organisationId: 'org-123',
625
+ eventId: undefined,
626
+ appId: 'app-123'
627
+ },
628
+ isLoading: false,
629
+ error: null
630
+ });
631
+
578
632
  mockUseCan.mockReturnValue({
579
633
  can: true,
580
634
  isLoading: false,
@@ -596,23 +650,26 @@ describe('PagePermissionGuard Component', () => {
596
650
 
597
651
  expect(mockUseCan).toHaveBeenCalledWith(
598
652
  'user-123',
599
- expect.objectContaining({
653
+ {
600
654
  organisationId: 'org-123',
601
655
  eventId: undefined,
602
656
  appId: 'app-123'
603
- }),
657
+ },
604
658
  'read:page.dashboard',
605
659
  'dashboard',
606
- true
660
+ true,
661
+ 'test-app'
607
662
  );
608
663
  });
609
664
 
610
- it('resolves scope from event context when organisation not available', async () => {
665
+ it('resolves scope from event context when organisation not available (event-required app)', async () => {
666
+ // For event-required apps, selectedOrganisation is null, org is derived from event
611
667
  mockUseUnifiedAuthFn.mockReturnValue({
612
668
  user: mockUser,
613
- selectedOrganisation: null,
669
+ selectedOrganisation: null, // Not available for event-required apps
614
670
  selectedEvent: { event_id: 'event-123' },
615
671
  appId: 'app-123',
672
+ appName: 'test-app',
616
673
  supabase: {
617
674
  from: vi.fn().mockReturnValue({
618
675
  select: vi.fn().mockReturnValue({
@@ -629,10 +686,15 @@ describe('PagePermissionGuard Component', () => {
629
686
  } as any
630
687
  });
631
688
 
632
- mockCreateScopeFromEvent.mockResolvedValue({
633
- organisationId: 'resolved-org',
634
- eventId: 'event-123',
635
- appId: 'resolved-app'
689
+ // Mock useResolvedScope to return scope resolved from event
690
+ mockUseResolvedScope.mockReturnValue({
691
+ resolvedScope: {
692
+ organisationId: 'resolved-org',
693
+ eventId: 'event-123',
694
+ appId: 'app-123'
695
+ },
696
+ isLoading: false,
697
+ error: null
636
698
  });
637
699
 
638
700
  mockUseCan.mockReturnValue({
@@ -654,20 +716,17 @@ describe('PagePermissionGuard Component', () => {
654
716
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
655
717
  }, { interval: 10 });
656
718
 
657
- expect(mockCreateScopeFromEvent).toHaveBeenCalledWith(
658
- expect.any(Object),
659
- 'event-123'
660
- );
661
719
  expect(mockUseCan).toHaveBeenCalledWith(
662
720
  'user-123',
663
- expect.objectContaining({
721
+ {
664
722
  organisationId: 'resolved-org',
665
723
  eventId: 'event-123',
666
724
  appId: 'app-123' // Component uses appId from context, not from resolved scope
667
- }),
725
+ },
668
726
  'read:page.dashboard',
669
727
  'dashboard',
670
- true
728
+ true,
729
+ 'test-app'
671
730
  );
672
731
  });
673
732
 
@@ -677,11 +736,23 @@ describe('PagePermissionGuard Component', () => {
677
736
  selectedOrganisation: null,
678
737
  selectedEvent: { event_id: 'event-123' },
679
738
  appId: undefined, // Not available for error case
739
+ appName: 'test-app',
680
740
  supabase: {} as any
681
741
  });
682
742
 
683
743
  const error = new Error('Could not resolve organisation from event');
684
- mockCreateScopeFromEvent.mockRejectedValue(error);
744
+ mockUseResolvedScope.mockReturnValue({
745
+ resolvedScope: null,
746
+ isLoading: false,
747
+ error
748
+ });
749
+
750
+ // Mock useCan to return quickly so component can process error
751
+ mockUseCan.mockReturnValue({
752
+ can: false,
753
+ isLoading: false,
754
+ error: null
755
+ });
685
756
 
686
757
  render(
687
758
  <PagePermissionGuard
@@ -693,9 +764,10 @@ describe('PagePermissionGuard Component', () => {
693
764
  </PagePermissionGuard>
694
765
  );
695
766
 
767
+ // Component shows fallback when there's a scope error
696
768
  await waitFor(() => {
697
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
698
- }, { interval: 10 });
769
+ expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
770
+ }, { interval: 10, timeout: 2000 });
699
771
  });
700
772
 
701
773
  it('handles missing context gracefully', async () => {
@@ -704,9 +776,16 @@ describe('PagePermissionGuard Component', () => {
704
776
  selectedOrganisation: null,
705
777
  selectedEvent: null,
706
778
  appId: undefined,
779
+ appName: 'test-app',
707
780
  supabase: null
708
781
  });
709
782
 
783
+ mockUseResolvedScope.mockReturnValue({
784
+ resolvedScope: null,
785
+ isLoading: true,
786
+ error: null
787
+ });
788
+
710
789
  render(
711
790
  <PagePermissionGuard
712
791
  pageName={mockPageName}
@@ -717,9 +796,7 @@ describe('PagePermissionGuard Component', () => {
717
796
  </PagePermissionGuard>
718
797
  );
719
798
 
720
- await waitFor(() => {
721
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
722
- }, { interval: 10 });
799
+ expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
723
800
  });
724
801
  });
725
802
 
@@ -922,23 +999,18 @@ describe('PagePermissionGuard Component', () => {
922
999
  it('handles missing user gracefully', async () => {
923
1000
  mockUseUnifiedAuthFn.mockReturnValue({
924
1001
  user: null,
925
- selectedOrganisation: { id: 'org-123' },
926
- selectedEvent: { event_id: 'event-123' },
927
- appId: 'app-123',
928
- supabase: {
929
- from: vi.fn().mockReturnValue({
930
- select: vi.fn().mockReturnValue({
931
- eq: vi.fn().mockReturnValue({
932
- eq: vi.fn().mockReturnValue({
933
- single: vi.fn().mockResolvedValue({
934
- data: { id: 'app-123', name: 'test-app', is_active: true },
935
- error: null
936
- })
937
- })
938
- })
939
- })
940
- })
941
- } as any
1002
+ selectedOrganisation: null, // No org when user is missing
1003
+ selectedEvent: null, // No event when user is missing
1004
+ appId: undefined,
1005
+ appName: 'test-app',
1006
+ supabase: null
1007
+ });
1008
+
1009
+ // When user is missing, scope resolution should return null
1010
+ mockUseResolvedScope.mockReturnValue({
1011
+ resolvedScope: null,
1012
+ isLoading: false,
1013
+ error: null
942
1014
  });
943
1015
 
944
1016
  mockUseCan.mockReturnValue({
@@ -957,21 +1029,8 @@ describe('PagePermissionGuard Component', () => {
957
1029
  </PagePermissionGuard>
958
1030
  );
959
1031
 
960
- await waitFor(() => {
961
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
962
- }, { interval: 10 });
963
-
964
- expect(mockUseCan).toHaveBeenCalledWith(
965
- '',
966
- expect.objectContaining({
967
- organisationId: undefined, // Changed from empty string - undefined is used when not resolved
968
- eventId: undefined,
969
- appId: undefined // appId is optional and should be undefined when not resolved
970
- }),
971
- 'read:page.dashboard',
972
- 'dashboard',
973
- true
974
- );
1032
+ // Component shows loading when user is missing (no valid user to check permissions for)
1033
+ expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
975
1034
  });
976
1035
 
977
1036
  it('handles database errors during app resolution', async () => {
@@ -980,6 +1039,7 @@ describe('PagePermissionGuard Component', () => {
980
1039
  selectedOrganisation: { id: 'org-123' },
981
1040
  selectedEvent: { event_id: 'event-123' },
982
1041
  appId: undefined, // Not resolved due to database error
1042
+ appName: 'test-app',
983
1043
  supabase: {
984
1044
  from: vi.fn().mockReturnValue({
985
1045
  select: vi.fn().mockReturnValue({
@@ -996,6 +1056,24 @@ describe('PagePermissionGuard Component', () => {
996
1056
  } as any
997
1057
  });
998
1058
 
1059
+ // When database error occurs, useResolvedScope might still return a scope (with appId undefined)
1060
+ // or might return null. For PORTAL app, it can work without appId
1061
+ mockUseResolvedScope.mockReturnValue({
1062
+ resolvedScope: {
1063
+ organisationId: 'org-123',
1064
+ eventId: 'event-123',
1065
+ appId: undefined // App resolution failed
1066
+ },
1067
+ isLoading: false,
1068
+ error: null
1069
+ });
1070
+
1071
+ mockUseCan.mockReturnValue({
1072
+ can: true, // Permission check can still succeed without appId for page permissions
1073
+ isLoading: false,
1074
+ error: null
1075
+ });
1076
+
999
1077
  // Set NODE_ENV to production
1000
1078
  const originalEnv = process.env.NODE_ENV;
1001
1079
  process.env.NODE_ENV = 'production';
@@ -1010,9 +1088,10 @@ describe('PagePermissionGuard Component', () => {
1010
1088
  </PagePermissionGuard>
1011
1089
  );
1012
1090
 
1091
+ // Component can still work if scope is resolved (even without appId for page permissions)
1013
1092
  await waitFor(() => {
1014
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
1015
- }, { interval: 10 });
1093
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
1094
+ }, { interval: 10, timeout: 2000 });
1016
1095
 
1017
1096
  // Restore NODE_ENV
1018
1097
  process.env.NODE_ENV = originalEnv;
@@ -22,6 +22,8 @@ const mockUseUnifiedAuthFn = vi.fn(() => ({
22
22
  selectedEvent: null,
23
23
  selectedOrganisationId: 'org-123',
24
24
  selectedEventId: undefined,
25
+ appId: 'app-123',
26
+ appName: 'test-app',
25
27
  supabase: {
26
28
  from: vi.fn(() => ({
27
29
  select: vi.fn(() => ({
@@ -41,17 +43,36 @@ vi.mock('../../hooks', () => ({
41
43
  useCan: vi.fn()
42
44
  }));
43
45
 
46
+ // Mock useResolvedScope hook
47
+ vi.mock('../../hooks/useResolvedScope', () => ({
48
+ useResolvedScope: vi.fn()
49
+ }));
50
+
44
51
  // Mock the app name resolver
45
- vi.mock('../../../utils/appNameResolver', () => ({
52
+ vi.mock('../../../utils/app/appNameResolver', () => ({
46
53
  getCurrentAppName: () => 'test-app'
47
54
  }));
48
55
 
56
+ import { useResolvedScope } from '../../hooks/useResolvedScope';
57
+
49
58
  describe('PagePermissionGuard Verification', () => {
50
59
  const mockUseCan = vi.mocked(useCan);
60
+ const mockUseResolvedScope = vi.mocked(useResolvedScope);
51
61
 
52
62
  beforeEach(() => {
53
63
  vi.clearAllMocks();
54
64
 
65
+ // Mock useResolvedScope to return resolved scope immediately
66
+ mockUseResolvedScope.mockReturnValue({
67
+ resolvedScope: {
68
+ organisationId: 'org-123',
69
+ eventId: undefined,
70
+ appId: 'app-123'
71
+ },
72
+ isLoading: false,
73
+ error: null
74
+ });
75
+
55
76
  // Set up default mock behavior
56
77
  mockUseCan.mockReturnValue({
57
78
  can: true,