@jmruthers/pace-core 0.6.11 → 0.6.12

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 (533) hide show
  1. package/dist/{DataTable-EFYP2QLE.js → DataTable-AQAHSFLM.js} +7 -6
  2. package/dist/{api-BZR2CYXL.js → api-6OQXYT67.js} +2 -1
  3. package/dist/{chunk-L5LFKKLJ.js → chunk-2GBDPPUC.js} +1 -1
  4. package/dist/{chunk-J2KQK6DG.js → chunk-AP5FG7W4.js} +2 -2
  5. package/dist/{chunk-UZNAFKGW.js → chunk-GHCUP64P.js} +1 -21
  6. package/dist/{chunk-V7FTM2LU.js → chunk-H6RTU4DZ.js} +37 -19
  7. package/dist/{chunk-KJXRL3XE.js → chunk-HQTYP6BX.js} +79 -45
  8. package/dist/{chunk-YFTFFJIV.js → chunk-M7QE7XOA.js} +3 -3
  9. package/dist/{chunk-YYTWKVHO.js → chunk-MVVWZ7JV.js} +25 -30
  10. package/dist/{chunk-PCSHBLPB.js → chunk-NJ7FGQWB.js} +5 -5
  11. package/dist/{chunk-WY6Y7KC3.js → chunk-QWIG36BZ.js} +3 -3
  12. package/dist/{chunk-4R3T5ENU.js → chunk-S57OLCLO.js} +9 -6
  13. package/dist/chunk-VFLR5K2H.js +23 -0
  14. package/dist/{chunk-2OEVOGGR.js → chunk-Y2LWSLLB.js} +41 -25
  15. package/dist/{chunk-7A6IMHH2.js → chunk-YFGNMB67.js} +75 -6
  16. package/dist/components.d.ts +3 -3
  17. package/dist/components.js +13 -12
  18. package/dist/{functions-DH45k8ec.d.ts → functions-hF5ImHCr.d.ts} +1 -1
  19. package/dist/hooks.js +8 -7
  20. package/dist/index.d.ts +5 -5
  21. package/dist/index.js +17 -16
  22. package/dist/providers.js +3 -2
  23. package/dist/rbac/index.d.ts +10 -10
  24. package/dist/rbac/index.js +9 -8
  25. package/dist/{types-BE2sEHKd.d.ts → types-Besvoyzb.d.ts} +1 -1
  26. package/dist/{types-CvOPXWWZ.d.ts → types-CGHrxfqc.d.ts} +3 -0
  27. package/dist/types.d.ts +2 -2
  28. package/dist/{usePublicPageContext-B91dGYW1.d.ts → usePublicPageContext-BQrHf95t.d.ts} +1 -1
  29. package/dist/utils.js +4 -3
  30. package/docs/api/@jmruthers/namespaces/DialogPortal/README.md +14 -0
  31. package/docs/api/@jmruthers/namespaces/DialogPortal/variables/displayName.md +11 -0
  32. package/docs/api/README.md +6 -2
  33. package/docs/api/_media/README.md +186 -0
  34. package/docs/api/classes/ColumnFactory.md +225 -0
  35. package/docs/api/classes/Logger.md +246 -0
  36. package/docs/api/classes/RBACAuditManager.md +299 -0
  37. package/docs/api/classes/RBACCache.md +389 -0
  38. package/docs/api/classes/RBACEngine.md +181 -0
  39. package/docs/api/classes/SecureSupabaseClient.md +168 -0
  40. package/docs/api/classes/StorageUtils.md +324 -0
  41. package/docs/api/enumerations/FileCategory.md +137 -0
  42. package/docs/api/enumerations/LogLevel.md +43 -0
  43. package/docs/api/enumerations/RBACErrorCode.md +169 -0
  44. package/docs/api/enumerations/RPCFunction.md +89 -0
  45. package/docs/api/functions/AccessDenied.md +30 -0
  46. package/docs/api/functions/AppSwitcher.md +21 -0
  47. package/docs/api/functions/Badge.md +42 -0
  48. package/docs/api/functions/ContextSelector.md +43 -0
  49. package/docs/api/functions/DataTable.md +36 -0
  50. package/docs/api/functions/DatePickerWithTimezone.md +28 -0
  51. package/docs/api/functions/DialogBody.md +24 -0
  52. package/docs/api/functions/DialogFooter.md +24 -0
  53. package/docs/api/functions/DialogHeader.md +24 -0
  54. package/docs/api/functions/ErrorBoundaryProvider.md +28 -0
  55. package/docs/api/functions/EventServiceProvider.md +28 -0
  56. package/docs/api/functions/FileDisplay.md +25 -0
  57. package/docs/api/functions/FileUpload.md +21 -0
  58. package/docs/api/functions/Form.md +50 -0
  59. package/docs/api/functions/FormField.md +102 -0
  60. package/docs/api/functions/Header.md +21 -0
  61. package/docs/api/functions/InactivityServiceProvider.md +28 -0
  62. package/docs/api/functions/InactivityWarningModal.md +21 -0
  63. package/docs/api/functions/Input.md +49 -0
  64. package/docs/api/functions/NavigationGuard.md +31 -0
  65. package/docs/api/functions/OrganisationServiceProvider.md +28 -0
  66. package/docs/api/functions/PaceAppLayout.md +169 -0
  67. package/docs/api/functions/PasswordChangeForm.md +21 -0
  68. package/docs/api/functions/ProtectedRoute.md +37 -0
  69. package/docs/api/functions/PublicPageFooter.md +21 -0
  70. package/docs/api/functions/PublicPageHeader.md +21 -0
  71. package/docs/api/functions/PublicPageLayout.md +33 -0
  72. package/docs/api/functions/PublicPageProvider.md +30 -0
  73. package/docs/api/functions/Textarea.md +49 -0
  74. package/docs/api/functions/Toaster.md +34 -0
  75. package/docs/api/functions/UnifiedAuthProvider.md +28 -0
  76. package/docs/api/functions/applyPalette.md +33 -0
  77. package/docs/api/functions/archiveFile.md +47 -0
  78. package/docs/api/functions/average.md +63 -0
  79. package/docs/api/functions/buildAppUrl.md +46 -0
  80. package/docs/api/functions/clearInFlightRequests.md +17 -0
  81. package/docs/api/functions/clearPalette.md +18 -0
  82. package/docs/api/functions/clearPublicEventCache.md +18 -0
  83. package/docs/api/functions/clearPublicFileDisplayCache.md +18 -0
  84. package/docs/api/functions/clearPublicLogoCache.md +18 -0
  85. package/docs/api/functions/cn.md +21 -0
  86. package/docs/api/functions/count.md +56 -0
  87. package/docs/api/functions/createAuditManager.md +45 -0
  88. package/docs/api/functions/createBaseClient.md +75 -0
  89. package/docs/api/functions/createLogger.md +95 -0
  90. package/docs/api/functions/createRBACConfig.md +21 -0
  91. package/docs/api/functions/createRBACEngine.md +33 -0
  92. package/docs/api/functions/createRBACExpressMiddleware.md +84 -0
  93. package/docs/api/functions/createRBACMiddleware.md +88 -0
  94. package/docs/api/functions/createSecureClient.md +80 -0
  95. package/docs/api/functions/createSecureDataAccess.md +39 -0
  96. package/docs/api/functions/deleteFile.md +33 -0
  97. package/docs/api/functions/disablePerformanceMonitoring.md +17 -0
  98. package/docs/api/functions/downloadFile.md +33 -0
  99. package/docs/api/functions/emitAuditEvent.md +27 -0
  100. package/docs/api/functions/enablePerformanceMonitoring.md +17 -0
  101. package/docs/api/functions/err.md +23 -0
  102. package/docs/api/functions/exportToCSV.md +56 -0
  103. package/docs/api/functions/exportToCSVWithTableRows.md +46 -0
  104. package/docs/api/functions/extractEventCodeFromPath.md +24 -0
  105. package/docs/api/functions/extractFileMetadata.md +33 -0
  106. package/docs/api/functions/formatCompactNumber.md +27 -0
  107. package/docs/api/functions/formatCurrency.md +31 -0
  108. package/docs/api/functions/formatDate.md +23 -0
  109. package/docs/api/functions/formatDateTime.md +24 -0
  110. package/docs/api/functions/formatFileSize.md +23 -0
  111. package/docs/api/functions/formatInTimeZone.md +46 -0
  112. package/docs/api/functions/formatNumber.md +31 -0
  113. package/docs/api/functions/formatPercent.md +64 -0
  114. package/docs/api/functions/formatTime.md +24 -0
  115. package/docs/api/functions/formatTimeInTimeZone.md +40 -0
  116. package/docs/api/functions/fromSupabaseClient.md +49 -0
  117. package/docs/api/functions/fromZonedTime.md +41 -0
  118. package/docs/api/functions/generateCSVContent.md +55 -0
  119. package/docs/api/functions/generateFilePath.md +29 -0
  120. package/docs/api/functions/generateFileUrlsBatch.md +33 -0
  121. package/docs/api/functions/generatePublicRoutePath.md +27 -0
  122. package/docs/api/functions/generateUniqueFileName.md +24 -0
  123. package/docs/api/functions/getAccessLevel.md +48 -0
  124. package/docs/api/functions/getAllAppPorts.md +19 -0
  125. package/docs/api/functions/getAllStylePaths.md +15 -0
  126. package/docs/api/functions/getAppConfig.md +17 -0
  127. package/docs/api/functions/getAppPort.md +34 -0
  128. package/docs/api/functions/getBucketName.md +27 -0
  129. package/docs/api/functions/getCurrentAppId.md +17 -0
  130. package/docs/api/functions/getCurrentAppName.md +17 -0
  131. package/docs/api/functions/getFileSizeLimit.md +23 -0
  132. package/docs/api/functions/getGlobalAuditManager.md +19 -0
  133. package/docs/api/functions/getInFlightRequestCount.md +19 -0
  134. package/docs/api/functions/getPerformanceMetrics.md +17 -0
  135. package/docs/api/functions/getPerformanceSummary.md +17 -0
  136. package/docs/api/functions/getPermissionMap.md +52 -0
  137. package/docs/api/functions/getPublicEventCacheStats.md +25 -0
  138. package/docs/api/functions/getPublicFileDisplayCacheStats.md +25 -0
  139. package/docs/api/functions/getPublicLogoCacheStats.md +25 -0
  140. package/docs/api/functions/getPublicUrl.md +39 -0
  141. package/docs/api/functions/getRBACConfig.md +15 -0
  142. package/docs/api/functions/getRBACLogger.md +15 -0
  143. package/docs/api/functions/getRoleContext.md +31 -0
  144. package/docs/api/functions/getSignedUrl.md +34 -0
  145. package/docs/api/functions/getStylePath.md +21 -0
  146. package/docs/api/functions/getTimeZoneDifference.md +40 -0
  147. package/docs/api/functions/getTimezoneAbbreviation.md +40 -0
  148. package/docs/api/functions/getUserTimeZone.md +26 -0
  149. package/docs/api/functions/hasAllPermissions.md +41 -0
  150. package/docs/api/functions/hasAnyPermission.md +41 -0
  151. package/docs/api/functions/isDebugMode.md +15 -0
  152. package/docs/api/functions/isDevelopmentMode.md +15 -0
  153. package/docs/api/functions/isErr.md +29 -0
  154. package/docs/api/functions/isOk.md +29 -0
  155. package/docs/api/functions/isPerformanceMonitoringEnabled.md +17 -0
  156. package/docs/api/functions/isPermitted.md +58 -0
  157. package/docs/api/functions/isPermittedCached.md +36 -0
  158. package/docs/api/functions/isRBACInitialized.md +19 -0
  159. package/docs/api/functions/isSecureClient.md +38 -0
  160. package/docs/api/functions/isValidPermission.md +27 -0
  161. package/docs/api/functions/listFiles.md +29 -0
  162. package/docs/api/functions/max.md +63 -0
  163. package/docs/api/functions/min.md +63 -0
  164. package/docs/api/functions/ok.md +29 -0
  165. package/docs/api/functions/parseAndNormalizeEventColours.md +105 -0
  166. package/docs/api/functions/recordAuditEvent.md +23 -0
  167. package/docs/api/functions/recordPermissionCheck.md +31 -0
  168. package/docs/api/functions/resetPerformanceMetrics.md +17 -0
  169. package/docs/api/functions/resolveAppContext.md +27 -0
  170. package/docs/api/functions/roundToNearestMinutes.md +41 -0
  171. package/docs/api/functions/sanitizeFormData.md +49 -0
  172. package/docs/api/functions/sanitizeHtml.md +39 -0
  173. package/docs/api/functions/sanitizeUserInput.md +27 -0
  174. package/docs/api/functions/setAppConfig.md +23 -0
  175. package/docs/api/functions/setGlobalAuditManager.md +25 -0
  176. package/docs/api/functions/setupRBAC.md +31 -0
  177. package/docs/api/functions/sum.md +63 -0
  178. package/docs/api/functions/toZonedTime.md +41 -0
  179. package/docs/api/functions/uploadFile.md +32 -0
  180. package/docs/api/functions/useAccessLevel.md +71 -0
  181. package/docs/api/functions/useAccessibleApps.md +55 -0
  182. package/docs/api/functions/useAppConfig.md +20 -0
  183. package/docs/api/functions/useAuthService.md +15 -0
  184. package/docs/api/functions/useCan.md +99 -0
  185. package/docs/api/functions/useEventService.md +15 -0
  186. package/docs/api/functions/useEventTheme.md +26 -0
  187. package/docs/api/functions/useEvents.md +45 -0
  188. package/docs/api/functions/useFileReference.md +264 -0
  189. package/docs/api/functions/useFileReferenceById.md +63 -0
  190. package/docs/api/functions/useFileReferenceForRecord.md +129 -0
  191. package/docs/api/functions/useFilesByCategory.md +80 -0
  192. package/docs/api/functions/useFormDialog.md +62 -0
  193. package/docs/api/functions/useInactivityService.md +15 -0
  194. package/docs/api/functions/useInactivityTracker.md +21 -0
  195. package/docs/api/functions/useIsPublicPage.md +19 -0
  196. package/docs/api/functions/useMultiplePermissions.md +88 -0
  197. package/docs/api/functions/useOptionalEvents.md +31 -0
  198. package/docs/api/functions/useOrganisationPermissions.md +27 -0
  199. package/docs/api/functions/useOrganisationSecurity.md +15 -0
  200. package/docs/api/functions/useOrganisationService.md +15 -0
  201. package/docs/api/functions/useOrganisations.md +48 -0
  202. package/docs/api/functions/usePermissions.md +130 -0
  203. package/docs/api/functions/usePublicEvent.md +36 -0
  204. package/docs/api/functions/usePublicEventCode.md +32 -0
  205. package/docs/api/functions/usePublicEventLogo.md +48 -0
  206. package/docs/api/functions/usePublicFileDisplay.md +54 -0
  207. package/docs/api/functions/usePublicPageContext.md +19 -0
  208. package/docs/api/functions/usePublicRouteParams.md +31 -0
  209. package/docs/api/functions/useRBAC.md +21 -0
  210. package/docs/api/functions/useResolvedScope.md +46 -0
  211. package/docs/api/functions/useResourcePermissions.md +25 -0
  212. package/docs/api/functions/useRoleManagement.md +121 -0
  213. package/docs/api/functions/useSecureSupabase.md +51 -0
  214. package/docs/api/functions/useSessionRestoration.md +15 -0
  215. package/docs/api/functions/useSessionTracking.md +62 -0
  216. package/docs/api/functions/useToast.md +83 -0
  217. package/docs/api/functions/useUnifiedAuth.md +24 -0
  218. package/docs/api/functions/useZodForm.md +27 -0
  219. package/docs/api/functions/validateFileSize.md +31 -0
  220. package/docs/api/functions/warnIfInsecureClient.md +40 -0
  221. package/docs/api/functions/withAccessLevelGuard.md +67 -0
  222. package/docs/api/functions/withPermissionGuard.md +73 -0
  223. package/docs/api/functions/withRoleGuard.md +86 -0
  224. package/docs/api/globals.md +502 -0
  225. package/docs/api/interfaces/AccessDeniedProps.md +87 -0
  226. package/docs/api/interfaces/AccessibleApp.md +41 -0
  227. package/docs/api/interfaces/AddressFieldProps.md +195 -0
  228. package/docs/api/interfaces/AddressFieldRef.md +67 -0
  229. package/docs/api/interfaces/AggregateConfig.md +35 -0
  230. package/docs/api/interfaces/AppSwitcherProps.md +51 -0
  231. package/docs/api/interfaces/AuthSessionData.md +27 -0
  232. package/docs/api/interfaces/AutocompleteOptions.md +61 -0
  233. package/docs/api/interfaces/AvatarProps.md +97 -0
  234. package/docs/api/interfaces/BadgeProps.md +30 -0
  235. package/docs/api/interfaces/BuildAppUrlOptions.md +41 -0
  236. package/docs/api/interfaces/ButtonProps.md +46 -0
  237. package/docs/api/interfaces/CalendarProps.md +60 -0
  238. package/docs/api/interfaces/CardProps.md +56 -0
  239. package/docs/api/interfaces/ColorPalette.md +13 -0
  240. package/docs/api/interfaces/ColorShade.md +58 -0
  241. package/docs/api/interfaces/ContextSelectorProps.md +131 -0
  242. package/docs/api/interfaces/DataRecord.md +16 -0
  243. package/docs/api/interfaces/DataTableAction.md +198 -0
  244. package/docs/api/interfaces/DataTableColumn.md +422 -0
  245. package/docs/api/interfaces/DataTableProps.md +511 -0
  246. package/docs/api/interfaces/DataTableToolbarButton.md +75 -0
  247. package/docs/api/interfaces/DatePickerWithTimezoneProps.md +75 -0
  248. package/docs/api/interfaces/DialogBodyProps.md +55 -0
  249. package/docs/api/interfaces/DialogCloseProps.md +25 -0
  250. package/docs/api/interfaces/DialogContentProps.md +160 -0
  251. package/docs/api/interfaces/DialogFooterProps.md +25 -0
  252. package/docs/api/interfaces/DialogHeaderProps.md +25 -0
  253. package/docs/api/interfaces/DialogPortalProps.md +19 -0
  254. package/docs/api/interfaces/DialogProps.md +53 -0
  255. package/docs/api/interfaces/DialogTriggerProps.md +53 -0
  256. package/docs/api/interfaces/EmptyStateConfig.md +55 -0
  257. package/docs/api/interfaces/ErrorBoundaryProps.md +131 -0
  258. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +31 -0
  259. package/docs/api/interfaces/ErrorBoundaryState.md +61 -0
  260. package/docs/api/interfaces/EventAppRoleData.md +54 -0
  261. package/docs/api/interfaces/ExportColumn.md +69 -0
  262. package/docs/api/interfaces/ExportOptions.md +109 -0
  263. package/docs/api/interfaces/FileDisplayProps.md +192 -0
  264. package/docs/api/interfaces/FileMetadata.md +97 -0
  265. package/docs/api/interfaces/FileReference.md +89 -0
  266. package/docs/api/interfaces/FileSizeLimits.md +13 -0
  267. package/docs/api/interfaces/FileUploadOptions.md +107 -0
  268. package/docs/api/interfaces/FooterProps.md +37 -0
  269. package/docs/api/interfaces/FormFieldProps.md +171 -0
  270. package/docs/api/interfaces/FormProps.md +93 -0
  271. package/docs/api/interfaces/GrantEventAppRoleParams.md +97 -0
  272. package/docs/api/interfaces/ImportSummary.md +49 -0
  273. package/docs/api/interfaces/InactivityWarningModalProps.md +87 -0
  274. package/docs/api/interfaces/InputProps.md +46 -0
  275. package/docs/api/interfaces/InvalidScopeError.md +37 -0
  276. package/docs/api/interfaces/LabelProps.md +85 -0
  277. package/docs/api/interfaces/LoggerConfig.md +51 -0
  278. package/docs/api/interfaces/LoginFormProps.md +146 -0
  279. package/docs/api/interfaces/MissingUserContextError.md +37 -0
  280. package/docs/api/interfaces/NavigationGuardProps.md +109 -0
  281. package/docs/api/interfaces/NavigationItem.md +91 -0
  282. package/docs/api/interfaces/NavigationMenuProps.md +169 -0
  283. package/docs/api/interfaces/Organisation.md +105 -0
  284. package/docs/api/interfaces/OrganisationContextRequiredError.md +37 -0
  285. package/docs/api/interfaces/OrganisationMembership.md +105 -0
  286. package/docs/api/interfaces/OrganisationSecurityError.md +49 -0
  287. package/docs/api/interfaces/PaceAppLayoutPermissionConfig.md +127 -0
  288. package/docs/api/interfaces/PaceAppLayoutRouteConfigItem.md +91 -0
  289. package/docs/api/interfaces/PaceAppLayoutRoutingConfig.md +79 -0
  290. package/docs/api/interfaces/PaceLoginPageProps.md +41 -0
  291. package/docs/api/interfaces/PagePermissionGuardProps.md +143 -0
  292. package/docs/api/interfaces/PaletteData.md +33 -0
  293. package/docs/api/interfaces/ParsedAddress.md +91 -0
  294. package/docs/api/interfaces/PermissionDeniedError.md +37 -0
  295. package/docs/api/interfaces/ProgressProps.md +35 -0
  296. package/docs/api/interfaces/ProtectedRouteProps.md +67 -0
  297. package/docs/api/interfaces/PublicPageFooterProps.md +97 -0
  298. package/docs/api/interfaces/PublicPageHeaderProps.md +99 -0
  299. package/docs/api/interfaces/PublicPageLayoutProps.md +153 -0
  300. package/docs/api/interfaces/RBACAccessValidateParams.md +41 -0
  301. package/docs/api/interfaces/RBACAccessValidateResult.md +33 -0
  302. package/docs/api/interfaces/RBACAuditLogParams.md +65 -0
  303. package/docs/api/interfaces/RBACAuditLogResult.md +41 -0
  304. package/docs/api/interfaces/RBACContext.md +41 -0
  305. package/docs/api/interfaces/RBACError.md +37 -0
  306. package/docs/api/interfaces/RBACLogger.md +97 -0
  307. package/docs/api/interfaces/RBACNotInitializedError.md +37 -0
  308. package/docs/api/interfaces/RBACPageAccessCheckParams.md +57 -0
  309. package/docs/api/interfaces/RBACPerformanceMetrics.md +109 -0
  310. package/docs/api/interfaces/RBACPermissionCheckParams.md +57 -0
  311. package/docs/api/interfaces/RBACPermissionCheckResult.md +41 -0
  312. package/docs/api/interfaces/RBACPermissionsGetParams.md +49 -0
  313. package/docs/api/interfaces/RBACPermissionsGetResult.md +49 -0
  314. package/docs/api/interfaces/RBACResult.md +47 -0
  315. package/docs/api/interfaces/RBACRoleGrantParams.md +49 -0
  316. package/docs/api/interfaces/RBACRoleGrantResult.md +41 -0
  317. package/docs/api/interfaces/RBACRoleRevokeParams.md +49 -0
  318. package/docs/api/interfaces/RBACRoleRevokeResult.md +41 -0
  319. package/docs/api/interfaces/RBACRoleValidateParams.md +41 -0
  320. package/docs/api/interfaces/RBACRoleValidateResult.md +49 -0
  321. package/docs/api/interfaces/RBACRolesListParams.md +41 -0
  322. package/docs/api/interfaces/RBACRolesListResult.md +57 -0
  323. package/docs/api/interfaces/RBACSessionTrackParams.md +57 -0
  324. package/docs/api/interfaces/RBACSessionTrackResult.md +41 -0
  325. package/docs/api/interfaces/ResourcePermissions.md +119 -0
  326. package/docs/api/interfaces/RevokeEventAppRoleParams.md +81 -0
  327. package/docs/api/interfaces/RoleManagementResult.md +41 -0
  328. package/docs/api/interfaces/SessionRestorationLoaderProps.md +29 -0
  329. package/docs/api/interfaces/StorageConfig.md +33 -0
  330. package/docs/api/interfaces/StorageFileInfo.md +57 -0
  331. package/docs/api/interfaces/StorageFileMetadata.md +113 -0
  332. package/docs/api/interfaces/StorageListOptions.md +79 -0
  333. package/docs/api/interfaces/StorageListResult.md +33 -0
  334. package/docs/api/interfaces/StorageUploadOptions.md +81 -0
  335. package/docs/api/interfaces/StorageUploadResult.md +49 -0
  336. package/docs/api/interfaces/StorageUploadSuccess.md +35 -0
  337. package/docs/api/interfaces/StorageUrlOptions.md +49 -0
  338. package/docs/api/interfaces/StyleImport.md +17 -0
  339. package/docs/api/interfaces/SwitchProps.md +30 -0
  340. package/docs/api/interfaces/TabsContentProps.md +13 -0
  341. package/docs/api/interfaces/TabsListProps.md +13 -0
  342. package/docs/api/interfaces/TabsProps.md +13 -0
  343. package/docs/api/interfaces/TabsTriggerProps.md +41 -0
  344. package/docs/api/interfaces/TextareaProps.md +43 -0
  345. package/docs/api/interfaces/ToastActionElement.md +16 -0
  346. package/docs/api/interfaces/ToastProps.md +13 -0
  347. package/docs/api/interfaces/UnifiedAuthProviderProps.md +129 -0
  348. package/docs/api/interfaces/UseAccessibleAppsReturn.md +55 -0
  349. package/docs/api/interfaces/UseFormDialogOptions.md +49 -0
  350. package/docs/api/interfaces/UseFormDialogReturn.md +95 -0
  351. package/docs/api/interfaces/UseInactivityTrackerOptions.md +103 -0
  352. package/docs/api/interfaces/UseInactivityTrackerReturn.md +91 -0
  353. package/docs/api/interfaces/UsePublicEventLogoOptions.md +69 -0
  354. package/docs/api/interfaces/UsePublicEventLogoReturn.md +66 -0
  355. package/docs/api/interfaces/UsePublicEventOptions.md +29 -0
  356. package/docs/api/interfaces/UsePublicEventReturn.md +56 -0
  357. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +39 -0
  358. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +96 -0
  359. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +76 -0
  360. package/docs/api/interfaces/UseResolvedScopeOptions.md +49 -0
  361. package/docs/api/interfaces/UseResolvedScopeReturn.md +39 -0
  362. package/docs/api/interfaces/UseResourcePermissionsOptions.md +29 -0
  363. package/docs/api/interfaces/UserEventAccess.md +92 -0
  364. package/docs/api/interfaces/UserMenuProps.md +69 -0
  365. package/docs/api/interfaces/UserProfile.md +49 -0
  366. package/docs/api/type-aliases/AccessLevel.md +11 -0
  367. package/docs/api/type-aliases/AccessLevelContext.md +14 -0
  368. package/docs/api/type-aliases/AllPermissions.md +11 -0
  369. package/docs/api/type-aliases/ApiError.md +37 -0
  370. package/docs/api/type-aliases/ApiResult.md +19 -0
  371. package/docs/api/type-aliases/AuditEventType.md +11 -0
  372. package/docs/api/type-aliases/BadgeVariant.md +18 -0
  373. package/docs/api/type-aliases/DataTableFeatureConfig.md +14 -0
  374. package/docs/api/type-aliases/DialogSize.md +13 -0
  375. package/docs/api/type-aliases/EventAppRole.md +11 -0
  376. package/docs/api/type-aliases/FileUploadProps.md +15 -0
  377. package/docs/api/type-aliases/GetRowId.md +33 -0
  378. package/docs/api/type-aliases/GlobalErrorHandler.md +35 -0
  379. package/docs/api/type-aliases/GlobalRole.md +11 -0
  380. package/docs/api/type-aliases/ImportHandlerResult.md +13 -0
  381. package/docs/api/type-aliases/NavigationMode.md +13 -0
  382. package/docs/api/type-aliases/Operation.md +11 -0
  383. package/docs/api/type-aliases/OrganisationContextType.md +13 -0
  384. package/docs/api/type-aliases/OrganisationRole.md +11 -0
  385. package/docs/api/type-aliases/PaceAppLayoutProps.md +14 -0
  386. package/docs/api/type-aliases/Permission.md +11 -0
  387. package/docs/api/type-aliases/PermissionCheck.md +46 -0
  388. package/docs/api/type-aliases/PermissionMap.md +11 -0
  389. package/docs/api/type-aliases/PermissionSource.md +13 -0
  390. package/docs/api/type-aliases/RBACConfig.md +13 -0
  391. package/docs/api/type-aliases/RBACFunctionResponse.md +57 -0
  392. package/docs/api/type-aliases/Scope.md +41 -0
  393. package/docs/api/type-aliases/SessionType.md +11 -0
  394. package/docs/api/type-aliases/UUID.md +11 -0
  395. package/docs/api/type-aliases/UnifiedAuthContextType.md +13 -0
  396. package/docs/api/type-aliases/UseFileReferenceForRecordReturn.md +161 -0
  397. package/docs/api/type-aliases/UseFileReferenceOptions.md +35 -0
  398. package/docs/api/type-aliases/UseFileReferenceReturn.md +13 -0
  399. package/docs/api/variables/ALL_PERMISSIONS.md +281 -0
  400. package/docs/api/variables/APP_PATH_MAPPING.md +14 -0
  401. package/docs/api/variables/AddressField.md +41 -0
  402. package/docs/api/variables/Alert.md +11 -0
  403. package/docs/api/variables/AlertDescription.md +11 -0
  404. package/docs/api/variables/AlertTitle.md +11 -0
  405. package/docs/api/variables/Avatar.md +13 -0
  406. package/docs/api/variables/Button.md +31 -0
  407. package/docs/api/variables/CACHE_PATTERNS.md +89 -0
  408. package/docs/api/variables/Calendar.md +74 -0
  409. package/docs/api/variables/Card.md +11 -0
  410. package/docs/api/variables/CardActions.md +11 -0
  411. package/docs/api/variables/CardContent.md +11 -0
  412. package/docs/api/variables/CardDescription.md +11 -0
  413. package/docs/api/variables/CardFooter.md +11 -0
  414. package/docs/api/variables/CardHeader.md +11 -0
  415. package/docs/api/variables/CardTitle.md +11 -0
  416. package/docs/api/variables/Checkbox.md +11 -0
  417. package/docs/api/variables/DEFAULT_APP_PORT_MAP.md +14 -0
  418. package/docs/api/variables/DEFAULT_FILE_SIZE_LIMIT.md +13 -0
  419. package/docs/api/variables/Dialog.md +14 -0
  420. package/docs/api/variables/DialogClose.md +14 -0
  421. package/docs/api/variables/DialogContent.md +28 -0
  422. package/docs/api/variables/DialogDescription.md +14 -0
  423. package/docs/api/variables/DialogPortal.md +14 -0
  424. package/docs/api/variables/DialogTitle.md +14 -0
  425. package/docs/api/variables/DialogTrigger.md +14 -0
  426. package/docs/api/variables/EVENT_APP_PERMISSIONS.md +109 -0
  427. package/docs/api/variables/ErrorBoundary.md +15 -0
  428. package/docs/api/variables/FILE_SIZE_LIMITS.md +13 -0
  429. package/docs/api/variables/Footer.md +11 -0
  430. package/docs/api/variables/GLOBAL_PERMISSIONS.md +29 -0
  431. package/docs/api/variables/Label.md +34 -0
  432. package/docs/api/variables/LoadingSpinner.md +28 -0
  433. package/docs/api/variables/LoginForm.md +34 -0
  434. package/docs/api/variables/NavigationMenu.md +203 -0
  435. package/docs/api/variables/ORGANISATION_PERMISSIONS.md +89 -0
  436. package/docs/api/variables/PAGE_PERMISSIONS.md +93 -0
  437. package/docs/api/variables/PaceLoginPage.md +40 -0
  438. package/docs/api/variables/PagePermissionGuard.md +11 -0
  439. package/docs/api/variables/Progress.md +32 -0
  440. package/docs/api/variables/SECURE_CLIENT_SYMBOL.md +14 -0
  441. package/docs/api/variables/STORAGE_CONFIG.md +13 -0
  442. package/docs/api/variables/Select.md +26 -0
  443. package/docs/api/variables/SelectContent.md +26 -0
  444. package/docs/api/variables/SelectGroup.md +26 -0
  445. package/docs/api/variables/SelectItem.md +26 -0
  446. package/docs/api/variables/SelectLabel.md +26 -0
  447. package/docs/api/variables/SelectSeparator.md +26 -0
  448. package/docs/api/variables/SelectTrigger.md +26 -0
  449. package/docs/api/variables/SelectValue.md +26 -0
  450. package/docs/api/variables/SessionRestorationLoader.md +11 -0
  451. package/docs/api/variables/Switch.md +23 -0
  452. package/docs/api/variables/Table.md +35 -0
  453. package/docs/api/variables/TableBody.md +11 -0
  454. package/docs/api/variables/TableCaption.md +11 -0
  455. package/docs/api/variables/TableCell.md +11 -0
  456. package/docs/api/variables/TableFooter.md +11 -0
  457. package/docs/api/variables/TableHead.md +11 -0
  458. package/docs/api/variables/TableHeader.md +11 -0
  459. package/docs/api/variables/TableRow.md +11 -0
  460. package/docs/api/variables/Tabs.md +25 -0
  461. package/docs/api/variables/TabsContent.md +24 -0
  462. package/docs/api/variables/TabsList.md +25 -0
  463. package/docs/api/variables/TabsTrigger.md +34 -0
  464. package/docs/api/variables/Toast.md +36 -0
  465. package/docs/api/variables/ToastAction.md +32 -0
  466. package/docs/api/variables/ToastClose.md +32 -0
  467. package/docs/api/variables/ToastDescription.md +32 -0
  468. package/docs/api/variables/ToastProvider.md +11 -0
  469. package/docs/api/variables/ToastTitle.md +32 -0
  470. package/docs/api/variables/ToastViewport.md +26 -0
  471. package/docs/api/variables/Tooltip.md +34 -0
  472. package/docs/api/variables/TooltipContent.md +34 -0
  473. package/docs/api/variables/TooltipProvider.md +11 -0
  474. package/docs/api/variables/TooltipRoot.md +11 -0
  475. package/docs/api/variables/TooltipTrigger.md +11 -0
  476. package/docs/api/variables/UserMenu.md +11 -0
  477. package/docs/api/variables/emailSchema.md +13 -0
  478. package/docs/api/variables/logger.md +203 -0
  479. package/docs/api/variables/nameSchema.md +13 -0
  480. package/docs/api/variables/passwordSchema.md +13 -0
  481. package/docs/api/variables/phoneSchema.md +13 -0
  482. package/docs/api/variables/rbacCache.md +16 -0
  483. package/docs/api/variables/styleConfig.md +25 -0
  484. package/docs/api/variables/urlSchema.md +13 -0
  485. package/docs/api-reference/hooks.md +2 -0
  486. package/docs/implementation-guides/data-tables.md +8 -0
  487. package/docs/rbac/getting-started.md +7 -0
  488. package/docs/rbac/troubleshooting.md +5 -1
  489. package/package.json +3 -3
  490. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +29 -19
  491. package/src/components/DataTable/hooks/useDataTableScope.test.ts +5 -13
  492. package/src/components/DataTable/hooks/useDataTableScope.ts +16 -14
  493. package/src/components/Dialog/useDialogLifecycle.test.ts +4 -1
  494. package/src/components/FileDisplay/useFileDisplay.unit.test.ts +12 -8
  495. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +33 -9
  496. package/src/components/PaceAppLayout/useFilteredNavItems.ts +22 -7
  497. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +44 -23
  498. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +1 -1
  499. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +6 -4
  500. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +2 -2
  501. package/src/hooks/useAppConfig.unit.test.ts +74 -66
  502. package/src/hooks/useComponentPerformance.unit.test.tsx +6 -4
  503. package/src/hooks/useFileUrl.unit.test.ts +1 -3
  504. package/src/hooks/useInactivityTracker.unit.test.ts +6 -2
  505. package/src/hooks/usePerformanceMonitor.unit.test.ts +6 -16
  506. package/src/hooks/usePublicEvent.simple.test.ts +32 -47
  507. package/src/hooks/usePublicEvent.test.ts +9 -15
  508. package/src/providers/services/AuthServiceProvider.test.tsx +10 -5
  509. package/src/providers/services/EventServiceProvider.test.tsx +8 -3
  510. package/src/providers/services/InactivityServiceProvider.test.tsx +8 -3
  511. package/src/providers/services/OrganisationServiceProvider.test.tsx +8 -3
  512. package/src/rbac/README.md +7 -5
  513. package/src/rbac/api.test.ts +113 -56
  514. package/src/rbac/api.ts +80 -10
  515. package/src/rbac/components/NavigationGuard.tsx +2 -1
  516. package/src/rbac/components/PagePermissionGuard.test.tsx +23 -10
  517. package/src/rbac/engine.ts +23 -1
  518. package/src/rbac/hooks/permissions/runPermissionCheck.ts +18 -4
  519. package/src/rbac/hooks/permissions/useCan.test.ts +59 -20
  520. package/src/rbac/hooks/permissions/useCan.ts +7 -3
  521. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +18 -9
  522. package/src/rbac/hooks/useCan.test.ts +2 -3
  523. package/src/rbac/hooks/usePageGuardScope.ts +6 -4
  524. package/src/rbac/hooks/usePagePermissionCheck.ts +4 -4
  525. package/src/rbac/hooks/useResolvedScope.ts +16 -10
  526. package/src/rbac/hooks/useResourcePermissions.test.ts +48 -58
  527. package/src/rbac/hooks/useResourcePermissions.ts +11 -20
  528. package/src/rbac/types.ts +3 -0
  529. package/src/services/AuthService.edge-cases.test.ts +2 -2
  530. package/src/services/EventService.ts +9 -4
  531. package/src/utils/file-reference/file-reference.test.ts +25 -14
  532. package/src/utils/supabase/createBaseClient.test.ts +30 -13
  533. package/docs/api/modules.md +0 -10028
@@ -45,6 +45,11 @@ vi.mock('../../utils/app/appNameResolver', () => ({
45
45
  getCurrentAppName: vi.fn()
46
46
  }));
47
47
 
48
+ // Mock useSuperAdminCheck so guard does not wait on async super-admin check
49
+ vi.mock('../hooks/useSuperAdminCheck', () => ({
50
+ useSuperAdminCheck: () => ({ isSuperAdmin: false })
51
+ }));
52
+
48
53
  // Mock the RBAC API for super admin checks
49
54
  vi.mock('../api', async () => {
50
55
  const actual = await vi.importActual('../api');
@@ -546,7 +551,7 @@ describe('PagePermissionGuard Component', () => {
546
551
  'read:page.dashboard',
547
552
  'dashboard',
548
553
  true,
549
- null, // precomputedSuperAdmin
554
+ false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
550
555
  'test-app'
551
556
  );
552
557
  });
@@ -582,7 +587,7 @@ describe('PagePermissionGuard Component', () => {
582
587
  'read:page.dashboard',
583
588
  'dashboard',
584
589
  true,
585
- null, // precomputedSuperAdmin
590
+ false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
586
591
  'test-app'
587
592
  );
588
593
  });
@@ -650,7 +655,7 @@ describe('PagePermissionGuard Component', () => {
650
655
  'read:page.dashboard',
651
656
  'dashboard',
652
657
  true,
653
- null, // precomputedSuperAdmin
658
+ false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
654
659
  'test-app'
655
660
  );
656
661
  });
@@ -719,7 +724,7 @@ describe('PagePermissionGuard Component', () => {
719
724
  'read:page.dashboard',
720
725
  'dashboard',
721
726
  true,
722
- null, // precomputedSuperAdmin
727
+ false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
723
728
  'test-app'
724
729
  );
725
730
  });
@@ -1268,7 +1273,7 @@ describe('PagePermissionGuard Component', () => {
1268
1273
  'read:page.dashboard',
1269
1274
  'dashboard',
1270
1275
  true,
1271
- null, // precomputedSuperAdmin
1276
+ false, // precomputedSuperAdmin (from mocked useSuperAdminCheck)
1272
1277
  'PORTAL'
1273
1278
  );
1274
1279
  }, TEST_TIMEOUT);
@@ -1320,7 +1325,7 @@ describe('PagePermissionGuard Component', () => {
1320
1325
  'read:page.dashboard',
1321
1326
  'dashboard',
1322
1327
  true,
1323
- null,
1328
+ false,
1324
1329
  'ADMIN'
1325
1330
  );
1326
1331
  }, TEST_TIMEOUT);
@@ -1364,7 +1369,7 @@ describe('PagePermissionGuard Component', () => {
1364
1369
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
1365
1370
  }, { interval: 10 });
1366
1371
 
1367
- // Should use contextAppId as fallback
1372
+ // Should use contextAppId as fallback (precomputedSuperAdmin is false from mocked useSuperAdminCheck)
1368
1373
  expect(mockUseCanFn).toHaveBeenCalledWith(
1369
1374
  'user-123',
1370
1375
  expect.objectContaining({
@@ -1373,7 +1378,7 @@ describe('PagePermissionGuard Component', () => {
1373
1378
  'read:page.dashboard',
1374
1379
  'dashboard',
1375
1380
  true,
1376
- null,
1381
+ false,
1377
1382
  'PORTAL'
1378
1383
  );
1379
1384
  }, TEST_TIMEOUT);
@@ -1403,7 +1408,7 @@ describe('PagePermissionGuard Component', () => {
1403
1408
  error: null
1404
1409
  });
1405
1410
 
1406
- render(
1411
+ const { rerender } = render(
1407
1412
  <PagePermissionGuard
1408
1413
  pageName={mockPageName}
1409
1414
  operation={mockOperation}
@@ -1415,12 +1420,20 @@ describe('PagePermissionGuard Component', () => {
1415
1420
  // Component should show loading state
1416
1421
  expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
1417
1422
 
1418
- // Then resolve the permission check
1423
+ // Then resolve the permission check and trigger re-render so the guard sees the update
1419
1424
  mockUseCanFn.mockReturnValue({
1420
1425
  can: true,
1421
1426
  isLoading: false,
1422
1427
  error: null
1423
1428
  });
1429
+ rerender(
1430
+ <PagePermissionGuard
1431
+ pageName={mockPageName}
1432
+ operation={mockOperation}
1433
+ >
1434
+ <TestComponent>Protected Page</TestComponent>
1435
+ </PagePermissionGuard>
1436
+ );
1424
1437
 
1425
1438
  await waitFor(() => {
1426
1439
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
@@ -91,11 +91,28 @@ export class RBACEngine {
91
91
  // Validate input
92
92
  const validation = await this.securityMiddleware.validateInput(input, securityContext);
93
93
  if (!validation.isValid) {
94
+ const scopeRejected = validation.errors.includes('Invalid scope format');
95
+ getRBACLogger().warn('[RBAC] Validation failed — check that an event is selected if the Menu/units page requires it.', {
96
+ errors: validation.errors,
97
+ scope: input.scope,
98
+ permission: input.permission,
99
+ });
94
100
  RBACSecurityValidator.logSecurityEvent({
95
101
  type: 'invalid_input',
96
102
  userId,
97
- details: { errors: validation.errors, input: JSON.stringify(input) },
103
+ details: {
104
+ errors: validation.errors,
105
+ input: JSON.stringify(input),
106
+ ...(scopeRejected && { scopeRejected: input.scope }),
107
+ },
98
108
  });
109
+ if (scopeRejected) {
110
+ getRBACLogger().warn('[RBAC] Invalid scope (from middleware). Scope must have at least one of organisationId, eventId, appId; no empty strings.', {
111
+ scope: input.scope,
112
+ permission: input.permission,
113
+ userId,
114
+ });
115
+ }
99
116
  return false;
100
117
  }
101
118
 
@@ -137,6 +154,11 @@ export class RBACEngine {
137
154
  userId,
138
155
  details: { error: 'Invalid scope format', scope },
139
156
  });
157
+ getRBACLogger().warn('[RBAC] Invalid scope (engine check). Scope must have at least one of organisationId, eventId, appId; no empty strings.', {
158
+ scope,
159
+ permission,
160
+ userId,
161
+ });
140
162
  return false;
141
163
  }
142
164
 
@@ -1,6 +1,7 @@
1
1
  import type { ApiResult } from '../../../types/api-result';
2
2
  import type { Permission, Scope, UUID } from '../../types';
3
3
  import { isPermitted, isPermittedCached } from '../../api';
4
+ import { getRBACConfig, getRBACLogger } from '../../config';
4
5
 
5
6
  const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
7
 
@@ -56,16 +57,29 @@ export async function runPermissionCheck(params: {
56
57
  !UUID_REGEX.test(pageId);
57
58
  const needsAppIdForPageName = isPagePermission && !!isPageName;
58
59
 
59
- if (needsAppIdForPageName && isEmptyString(appId)) {
60
+ // Only deny early when appId is missing and we cannot resolve it (no eventId).
61
+ // When eventId is present, pass scope through so api layer can resolve appId from current app name.
62
+ if (needsAppIdForPageName && isEmptyString(appId) && isEmptyString(eventId)) {
60
63
  return { ok: true, data: false };
61
64
  }
62
65
 
66
+ // Build scope with only non-empty values so we never send scope that fails validateScope (no empty strings)
63
67
  const validScope: Scope = {
64
- ...(organisationId ? { organisationId } : {}),
65
- ...(eventId ? { eventId } : {}),
66
- ...(appId ? { appId } : {}),
68
+ ...(!isEmptyString(organisationId) && organisationId ? { organisationId } : {}),
69
+ ...(!isEmptyString(eventId) && eventId ? { eventId } : {}),
70
+ ...(!isEmptyString(appId) && appId ? { appId } : {}),
67
71
  };
68
72
 
73
+ // Never call engine with empty scope — validateScope requires at least one of org/event/app; otherwise engine logs invalid_input
74
+ if (!validScope.organisationId && !validScope.eventId && !validScope.appId) {
75
+ return { ok: true, data: false };
76
+ }
77
+
78
+ const config = getRBACConfig();
79
+ if (config?.debug || config?.logLevel === 'debug') {
80
+ getRBACLogger().debug('[RBAC] runPermissionCheck', { scope: validScope, permission, pageId, useCache });
81
+ }
82
+
69
83
  if (useCache) {
70
84
  return isPermittedCached({ userId, scope: validScope, permission, pageId }, appName);
71
85
  }
@@ -35,7 +35,7 @@ vi.mock('../../api', async () => {
35
35
  };
36
36
  });
37
37
 
38
- // Mock RBAC logger
38
+ // Mock RBAC config (runPermissionCheck calls getRBACConfig)
39
39
  vi.mock('../../config', () => ({
40
40
  getRBACLogger: vi.fn(() => ({
41
41
  warn: vi.fn(),
@@ -43,15 +43,21 @@ vi.mock('../../config', () => ({
43
43
  info: vi.fn(),
44
44
  debug: vi.fn(),
45
45
  })),
46
+ getRBACConfig: vi.fn().mockReturnValue({ debug: false, logLevel: 'warn' as const }),
46
47
  }));
47
48
 
48
49
  import { isPermitted, isPermittedCached, isSuperAdmin } from '../../api';
49
50
 
50
51
  const mockUserId = 'user-123';
52
+ // Valid UUIDs so RBACSecurityValidator.validateScope passes in API
53
+ const VALID_ORG_ID = '00000000-0000-0000-0000-000000000001';
54
+ const VALID_APP_ID = '00000000-0000-0000-0000-000000000002';
55
+ const VALID_EVENT_ID = '00000000-0000-0000-0000-000000000003';
56
+ const VALID_ORG_ID_2 = '00000000-0000-0000-0000-000000000099'; // second org for refetch tests
51
57
  const mockScope = {
52
- organisationId: 'org-123',
53
- eventId: 'event-123',
54
- appId: 'app-123',
58
+ organisationId: VALID_ORG_ID,
59
+ eventId: VALID_EVENT_ID,
60
+ appId: VALID_APP_ID,
55
61
  };
56
62
  const mockPermission = 'read:users' as const;
57
63
 
@@ -272,7 +278,7 @@ describe('useCan Hook', () => {
272
278
 
273
279
  it('times out when organisation context is missing for resource-level permission', async () => {
274
280
  vi.useFakeTimers();
275
- const scopeWithoutOrg = { eventId: 'event-123' };
281
+ const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
276
282
 
277
283
  const { result } = renderHook(() =>
278
284
  useCan(mockUserId, scopeWithoutOrg, mockPermission)
@@ -292,7 +298,7 @@ describe('useCan Hook', () => {
292
298
 
293
299
  it('allows undefined organisationId for page-level permissions', async () => {
294
300
  const pagePermission = 'read:page.users' as const;
295
- const scopeWithoutOrg = { eventId: 'event-123' };
301
+ const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
296
302
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
297
303
 
298
304
  const { result } = renderHook(() =>
@@ -310,7 +316,7 @@ describe('useCan Hook', () => {
310
316
  const pageId = 'page-123';
311
317
  // pageId is a UUID, so it doesn't need appId
312
318
  // Provide valid scope with eventId and appId
313
- const scopeWithoutOrg = { eventId: 'event-123', appId: 'app-123' };
319
+ const scopeWithoutOrg = { eventId: VALID_EVENT_ID, appId: VALID_APP_ID };
314
320
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
315
321
  // Set precomputedSuperAdmin to false to skip super admin check
316
322
  mockIsSuperAdmin.mockResolvedValue({ ok: true, data: false });
@@ -335,7 +341,7 @@ describe('useCan Hook', () => {
335
341
 
336
342
  it('waits for appId when pageId is a pageName (not UUID)', async () => {
337
343
  const pageName = 'users-page'; // Not a UUID
338
- const scopeWithoutAppId = { organisationId: 'org-123' };
344
+ const scopeWithoutAppId = { organisationId: VALID_ORG_ID };
339
345
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
340
346
 
341
347
  const { result, rerender } = renderHook(
@@ -349,7 +355,7 @@ describe('useCan Hook', () => {
349
355
  expect(result.current.isLoading).toBe(true);
350
356
 
351
357
  // Provide appId
352
- const scopeWithAppId = { ...scopeWithoutAppId, appId: 'app-123' };
358
+ const scopeWithAppId = { ...scopeWithoutAppId, appId: VALID_APP_ID };
353
359
  rerender({ scope: scopeWithAppId });
354
360
 
355
361
  await waitFor(() => {
@@ -361,9 +367,42 @@ describe('useCan Hook', () => {
361
367
  }, { timeout: WAIT_FOR_TIMEOUT });
362
368
  }, TEST_TIMEOUT);
363
369
 
370
+ it('calls API when scope has eventId but no appId for page-name permission (appId resolved in api)', async () => {
371
+ const pageName = 'items';
372
+ const pagePermission = 'read:page.items' as const;
373
+ const scopeWithEventNoApp = { organisationId: VALID_ORG_ID, eventId: VALID_EVENT_ID };
374
+ mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
375
+
376
+ const { result } = renderHook(() =>
377
+ useCan(mockUserId, scopeWithEventNoApp, pagePermission, pageName)
378
+ );
379
+
380
+ await waitFor(() => {
381
+ expect(mockIsPermittedCached).toHaveBeenCalled();
382
+ }, { timeout: WAIT_FOR_TIMEOUT });
383
+
384
+ await waitFor(() => {
385
+ expect(result.current.isLoading).toBe(false);
386
+ expect(result.current.can).toBe(true);
387
+ }, { timeout: WAIT_FOR_TIMEOUT });
388
+
389
+ expect(mockIsPermittedCached).toHaveBeenCalledWith(
390
+ expect.objectContaining({
391
+ userId: mockUserId,
392
+ scope: expect.objectContaining({
393
+ organisationId: VALID_ORG_ID,
394
+ eventId: VALID_EVENT_ID,
395
+ }),
396
+ permission: pagePermission,
397
+ pageId: pageName,
398
+ }),
399
+ undefined
400
+ );
401
+ }, TEST_TIMEOUT);
402
+
364
403
  it('clears error when organisation context becomes available', async () => {
365
404
  vi.useFakeTimers();
366
- const scopeWithoutOrg = { eventId: 'event-123' };
405
+ const scopeWithoutOrg = { eventId: VALID_EVENT_ID };
367
406
 
368
407
  const { result, rerender } = renderHook(
369
408
  ({ scope }) => useCan(mockUserId, scope, mockPermission),
@@ -381,7 +420,7 @@ describe('useCan Hook', () => {
381
420
  }, { timeout: WAIT_FOR_TIMEOUT });
382
421
 
383
422
  // Provide organisation context
384
- const scopeWithOrg = { ...scopeWithoutOrg, organisationId: 'org-123' };
423
+ const scopeWithOrg = { ...scopeWithoutOrg, organisationId: VALID_ORG_ID };
385
424
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
386
425
  rerender({ scope: scopeWithOrg });
387
426
 
@@ -413,11 +452,11 @@ describe('useCan Hook', () => {
413
452
 
414
453
  it('allows undefined organisationId for page-level permissions', async () => {
415
454
  const pagePermission = 'read:page.users' as const;
416
- const scopeWithoutOrg = {};
455
+ const scopeWithAppIdOnly = { appId: VALID_APP_ID };
417
456
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
418
457
 
419
458
  const { result } = renderHook(() =>
420
- useCan(mockUserId, scopeWithoutOrg, pagePermission)
459
+ useCan(mockUserId, scopeWithAppIdOnly, pagePermission)
421
460
  );
422
461
 
423
462
  await waitFor(() => {
@@ -429,7 +468,7 @@ describe('useCan Hook', () => {
429
468
  it('treats permission with pageId as page-level', async () => {
430
469
  const pageId = 'page-123';
431
470
  // Provide valid scope with eventId and appId
432
- const scopeWithoutOrg = { eventId: 'event-123', appId: 'app-123' };
471
+ const scopeWithoutOrg = { eventId: VALID_EVENT_ID, appId: VALID_APP_ID };
433
472
  mockIsPermittedCached.mockResolvedValue({ ok: true, data: true });
434
473
  // Set precomputedSuperAdmin to false to skip super admin check
435
474
  mockIsSuperAdmin.mockResolvedValue({ ok: true, data: false });
@@ -509,7 +548,7 @@ describe('useCan Hook', () => {
509
548
  expect(result.current.isLoading).toBe(false);
510
549
  }, { timeout: WAIT_FOR_TIMEOUT });
511
550
 
512
- const newScope = { ...mockScope, organisationId: 'org-456' };
551
+ const newScope = { ...mockScope, organisationId: VALID_ORG_ID_2 };
513
552
  rerender({ scope: newScope });
514
553
 
515
554
  await waitFor(() => {
@@ -600,11 +639,11 @@ describe('useCan Hook', () => {
600
639
 
601
640
  const _initialCallCount = mockIsPermittedCached.mock.calls.length;
602
641
 
603
- // Create new scope object with same values
642
+ // Create new scope object with same values (valid UUIDs)
604
643
  const newScope = {
605
- organisationId: 'org-123',
606
- eventId: 'event-123',
607
- appId: 'app-123',
644
+ organisationId: VALID_ORG_ID,
645
+ eventId: VALID_EVENT_ID,
646
+ appId: VALID_APP_ID,
608
647
  };
609
648
  rerender({ scope: newScope });
610
649
 
@@ -713,7 +752,7 @@ describe('useCan Hook', () => {
713
752
 
714
753
  // Rapid changes
715
754
  rerender({ userId: 'user-2', scope: mockScope });
716
- rerender({ userId: mockUserId, scope: { ...mockScope, organisationId: 'org-2' } });
755
+ rerender({ userId: mockUserId, scope: { ...mockScope, organisationId: VALID_ORG_ID_2 } });
717
756
  rerender({ userId: mockUserId, scope: mockScope });
718
757
 
719
758
  await waitFor(() => {
@@ -87,7 +87,7 @@ function useSuperAdminForCan(
87
87
  * Hook to check if user can perform an action
88
88
  *
89
89
  * @param userId - User ID
90
- * @param scope - Scope for permission checking
90
+ * @param scope - Scope for permission checking; null means scope not yet resolved — permission check stays in loading, no RPC is called
91
91
  * @param permission - Permission to check
92
92
  * @param pageId - Optional page ID
93
93
  * @param useCache - Whether to use cached results
@@ -108,7 +108,7 @@ function useSuperAdminForCan(
108
108
  */
109
109
  export function useCan(
110
110
  userId: UUID,
111
- scope: Scope,
111
+ scope: Scope | null,
112
112
  permission: Permission,
113
113
  pageId?: UUID,
114
114
  useCache: boolean = true,
@@ -129,7 +129,11 @@ export function useCan(
129
129
  const [isLoading, setIsLoading] = useState<boolean>(initialIsLoading);
130
130
  const [error, setError] = useState<Error | null>(null);
131
131
 
132
- const isValidScope = scope && typeof scope === 'object';
132
+ // Require at least one identifier so we never call the RPC with scope that fails validateScope
133
+ const isValidScope =
134
+ scope != null &&
135
+ typeof scope === 'object' &&
136
+ !!(scope.organisationId || scope.eventId || scope.appId);
133
137
  const organisationId = isValidScope ? scope.organisationId : undefined;
134
138
  const eventId = isValidScope ? scope.eventId : undefined;
135
139
  const appId = isValidScope ? scope.appId : undefined;
@@ -7,7 +7,7 @@ import { Permission, Scope, UUID } from '../../types';
7
7
  * Hook to check multiple permissions at once
8
8
  *
9
9
  * @param userId - User ID
10
- * @param scope - Scope for permission checking
10
+ * @param scope - Scope for permission checking; null means scope not yet resolved — stays loading, no API call
11
11
  * @param permissions - Array of permissions to check
12
12
  * @param useCache - Whether to use cached results
13
13
  * @returns Multiple permission check results
@@ -36,7 +36,7 @@ import { Permission, Scope, UUID } from '../../types';
36
36
  */
37
37
  export function useMultiplePermissions(
38
38
  userId: UUID,
39
- scope: Scope,
39
+ scope: Scope | null,
40
40
  permissions: Permission[],
41
41
  useCache: boolean = true
42
42
  ): {
@@ -76,6 +76,12 @@ export function useMultiplePermissions(
76
76
  return;
77
77
  }
78
78
 
79
+ if (scope === null) {
80
+ setIsLoading(true);
81
+ setError(null);
82
+ return;
83
+ }
84
+
79
85
  // Prevent concurrent runs
80
86
  if (isCheckingRef.current) {
81
87
  return;
@@ -109,6 +115,9 @@ export function useMultiplePermissions(
109
115
  isCheckingRef.current = false;
110
116
  }
111
117
  }, [userId, scope, permissions, useCache]);
118
+ const scopeOrgId = scope?.organisationId;
119
+ const scopeEventId = scope?.eventId;
120
+ const scopeAppId = scope?.appId;
112
121
 
113
122
  useEffect(() => {
114
123
  // Serialize permissions array for comparison
@@ -117,24 +126,24 @@ export function useMultiplePermissions(
117
126
  // Check if anything actually changed
118
127
  const hasChanged =
119
128
  prevValuesRef.current.userId !== userId ||
120
- prevValuesRef.current.organisationId !== scope.organisationId ||
121
- prevValuesRef.current.eventId !== scope.eventId ||
122
- prevValuesRef.current.appId !== scope.appId ||
129
+ prevValuesRef.current.organisationId !== scopeOrgId ||
130
+ prevValuesRef.current.eventId !== scopeEventId ||
131
+ prevValuesRef.current.appId !== scopeAppId ||
123
132
  prevValuesRef.current.permissions !== permissionsKey ||
124
133
  prevValuesRef.current.useCache !== useCache;
125
134
 
126
135
  if (hasChanged) {
127
136
  prevValuesRef.current = {
128
137
  userId,
129
- organisationId: scope.organisationId,
130
- eventId: scope.eventId,
131
- appId: scope.appId,
138
+ organisationId: scopeOrgId,
139
+ eventId: scopeEventId,
140
+ appId: scopeAppId,
132
141
  permissions: permissionsKey,
133
142
  useCache,
134
143
  };
135
144
  checkPermissions();
136
145
  }
137
- }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache, checkPermissions]);
146
+ }, [userId, scopeOrgId, scopeEventId, scopeAppId, permissions, useCache, checkPermissions]);
138
147
 
139
148
  // Memoize the return object to prevent unnecessary re-renders
140
149
  return useMemo(() => ({
@@ -887,14 +887,13 @@ describe('useCan Hook', () => {
887
887
  useCan(mockUserId, scopeWithoutAppId, 'read:page.dashboard' as any, 'dashboard', false, false)
888
888
  );
889
889
 
890
- // Should wait for appId when pageName is provided
890
+ // Should show loading or denied when appId is missing for page-name permission
891
891
  await waitFor(() => {
892
892
  expect(result.current.isLoading).toBe(true);
893
893
  expect(result.current.can).toBe(false);
894
894
  }, { timeout: 1000 });
895
895
 
896
- // Should not call isPermitted until appId is available
897
- expect(mockIsPermitted).not.toHaveBeenCalled();
896
+ // When eventId is present, runPermissionCheck may still call isPermitted so the API can resolve appId; do not assert on call count.
898
897
  });
899
898
 
900
899
  it('handles UUID pageId not requiring appId', async () => {
@@ -101,10 +101,12 @@ export function usePageGuardScope({
101
101
  }, [effectiveScope, contextAppId, selectedEventId, allowsOptionalContexts]);
102
102
 
103
103
  const shouldBypassScopeForSuperAdmin = isSuperAdmin === true;
104
- const scopeForPermissionCheck =
105
- shouldBypassScopeForSuperAdmin && !stableScope?.organisationId
106
- ? { organisationId: undefined, appId: contextAppId || undefined, eventId: selectedEventId || undefined }
107
- : stableScope;
104
+ const scopeForPermissionCheck: Scope | null =
105
+ scopeLoading
106
+ ? null
107
+ : shouldBypassScopeForSuperAdmin && !stableScope?.organisationId
108
+ ? { organisationId: undefined, appId: contextAppId || undefined, eventId: selectedEventId || undefined }
109
+ : stableScope;
108
110
 
109
111
  return {
110
112
  effectiveScope,
@@ -32,8 +32,8 @@ export interface UsePagePermissionCheckReturn {
32
32
  const dummyScope: Scope = { organisationId: undefined, appId: undefined, eventId: undefined };
33
33
 
34
34
  /**
35
- * Runs useCan with the appropriate scope (dummy when skipping) and returns
36
- * can, effectiveCan (true when skipping), canIsLoading, canError.
35
+ * Runs useCan with the appropriate scope (dummy when skipping). When not skipping,
36
+ * passes scopeForPermissionCheck through (may be null useCan stays loading).
37
37
  */
38
38
  export function usePagePermissionCheck({
39
39
  userId,
@@ -45,10 +45,10 @@ export function usePagePermissionCheck({
45
45
  isSuperAdmin,
46
46
  appName
47
47
  }: UsePagePermissionCheckOptions): UsePagePermissionCheckReturn {
48
- const scope =
48
+ const scope: Scope | null =
49
49
  shouldSkipPermissionCheck
50
50
  ? { ...dummyScope, appId: contextAppId }
51
- : (scopeForPermissionCheck ?? dummyScope);
51
+ : scopeForPermissionCheck;
52
52
 
53
53
  const { can, isLoading: canIsLoading, error: canError } = useCan(
54
54
  userId,
@@ -79,8 +79,12 @@ export function useResolvedScope({
79
79
  selectedEventOrganisationId
80
80
  }: UseResolvedScopeOptions): UseResolvedScopeReturn {
81
81
  // Get immediate context (synchronous) - allows secure client creation immediately
82
- const immediateOrganisationId = selectedEventOrganisationId || selectedOrganisationId || undefined;
83
- const immediateEventId = selectedEventId || undefined;
82
+ // Normalise empty string to undefined so we never build a scope that fails validateScope (empty org/event)
83
+ const rawOrgId = selectedEventOrganisationId || selectedOrganisationId || undefined;
84
+ const immediateOrganisationId =
85
+ typeof rawOrgId === 'string' && rawOrgId.trim() !== '' ? rawOrgId.trim() : undefined;
86
+ const immediateEventId =
87
+ typeof selectedEventId === 'string' && selectedEventId.trim() !== '' ? selectedEventId.trim() : undefined;
84
88
 
85
89
  const [appId, setAppId] = useState<string | undefined>(undefined);
86
90
  const [isResolvingAppId, setIsResolvingAppId] = useState(false);
@@ -209,16 +213,18 @@ export function useResolvedScope({
209
213
  // Build scope immediately with synchronous context + async appId
210
214
  // This allows secure client creation immediately while appId resolves
211
215
  const immediateScope: Scope | null = useMemo(() => {
212
- // For PORTAL/ADMIN apps, allow scope without org/event
216
+ // For PORTAL/ADMIN apps, allow scope with only appId (no org/event required)
217
+ // Return null until appId is resolved so we never pass scope that fails validateScope (needs at least one of org/event/app)
213
218
  if (appName === 'PORTAL' || appName === 'ADMIN') {
219
+ if (!appId) return null;
214
220
  return {
215
221
  organisationId: undefined,
216
222
  eventId: undefined,
217
- appId: appId || undefined
223
+ appId
218
224
  };
219
225
  }
220
-
221
- // Build scope with immediate context
226
+
227
+ // Build scope with immediate context; never set empty string (validateScope rejects it)
222
228
  const scope: Scope = {};
223
229
  if (immediateOrganisationId) {
224
230
  scope.organisationId = immediateOrganisationId;
@@ -229,12 +235,12 @@ export function useResolvedScope({
229
235
  if (appId) {
230
236
  scope.appId = appId;
231
237
  }
232
-
233
- // For non-PORTAL/ADMIN apps, require at least orgId or appId
234
- if (!scope.organisationId && !scope.appId) {
238
+
239
+ // Require at least one valid identifier so validateScope passes
240
+ if (!scope.organisationId && !scope.eventId && !scope.appId) {
235
241
  return null;
236
242
  }
237
-
243
+
238
244
  return scope;
239
245
  }, [immediateOrganisationId, immediateEventId, appId, appName]);
240
246