@jmruthers/pace-core 0.5.184 → 0.5.186

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 (319) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
  13. package/dist/chunk-DAGICKHT.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
  17. package/dist/chunk-GRIQLQ52.js.map +1 -0
  18. package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
  19. package/dist/chunk-HDCUMOOI.js.map +1 -0
  20. package/dist/chunk-HESYZWZW.js +388 -0
  21. package/dist/chunk-HESYZWZW.js.map +1 -0
  22. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  23. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  24. package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
  25. package/dist/chunk-OALXJH4Y.js.map +1 -0
  26. package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
  27. package/dist/chunk-TC7D3CR3.js.map +1 -0
  28. package/dist/chunk-THRPYOFK.js +215 -0
  29. package/dist/chunk-THRPYOFK.js.map +1 -0
  30. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  31. package/dist/chunk-U6WNSFX5.js.map +1 -0
  32. package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
  33. package/dist/chunk-UQWSHFVX.js.map +1 -0
  34. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  35. package/dist/components.d.ts +182 -6
  36. package/dist/components.js +157 -11
  37. package/dist/components.js.map +1 -1
  38. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  39. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  40. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
  41. package/dist/hooks.d.ts +52 -15
  42. package/dist/hooks.js +12 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +12 -12
  45. package/dist/index.js +82 -18
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.d.ts +1 -1
  48. package/dist/providers.js +3 -1
  49. package/dist/rbac/index.d.ts +206 -15
  50. package/dist/rbac/index.js +28 -6
  51. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  52. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  53. package/dist/types.d.ts +2 -2
  54. package/dist/types.js +1 -1
  55. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
  56. package/dist/utils.d.ts +110 -152
  57. package/dist/utils.js +128 -138
  58. package/dist/utils.js.map +1 -1
  59. package/docs/api/README.md +60 -1
  60. package/docs/api/classes/ColumnFactory.md +1 -1
  61. package/docs/api/classes/ErrorBoundary.md +1 -1
  62. package/docs/api/classes/InvalidScopeError.md +1 -1
  63. package/docs/api/classes/Logger.md +178 -0
  64. package/docs/api/classes/MissingUserContextError.md +1 -1
  65. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  66. package/docs/api/classes/PermissionDeniedError.md +1 -1
  67. package/docs/api/classes/RBACAuditManager.md +2 -2
  68. package/docs/api/classes/RBACCache.md +1 -1
  69. package/docs/api/classes/RBACEngine.md +2 -2
  70. package/docs/api/classes/RBACError.md +1 -1
  71. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  72. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  73. package/docs/api/classes/StorageUtils.md +1 -1
  74. package/docs/api/enums/FileCategory.md +1 -1
  75. package/docs/api/enums/LogLevel.md +54 -0
  76. package/docs/api/enums/RBACErrorCode.md +1 -1
  77. package/docs/api/enums/RPCFunction.md +1 -1
  78. package/docs/api/interfaces/AggregateConfig.md +1 -1
  79. package/docs/api/interfaces/BadgeProps.md +1 -1
  80. package/docs/api/interfaces/ButtonProps.md +1 -1
  81. package/docs/api/interfaces/CalendarProps.md +18 -2
  82. package/docs/api/interfaces/CardProps.md +1 -1
  83. package/docs/api/interfaces/ColorPalette.md +1 -1
  84. package/docs/api/interfaces/ColorShade.md +1 -1
  85. package/docs/api/interfaces/ComplianceResult.md +30 -0
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +1 -1
  88. package/docs/api/interfaces/DataTableAction.md +1 -1
  89. package/docs/api/interfaces/DataTableColumn.md +1 -1
  90. package/docs/api/interfaces/DataTableProps.md +1 -1
  91. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  92. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  93. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  94. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  95. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  97. package/docs/api/interfaces/ExportColumn.md +1 -1
  98. package/docs/api/interfaces/ExportOptions.md +1 -1
  99. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  100. package/docs/api/interfaces/FileMetadata.md +1 -1
  101. package/docs/api/interfaces/FileReference.md +1 -1
  102. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  103. package/docs/api/interfaces/FileUploadOptions.md +48 -8
  104. package/docs/api/interfaces/FileUploadProps.md +46 -13
  105. package/docs/api/interfaces/FooterProps.md +1 -1
  106. package/docs/api/interfaces/FormFieldProps.md +1 -1
  107. package/docs/api/interfaces/FormProps.md +1 -1
  108. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  109. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  110. package/docs/api/interfaces/InputProps.md +1 -1
  111. package/docs/api/interfaces/LabelProps.md +1 -1
  112. package/docs/api/interfaces/LoggerConfig.md +62 -0
  113. package/docs/api/interfaces/LoginFormProps.md +1 -1
  114. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  115. package/docs/api/interfaces/NavigationContextType.md +1 -1
  116. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  117. package/docs/api/interfaces/NavigationItem.md +1 -1
  118. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  119. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  120. package/docs/api/interfaces/Organisation.md +1 -1
  121. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  122. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  123. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  124. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  125. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  126. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  127. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  128. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  129. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  130. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  131. package/docs/api/interfaces/PaletteData.md +1 -1
  132. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  133. package/docs/api/interfaces/ProgressProps.md +1 -1
  134. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  135. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  136. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  138. package/docs/api/interfaces/QuickFix.md +52 -0
  139. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  140. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  141. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  142. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  143. package/docs/api/interfaces/RBACConfig.md +4 -4
  144. package/docs/api/interfaces/RBACContext.md +1 -1
  145. package/docs/api/interfaces/RBACLogger.md +1 -1
  146. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  149. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  151. package/docs/api/interfaces/RBACResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  156. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  157. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  158. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  159. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  160. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  161. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  162. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  163. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  164. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  165. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  166. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  167. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  168. package/docs/api/interfaces/RouteConfig.md +1 -1
  169. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  170. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  171. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  172. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  173. package/docs/api/interfaces/SetupIssue.md +41 -0
  174. package/docs/api/interfaces/StorageConfig.md +1 -1
  175. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  176. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  177. package/docs/api/interfaces/StorageListOptions.md +1 -1
  178. package/docs/api/interfaces/StorageListResult.md +1 -1
  179. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  180. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  181. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  182. package/docs/api/interfaces/StyleImport.md +1 -1
  183. package/docs/api/interfaces/SwitchProps.md +1 -1
  184. package/docs/api/interfaces/TabsContentProps.md +1 -1
  185. package/docs/api/interfaces/TabsListProps.md +1 -1
  186. package/docs/api/interfaces/TabsProps.md +1 -1
  187. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  188. package/docs/api/interfaces/TextareaProps.md +1 -1
  189. package/docs/api/interfaces/ToastActionElement.md +1 -1
  190. package/docs/api/interfaces/ToastProps.md +1 -1
  191. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  192. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  193. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  194. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  195. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  196. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  198. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  202. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  203. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  204. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  205. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  206. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  207. package/docs/api/interfaces/UserEventAccess.md +1 -1
  208. package/docs/api/interfaces/UserMenuProps.md +1 -1
  209. package/docs/api/interfaces/UserProfile.md +1 -1
  210. package/docs/api/modules.md +746 -50
  211. package/docs/api-reference/components.md +26 -12
  212. package/docs/api-reference/hooks.md +111 -0
  213. package/docs/api-reference/rpc-functions.md +1 -1
  214. package/docs/api-reference/utilities.md +184 -0
  215. package/docs/getting-started/installation-guide.md +75 -16
  216. package/docs/getting-started/quick-start.md +61 -11
  217. package/docs/implementation-guides/authentication.md +88 -12
  218. package/docs/implementation-guides/file-reference-system.md +26 -3
  219. package/docs/implementation-guides/file-upload-storage.md +30 -1
  220. package/docs/rbac/README.md +1 -0
  221. package/docs/rbac/compliance/compliance-guide.md +544 -0
  222. package/docs/rbac/getting-started.md +158 -33
  223. package/docs/standards/pace-core-compliance.md +432 -0
  224. package/eslint-config-pace-core.cjs +93 -0
  225. package/package.json +15 -3
  226. package/scripts/analyze-bundle.js +232 -0
  227. package/scripts/build-css.js +56 -0
  228. package/scripts/build-docs-incremental.js +1015 -0
  229. package/scripts/check-pace-core-compliance.cjs +2353 -0
  230. package/scripts/check-pace-core-compliance.js +512 -0
  231. package/scripts/generate-docs.js +157 -0
  232. package/scripts/setup-build-cache.js +73 -0
  233. package/scripts/utils/command-runner.js +131 -0
  234. package/scripts/utils/env.js +33 -0
  235. package/scripts/utils/index.js +10 -0
  236. package/scripts/utils/logger.js +88 -0
  237. package/scripts/utils/path-helpers.js +37 -0
  238. package/scripts/validate-formats.js +133 -0
  239. package/scripts/validate-master.js +155 -0
  240. package/scripts/validate-pre-publish.js +140 -0
  241. package/scripts/validate-theme.js +142 -0
  242. package/src/components/Calendar/Calendar.tsx +8 -1
  243. package/src/components/Card/Card.tsx +47 -8
  244. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  245. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  246. package/src/components/DatePickerWithTimezone/README.md +135 -0
  247. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  248. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  249. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  250. package/src/components/DateTimeField/README.md +148 -0
  251. package/src/components/DateTimeField/index.ts +10 -0
  252. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  253. package/src/components/FileUpload/FileUpload.tsx +10 -1
  254. package/src/components/Header/Header.test.tsx +47 -18
  255. package/src/components/Header/Header.tsx +22 -7
  256. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  257. package/src/components/PaceAppLayout/README.md +9 -0
  258. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  259. package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
  260. package/src/components/index.ts +8 -0
  261. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  262. package/src/eslint-rules/pace-core-compliance.js +640 -0
  263. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  264. package/src/hooks/index.ts +5 -0
  265. package/src/hooks/useFileReference.test.ts +2 -0
  266. package/src/hooks/useFormDialog.ts +147 -0
  267. package/src/hooks/usePreventTabReload.ts +106 -0
  268. package/src/hooks/useSecureDataAccess.ts +2 -2
  269. package/src/index.ts +27 -0
  270. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  271. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  272. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  273. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  274. package/src/rbac/compliance/database-validator.ts +165 -0
  275. package/src/rbac/compliance/index.ts +38 -0
  276. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  277. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  278. package/src/rbac/compliance/setup-validator.ts +131 -0
  279. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  280. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  281. package/src/rbac/docs/event-based-apps.md +285 -0
  282. package/src/rbac/errors.ts +11 -0
  283. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  284. package/src/rbac/index.ts +30 -0
  285. package/src/services/OrganisationService.ts +4 -0
  286. package/src/styles/core.css +5 -5
  287. package/src/types/database.generated.ts +63 -9
  288. package/src/types/file-reference.ts +9 -0
  289. package/src/utils/__tests__/timezone.test.ts +345 -0
  290. package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
  291. package/src/utils/file-reference/index.ts +13 -2
  292. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  293. package/src/utils/formatting/formatting.ts +179 -0
  294. package/src/utils/index.ts +27 -1
  295. package/src/utils/location/index.ts +16 -0
  296. package/src/utils/location/location.test.ts +286 -0
  297. package/src/utils/location/location.ts +175 -0
  298. package/src/utils/security/secureDataAccess.ts +1 -1
  299. package/src/utils/storage/helpers.ts +68 -0
  300. package/src/utils/timezone/index.ts +17 -0
  301. package/src/utils/timezone/timezone.test.ts +349 -0
  302. package/src/utils/timezone/timezone.ts +281 -0
  303. package/dist/chunk-CSOFYHAG.js.map +0 -1
  304. package/dist/chunk-FUEYYMX5.js.map +0 -1
  305. package/dist/chunk-HKIT6O7W.js +0 -198
  306. package/dist/chunk-HKIT6O7W.js.map +0 -1
  307. package/dist/chunk-KUEN3HFB.js +0 -94
  308. package/dist/chunk-KUEN3HFB.js.map +0 -1
  309. package/dist/chunk-M7W4CP3M.js.map +0 -1
  310. package/dist/chunk-MI7HBHN3.js.map +0 -1
  311. package/dist/chunk-NQPMQGS2.js.map +0 -1
  312. package/dist/chunk-PWAHJW4G.js.map +0 -1
  313. package/dist/chunk-UHNYIBXL.js.map +0 -1
  314. package/dist/chunk-W22JP75J.js.map +0 -1
  315. package/dist/formatting-5wETwiGF.d.ts +0 -162
  316. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  317. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  318. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  319. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Quick Fix Suggestions for RBAC/Auth Compliance
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/Compliance/QuickFixSuggestions
5
+ * @since 1.0.0
6
+ *
7
+ * This module provides auto-suggest fixes for common RBAC/auth compliance issues.
8
+ */
9
+
10
+ export interface QuickFix {
11
+ issue: string;
12
+ suggestion: string;
13
+ codeExample?: string;
14
+ migrationSteps?: string[];
15
+ }
16
+
17
+ /**
18
+ * Get quick fix suggestions for custom auth code
19
+ */
20
+ export function getCustomAuthCodeFixes(customCodeName: string, type: 'hook' | 'component' | 'util'): QuickFix {
21
+ const fixes: Record<string, QuickFix> = {
22
+ 'useAuth': {
23
+ issue: `Custom ${type} '${customCodeName}' detected`,
24
+ suggestion: `Replace with useUnifiedAuth from pace-core`,
25
+ codeExample: `// Before
26
+ import { useAuth } from './hooks/useAuth';
27
+
28
+ // After
29
+ import { useUnifiedAuth } from '@jmruthers/pace-core';`,
30
+ migrationSteps: [
31
+ 'Remove custom useAuth hook',
32
+ 'Import useUnifiedAuth from @jmruthers/pace-core',
33
+ 'Update all usages to use useUnifiedAuth',
34
+ 'Ensure UnifiedAuthProvider wraps your app'
35
+ ]
36
+ },
37
+ 'usePermissions': {
38
+ issue: `Custom ${type} '${customCodeName}' detected`,
39
+ suggestion: `Replace with usePermissions from pace-core`,
40
+ codeExample: `// Before
41
+ import { usePermissions } from './hooks/usePermissions';
42
+
43
+ // After
44
+ import { usePermissions } from '@jmruthers/pace-core/rbac';`,
45
+ migrationSteps: [
46
+ 'Remove custom usePermissions hook',
47
+ 'Import usePermissions from @jmruthers/pace-core/rbac',
48
+ 'Update all usages - ensure setupRBAC() has been called',
49
+ 'Verify provider hierarchy is correct'
50
+ ]
51
+ },
52
+ 'PermissionGuard': {
53
+ issue: `Custom ${type} '${customCodeName}' detected`,
54
+ suggestion: `Replace with PagePermissionGuard from pace-core`,
55
+ codeExample: `// Before
56
+ import { PermissionGuard } from './components/PermissionGuard';
57
+
58
+ // After
59
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';`,
60
+ migrationSteps: [
61
+ 'Remove custom PermissionGuard component',
62
+ 'Import PagePermissionGuard from @jmruthers/pace-core/rbac',
63
+ 'Wrap pages with PagePermissionGuard',
64
+ 'Use pageName and operation props instead of custom permission strings'
65
+ ]
66
+ },
67
+ 'checkPermission': {
68
+ issue: `Custom ${type} '${customCodeName}' detected`,
69
+ suggestion: `Replace with isPermitted from pace-core`,
70
+ codeExample: `// Before
71
+ import { checkPermission } from './utils/permissions';
72
+
73
+ // After
74
+ import { isPermitted } from '@jmruthers/pace-core/rbac';`,
75
+ migrationSteps: [
76
+ 'Remove custom checkPermission utility',
77
+ 'Import isPermitted from @jmruthers/pace-core/rbac',
78
+ 'Update all usages to use isPermitted with proper scope',
79
+ 'Ensure setupRBAC() has been called'
80
+ ]
81
+ }
82
+ };
83
+
84
+ return fixes[customCodeName] || {
85
+ issue: `Custom ${type} '${customCodeName}' detected`,
86
+ suggestion: `Use pace-core's equivalent instead. Check @jmruthers/pace-core documentation for the correct import.`,
87
+ migrationSteps: [
88
+ `Remove custom ${customCodeName} ${type}`,
89
+ 'Find equivalent in pace-core',
90
+ 'Import from @jmruthers/pace-core or @jmruthers/pace-core/rbac',
91
+ 'Update all usages'
92
+ ]
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Get quick fix suggestions for duplicate Supabase config
98
+ */
99
+ export function getDuplicateConfigFixes(): QuickFix {
100
+ return {
101
+ issue: 'Multiple Supabase client instantiations found',
102
+ suggestion: 'Consolidate to a single Supabase client configuration',
103
+ codeExample: `// Before - Multiple createClient calls
104
+ // src/lib/supabase.ts
105
+ export const supabase = createClient(url, key);
106
+
107
+ // src/utils/api.ts
108
+ export const supabase = createClient(url, key);
109
+
110
+ // After - Single configuration
111
+ // src/lib/supabase.ts
112
+ export const supabase = createClient(
113
+ import.meta.env.VITE_SUPABASE_URL,
114
+ import.meta.env.VITE_SUPABASE_ANON_KEY
115
+ );
116
+
117
+ // src/utils/api.ts
118
+ import { supabase } from '../lib/supabase';`,
119
+ migrationSteps: [
120
+ 'Create a single supabase.ts file in a shared location (e.g., src/lib/supabase.ts)',
121
+ 'Move all Supabase client creation to this file',
122
+ 'Export the client instance',
123
+ 'Update all files to import from the shared location',
124
+ 'Remove duplicate createClient calls'
125
+ ]
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Get quick fix suggestions for unprotected pages
131
+ */
132
+ export function getUnprotectedPageFixes(): QuickFix {
133
+ return {
134
+ issue: 'Route/page found without PagePermissionGuard',
135
+ suggestion: 'Wrap all routes with PagePermissionGuard',
136
+ codeExample: `// Before
137
+ <Route path="/dashboard" element={<Dashboard />} />
138
+
139
+ // After
140
+ <Route
141
+ path="/dashboard"
142
+ element={
143
+ <PagePermissionGuard pageName="dashboard" operation="read">
144
+ <Dashboard />
145
+ </PagePermissionGuard>
146
+ }
147
+ />`,
148
+ migrationSteps: [
149
+ 'Import PagePermissionGuard from @jmruthers/pace-core/rbac',
150
+ 'Wrap each route/page component with PagePermissionGuard',
151
+ 'Set pageName prop to match your page name in rbac_app_pages table',
152
+ 'Set operation prop (read, create, update, or delete)',
153
+ 'Ensure setupRBAC() has been called and providers are set up correctly'
154
+ ]
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Get quick fix suggestions for direct Supabase auth usage
160
+ */
161
+ export function getDirectSupabaseAuthFixes(): QuickFix {
162
+ return {
163
+ issue: 'Direct Supabase auth usage detected',
164
+ suggestion: 'Use UnifiedAuthProvider and useUnifiedAuth from pace-core',
165
+ codeExample: `// Before
166
+ import { createClient } from '@supabase/supabase-js';
167
+ const supabase = createClient(url, key);
168
+ await supabase.auth.signInWithPassword({ email, password });
169
+
170
+ // After
171
+ import { useUnifiedAuth } from '@jmruthers/pace-core';
172
+ const { signIn } = useUnifiedAuth();
173
+ await signIn({ email, password });`,
174
+ migrationSteps: [
175
+ 'Remove direct Supabase auth calls',
176
+ 'Import useUnifiedAuth from @jmruthers/pace-core',
177
+ 'Use the auth methods from useUnifiedAuth hook',
178
+ 'Ensure UnifiedAuthProvider wraps your app',
179
+ 'Update all auth-related code to use pace-core hooks'
180
+ ]
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Get all quick fix suggestions for a compliance issue
186
+ */
187
+ export function getQuickFixes(issueType: string, details?: Record<string, any>): QuickFix[] {
188
+ const fixes: QuickFix[] = [];
189
+
190
+ switch (issueType) {
191
+ case 'custom-auth-code':
192
+ if (details?.name && details?.type) {
193
+ fixes.push(getCustomAuthCodeFixes(details.name, details.type));
194
+ }
195
+ break;
196
+ case 'duplicate-config':
197
+ fixes.push(getDuplicateConfigFixes());
198
+ break;
199
+ case 'unprotected-pages':
200
+ fixes.push(getUnprotectedPageFixes());
201
+ break;
202
+ case 'direct-supabase-auth':
203
+ fixes.push(getDirectSupabaseAuthFixes());
204
+ break;
205
+ }
206
+
207
+ return fixes;
208
+ }
209
+
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Runtime Compliance Checking
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/Compliance/RuntimeCompliance
5
+ * @since 1.0.0
6
+ *
7
+ * This module provides runtime compliance checking utilities.
8
+ */
9
+
10
+ import { validateRBACSetup, SetupIssue } from './setup-validator';
11
+ import { getRBACLogger } from '../config';
12
+
13
+ export interface RuntimeComplianceResult {
14
+ setup: {
15
+ isCompliant: boolean;
16
+ issues: SetupIssue[];
17
+ };
18
+ warnings: string[];
19
+ providerContext?: {
20
+ available: boolean;
21
+ message?: string;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Check runtime compliance
27
+ *
28
+ * This function checks if the RBAC system is properly set up and logs warnings
29
+ * to the console if issues are found. This is intended for development-time
30
+ * validation only.
31
+ *
32
+ * @returns Runtime compliance result
33
+ */
34
+ export function checkRuntimeCompliance(): RuntimeComplianceResult {
35
+ const logger = getRBACLogger();
36
+ const setupValidation = validateRBACSetup();
37
+ const warnings: string[] = [];
38
+
39
+ if (!setupValidation.isCompliant) {
40
+ setupValidation.issues.forEach(issue => {
41
+ const warning = `[RBAC Compliance] ${issue.message}\n Recommendation: ${issue.recommendation}`;
42
+ warnings.push(warning);
43
+ logger.warn(warning);
44
+ });
45
+ }
46
+
47
+ // Check for provider context issues
48
+ const providerContextIssues = setupValidation.issues.filter(
49
+ issue => issue.type === 'missing-provider-context' || issue.type === 'not-initialized'
50
+ );
51
+
52
+ const providerContext = providerContextIssues.length > 0 ? {
53
+ available: false,
54
+ message: 'UnifiedAuthProvider context may not be available. Ensure your app is wrapped with UnifiedAuthProvider from @jmruthers/pace-core.'
55
+ } : {
56
+ available: true
57
+ };
58
+
59
+ return {
60
+ setup: setupValidation,
61
+ warnings,
62
+ providerContext
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Validate and warn about RBAC setup issues
68
+ *
69
+ * This is a convenience function that checks compliance and logs warnings.
70
+ * Call this in development mode to get early warnings about setup issues.
71
+ */
72
+ export function validateAndWarn(): void {
73
+ if (import.meta.env.MODE === 'development' || import.meta.env.DEV) {
74
+ checkRuntimeCompliance();
75
+ }
76
+ }
77
+
@@ -0,0 +1,131 @@
1
+ /**
2
+ * RBAC Setup Validator
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/Compliance/SetupValidator
5
+ * @since 1.0.0
6
+ *
7
+ * This module provides utilities to validate RBAC setup state.
8
+ */
9
+
10
+ import { getRBACConfig } from '../config';
11
+ import { RBACNotInitializedError } from '../errors';
12
+
13
+ export interface SetupIssue {
14
+ type: 'not-initialized' | 'missing-config' | 'invalid-config' | 'missing-provider-context';
15
+ message: string;
16
+ recommendation: string;
17
+ }
18
+
19
+ export interface ComplianceResult {
20
+ isCompliant: boolean;
21
+ issues: SetupIssue[];
22
+ }
23
+
24
+ /**
25
+ * Check if RBAC system is initialized
26
+ *
27
+ * @returns true if RBAC is initialized, false otherwise
28
+ */
29
+ export function isRBACInitialized(): boolean {
30
+ try {
31
+ const config = getRBACConfig();
32
+ return config !== null && config.supabase !== null;
33
+ } catch (error) {
34
+ if (error instanceof RBACNotInitializedError) {
35
+ return false;
36
+ }
37
+ // Re-throw unexpected errors
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get setup issues
44
+ *
45
+ * @returns Array of setup issues
46
+ */
47
+ export function getSetupIssues(): SetupIssue[] {
48
+ const issues: SetupIssue[] = [];
49
+
50
+ const config = getRBACConfig();
51
+
52
+ if (!config) {
53
+ issues.push({
54
+ type: 'not-initialized',
55
+ message: 'RBAC system has not been initialized. setupRBAC() has not been called.',
56
+ recommendation: 'Call setupRBAC(supabase) before using any RBAC features. This should be done in your main entry point (main.tsx or App.tsx) before rendering the app.'
57
+ });
58
+ return issues;
59
+ }
60
+
61
+ if (!config.supabase) {
62
+ issues.push({
63
+ type: 'missing-config',
64
+ message: 'RBAC configuration is missing Supabase client.',
65
+ recommendation: 'Ensure setupRBAC() is called with a valid Supabase client instance.'
66
+ });
67
+ }
68
+
69
+ return issues;
70
+ }
71
+
72
+ /**
73
+ * Check if UnifiedAuthProvider context is available
74
+ *
75
+ * This function can be called from React components to check if the context
76
+ * is available. It uses React's useContext hook, so it must be called from
77
+ * within a React component.
78
+ *
79
+ * @returns true if context is available, false otherwise
80
+ * @throws Error if called outside React component context
81
+ */
82
+ export function isUnifiedAuthContextAvailable(): boolean {
83
+ try {
84
+ // This will only work if called from within a React component
85
+ // We can't import useContext here directly as it would require React
86
+ // Instead, we'll check if the context can be accessed
87
+ // Note: This is a best-effort check and may not work in all scenarios
88
+ return true; // Context availability is checked by useUnifiedAuth hook itself
89
+ } catch (error) {
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get provider context setup issues
96
+ *
97
+ * This provides guidance on common provider setup problems.
98
+ * Actual context availability must be checked at component level.
99
+ *
100
+ * @returns Array of setup issues related to provider context
101
+ */
102
+ export function getProviderContextIssues(): SetupIssue[] {
103
+ const issues: SetupIssue[] = [];
104
+
105
+ // Check if RBAC is initialized (prerequisite for provider context)
106
+ if (!isRBACInitialized()) {
107
+ issues.push({
108
+ type: 'not-initialized',
109
+ message: 'RBAC system must be initialized before provider context can be used.',
110
+ recommendation: 'Call setupRBAC(supabase) before rendering UnifiedAuthProvider.'
111
+ });
112
+ }
113
+
114
+ return issues;
115
+ }
116
+
117
+ /**
118
+ * Validate RBAC setup
119
+ *
120
+ * @returns Compliance result with issues and recommendations
121
+ */
122
+ export function validateRBACSetup(): ComplianceResult {
123
+ const issues = getSetupIssues();
124
+ const providerIssues = getProviderContextIssues();
125
+
126
+ return {
127
+ isCompliant: issues.length === 0 && providerIssues.length === 0,
128
+ issues: [...issues, ...providerIssues]
129
+ };
130
+ }
131
+
@@ -72,7 +72,6 @@ import { useCan } from '../hooks';
72
72
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
73
73
  import { UUID, Permission, Scope } from '../types';
74
74
  import { createScopeFromEvent } from '../utils/eventContext';
75
- import { getCurrentAppName } from '../../utils/app/appNameResolver';
76
75
  import { getRBACLogger } from '../config';
77
76
 
78
77
  export interface PagePermissionGuardProps {
@@ -129,16 +128,16 @@ const PagePermissionGuardComponent = ({
129
128
  onDenied,
130
129
  loading = <DefaultLoading />
131
130
  }: PagePermissionGuardProps) => {
132
- // Generate a unique instance ID for debugging
133
- const instanceId = useMemo(() => Math.random().toString(36).substr(2, 9), []);
134
-
135
131
  // Track render count for debugging
136
132
  const renderCountRef = useRef(0);
137
133
  renderCountRef.current += 1;
138
134
 
135
+ // Generate a unique instance ID for debugging (must be called before any conditional returns)
136
+ const instanceId = useMemo(() => Math.random().toString(36).substr(2, 9), []);
139
137
 
140
-
141
- const { user, selectedOrganisation, selectedEvent, supabase } = useUnifiedAuth();
138
+ // Use UnifiedAuth hook - if context is not available, it will throw and ErrorBoundary will handle it
139
+ // This is better than checking for context and returning early, which causes infinite loops
140
+ const { user, selectedOrganisation, selectedEvent, supabase, appId: contextAppId } = useUnifiedAuth();
142
141
 
143
142
  const [hasChecked, setHasChecked] = useState(false);
144
143
  const [checkError, setCheckError] = useState<Error | null>(null);
@@ -179,64 +178,9 @@ const PagePermissionGuardComponent = ({
179
178
  return;
180
179
  }
181
180
 
182
- // Get app ID from package.json or environment
183
- let appId: string | undefined = undefined;
184
-
185
- // Try to resolve from database
186
- if (supabaseRef.current) {
187
- const appName = getCurrentAppName();
188
- if (appName) {
189
- try {
190
- const { data: app, error } = await supabaseRef.current
191
- .from('rbac_apps')
192
- .select('id, name, is_active')
193
- .eq('name', appName)
194
- .eq('is_active', true)
195
- .single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
196
-
197
- if (signal.aborted) {
198
- return;
199
- }
200
-
201
- if (error) {
202
- const logger = getRBACLogger();
203
- logger.error('Database error resolving app ID:', error);
204
- if (signal.aborted) {
205
- return;
206
- }
207
- const { data: inactiveApp } = await supabaseRef.current
208
- .from('rbac_apps')
209
- .select('id, name, is_active')
210
- .eq('name', appName)
211
- .single() as { data: { id: string; name: string; is_active: boolean } | null };
212
-
213
- if (signal.aborted) {
214
- return;
215
- }
216
-
217
- if (inactiveApp) {
218
- logger.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
219
- } else {
220
- logger.error(`App "${appName}" not found in rbac_apps table`);
221
- }
222
- } else if (app) {
223
- appId = app.id;
224
- } else {
225
- const logger = getRBACLogger();
226
- logger.error('No app data returned for:', appName);
227
- }
228
- } catch (error) {
229
- if (signal.aborted) {
230
- return;
231
- }
232
- const logger = getRBACLogger();
233
- logger.error('Unexpected error resolving app ID:', error);
234
- }
235
- } else {
236
- const logger = getRBACLogger();
237
- logger.error('No app name found. Make sure to call setRBACAppName() in your app setup.');
238
- }
239
- }
181
+ // Get app ID from UnifiedAuth context (already resolved on login)
182
+ // This is much faster than querying the database
183
+ const appId = contextAppId;
240
184
 
241
185
  if (signal.aborted) {
242
186
  return;
@@ -80,6 +80,7 @@ describe('PagePermissionGuard Component', () => {
80
80
  user: mockUser,
81
81
  selectedOrganisation: { id: 'org-123' },
82
82
  selectedEvent: { event_id: 'event-123' },
83
+ appId: 'app-123', // Required for scope resolution
83
84
  supabase: {
84
85
  from: vi.fn().mockReturnValue({
85
86
  select: vi.fn().mockReturnValue({
@@ -343,14 +344,17 @@ describe('PagePermissionGuard Component', () => {
343
344
  </PagePermissionGuard>
344
345
  );
345
346
 
347
+ // When there's an error and no permission, component shows fallback
346
348
  await waitFor(() => {
347
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
348
- }, { interval: 10 });
349
+ expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
350
+ }, { interval: 10, timeout: 2000 });
349
351
  });
350
352
  });
351
353
 
352
354
  describe('App ID Resolution', () => {
353
355
  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
354
358
  mockUseCan.mockReturnValue({
355
359
  can: true,
356
360
  isLoading: false,
@@ -368,9 +372,11 @@ describe('PagePermissionGuard Component', () => {
368
372
 
369
373
  await waitFor(() => {
370
374
  expect(screen.getByTestId('test-component')).toBeInTheDocument();
371
- }, { interval: 10 });
375
+ }, { interval: 10, timeout: 2000 });
372
376
 
373
- expect(mockGetCurrentAppName).toHaveBeenCalled();
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();
374
380
  });
375
381
 
376
382
  it('handles app resolution errors in test environment', async () => {
@@ -432,10 +438,12 @@ describe('PagePermissionGuard Component', () => {
432
438
  mockGetCurrentAppName.mockReturnValue('test-app');
433
439
 
434
440
  // Mock database returning invalid app ID
441
+ // In test mode, validation is skipped, so component should work normally
435
442
  mockUseUnifiedAuthFn.mockReturnValue({
436
443
  user: mockUser,
437
444
  selectedOrganisation: { id: 'org-123' },
438
445
  selectedEvent: { event_id: 'event-123' },
446
+ appId: 'app-123',
439
447
  supabase: {
440
448
  from: vi.fn().mockReturnValue({
441
449
  select: vi.fn().mockReturnValue({
@@ -452,9 +460,11 @@ describe('PagePermissionGuard Component', () => {
452
460
  } as any
453
461
  });
454
462
 
455
- // Set NODE_ENV to production
456
- const originalEnv = process.env.NODE_ENV;
457
- process.env.NODE_ENV = 'production';
463
+ mockUseCan.mockReturnValue({
464
+ can: true,
465
+ isLoading: false,
466
+ error: null
467
+ });
458
468
 
459
469
  render(
460
470
  <PagePermissionGuard
@@ -466,12 +476,10 @@ describe('PagePermissionGuard Component', () => {
466
476
  </PagePermissionGuard>
467
477
  );
468
478
 
479
+ // In test mode, validation is skipped, so component should render normally
469
480
  await waitFor(() => {
470
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
471
- }, { interval: 10 });
472
-
473
- // Restore NODE_ENV
474
- process.env.NODE_ENV = originalEnv;
481
+ expect(screen.getByTestId('test-component')).toBeInTheDocument();
482
+ }, { interval: 10, timeout: 2000 });
475
483
  });
476
484
  });
477
485
 
@@ -550,6 +558,7 @@ describe('PagePermissionGuard Component', () => {
550
558
  user: mockUser,
551
559
  selectedOrganisation: { id: 'org-123' },
552
560
  selectedEvent: null,
561
+ appId: 'app-123',
553
562
  supabase: {
554
563
  from: vi.fn().mockReturnValue({
555
564
  select: vi.fn().mockReturnValue({
@@ -603,6 +612,7 @@ describe('PagePermissionGuard Component', () => {
603
612
  user: mockUser,
604
613
  selectedOrganisation: null,
605
614
  selectedEvent: { event_id: 'event-123' },
615
+ appId: 'app-123',
606
616
  supabase: {
607
617
  from: vi.fn().mockReturnValue({
608
618
  select: vi.fn().mockReturnValue({
@@ -653,7 +663,7 @@ describe('PagePermissionGuard Component', () => {
653
663
  expect.objectContaining({
654
664
  organisationId: 'resolved-org',
655
665
  eventId: 'event-123',
656
- appId: 'app-123'
666
+ appId: 'app-123' // Component uses appId from context, not from resolved scope
657
667
  }),
658
668
  'read:page.dashboard',
659
669
  'dashboard',
@@ -666,6 +676,7 @@ describe('PagePermissionGuard Component', () => {
666
676
  user: mockUser,
667
677
  selectedOrganisation: null,
668
678
  selectedEvent: { event_id: 'event-123' },
679
+ appId: undefined, // Not available for error case
669
680
  supabase: {} as any
670
681
  });
671
682
 
@@ -692,6 +703,7 @@ describe('PagePermissionGuard Component', () => {
692
703
  user: mockUser,
693
704
  selectedOrganisation: null,
694
705
  selectedEvent: null,
706
+ appId: undefined,
695
707
  supabase: null
696
708
  });
697
709
 
@@ -809,8 +821,8 @@ describe('PagePermissionGuard Component', () => {
809
821
  );
810
822
 
811
823
  await waitFor(() => {
812
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
813
- }, { interval: 10 });
824
+ expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
825
+ }, { interval: 10, timeout: 2000 });
814
826
 
815
827
  expect(onDeniedSpy).toHaveBeenCalledWith(mockPageName, mockOperation);
816
828
  });
@@ -864,8 +876,8 @@ describe('PagePermissionGuard Component', () => {
864
876
  );
865
877
 
866
878
  await waitFor(() => {
867
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
868
- }, { interval: 10 });
879
+ expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
880
+ }, { interval: 10, timeout: 2000 });
869
881
 
870
882
  expect(consoleSpy).not.toHaveBeenCalledWith(
871
883
  expect.stringContaining('STRICT MODE VIOLATION')
@@ -895,8 +907,8 @@ describe('PagePermissionGuard Component', () => {
895
907
  );
896
908
 
897
909
  await waitFor(() => {
898
- expect(screen.getByText('Checking permissions...')).toBeInTheDocument();
899
- }, { interval: 10 });
910
+ expect(screen.getByTestId('test-fallback')).toBeInTheDocument();
911
+ }, { interval: 10, timeout: 2000 });
900
912
 
901
913
  expect(consoleSpy).not.toHaveBeenCalledWith(
902
914
  expect.stringContaining('Page access attempt')
@@ -910,8 +922,9 @@ describe('PagePermissionGuard Component', () => {
910
922
  it('handles missing user gracefully', async () => {
911
923
  mockUseUnifiedAuthFn.mockReturnValue({
912
924
  user: null,
913
- selectedOrganisationId: 'org-123',
914
- selectedEventId: 'event-123',
925
+ selectedOrganisation: { id: 'org-123' },
926
+ selectedEvent: { event_id: 'event-123' },
927
+ appId: 'app-123',
915
928
  supabase: {
916
929
  from: vi.fn().mockReturnValue({
917
930
  select: vi.fn().mockReturnValue({
@@ -966,6 +979,7 @@ describe('PagePermissionGuard Component', () => {
966
979
  user: mockUser,
967
980
  selectedOrganisation: { id: 'org-123' },
968
981
  selectedEvent: { event_id: 'event-123' },
982
+ appId: undefined, // Not resolved due to database error
969
983
  supabase: {
970
984
  from: vi.fn().mockReturnValue({
971
985
  select: vi.fn().mockReturnValue({