@jmruthers/pace-core 0.5.183 → 0.5.185

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 (307) 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-BABf6JCh.d.ts} +21 -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-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
  13. package/dist/chunk-AISXLWGZ.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-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
  17. package/dist/chunk-HC67NW5K.js.map +1 -0
  18. package/dist/chunk-HESYZWZW.js +388 -0
  19. package/dist/chunk-HESYZWZW.js.map +1 -0
  20. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  21. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  22. package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
  23. package/dist/chunk-IXSNYUCT.js.map +1 -0
  24. package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
  25. package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
  26. package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
  27. package/dist/chunk-OKI34GZD.js.map +1 -0
  28. package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
  29. package/dist/chunk-THRPYOFK.js +215 -0
  30. package/dist/chunk-THRPYOFK.js.map +1 -0
  31. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  32. package/dist/chunk-U6WNSFX5.js.map +1 -0
  33. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  34. package/dist/components.d.ts +182 -6
  35. package/dist/components.js +157 -11
  36. package/dist/components.js.map +1 -1
  37. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  38. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
  39. package/dist/hooks.d.ts +7 -14
  40. package/dist/hooks.js +10 -22
  41. package/dist/hooks.js.map +1 -1
  42. package/dist/index.d.ts +11 -11
  43. package/dist/index.js +79 -16
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.d.ts +1 -1
  46. package/dist/providers.js +3 -1
  47. package/dist/rbac/index.d.ts +205 -14
  48. package/dist/rbac/index.js +28 -6
  49. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  50. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  51. package/dist/types.d.ts +1 -1
  52. package/dist/types.js +1 -1
  53. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
  54. package/dist/utils.d.ts +109 -151
  55. package/dist/utils.js +128 -138
  56. package/dist/utils.js.map +1 -1
  57. package/docs/api/README.md +60 -1
  58. package/docs/api/classes/ColumnFactory.md +1 -1
  59. package/docs/api/classes/ErrorBoundary.md +1 -1
  60. package/docs/api/classes/InvalidScopeError.md +1 -1
  61. package/docs/api/classes/Logger.md +178 -0
  62. package/docs/api/classes/MissingUserContextError.md +1 -1
  63. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  64. package/docs/api/classes/PermissionDeniedError.md +1 -1
  65. package/docs/api/classes/RBACAuditManager.md +2 -2
  66. package/docs/api/classes/RBACCache.md +1 -1
  67. package/docs/api/classes/RBACEngine.md +2 -2
  68. package/docs/api/classes/RBACError.md +1 -1
  69. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  70. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  71. package/docs/api/classes/StorageUtils.md +1 -1
  72. package/docs/api/enums/FileCategory.md +1 -1
  73. package/docs/api/enums/LogLevel.md +54 -0
  74. package/docs/api/enums/RBACErrorCode.md +1 -1
  75. package/docs/api/enums/RPCFunction.md +1 -1
  76. package/docs/api/interfaces/AggregateConfig.md +1 -1
  77. package/docs/api/interfaces/BadgeProps.md +1 -1
  78. package/docs/api/interfaces/ButtonProps.md +1 -1
  79. package/docs/api/interfaces/CalendarProps.md +18 -2
  80. package/docs/api/interfaces/CardProps.md +1 -1
  81. package/docs/api/interfaces/ColorPalette.md +1 -1
  82. package/docs/api/interfaces/ColorShade.md +1 -1
  83. package/docs/api/interfaces/ComplianceResult.md +30 -0
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataRecord.md +1 -1
  86. package/docs/api/interfaces/DataTableAction.md +1 -1
  87. package/docs/api/interfaces/DataTableColumn.md +1 -1
  88. package/docs/api/interfaces/DataTableProps.md +1 -1
  89. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  90. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  91. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  92. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  95. package/docs/api/interfaces/ExportColumn.md +1 -1
  96. package/docs/api/interfaces/ExportOptions.md +1 -1
  97. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  98. package/docs/api/interfaces/FileMetadata.md +1 -1
  99. package/docs/api/interfaces/FileReference.md +1 -1
  100. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  101. package/docs/api/interfaces/FileUploadOptions.md +24 -8
  102. package/docs/api/interfaces/FileUploadProps.md +24 -13
  103. package/docs/api/interfaces/FooterProps.md +1 -1
  104. package/docs/api/interfaces/FormFieldProps.md +1 -1
  105. package/docs/api/interfaces/FormProps.md +1 -1
  106. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  107. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  108. package/docs/api/interfaces/InputProps.md +1 -1
  109. package/docs/api/interfaces/LabelProps.md +1 -1
  110. package/docs/api/interfaces/LoggerConfig.md +62 -0
  111. package/docs/api/interfaces/LoginFormProps.md +1 -1
  112. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  113. package/docs/api/interfaces/NavigationContextType.md +1 -1
  114. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  115. package/docs/api/interfaces/NavigationItem.md +1 -1
  116. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  117. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  118. package/docs/api/interfaces/Organisation.md +1 -1
  119. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  120. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  121. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  122. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  123. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  124. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  125. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  126. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  127. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  128. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  129. package/docs/api/interfaces/PaletteData.md +1 -1
  130. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  131. package/docs/api/interfaces/ProgressProps.md +1 -1
  132. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  134. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  135. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  136. package/docs/api/interfaces/QuickFix.md +52 -0
  137. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  138. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  139. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  140. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  141. package/docs/api/interfaces/RBACConfig.md +4 -4
  142. package/docs/api/interfaces/RBACContext.md +1 -1
  143. package/docs/api/interfaces/RBACLogger.md +1 -1
  144. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  149. package/docs/api/interfaces/RBACResult.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  151. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  157. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  159. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  160. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  161. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  162. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  163. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  164. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  165. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  166. package/docs/api/interfaces/RouteConfig.md +1 -1
  167. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  168. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  169. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  170. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  171. package/docs/api/interfaces/SetupIssue.md +41 -0
  172. package/docs/api/interfaces/StorageConfig.md +1 -1
  173. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  174. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  175. package/docs/api/interfaces/StorageListOptions.md +1 -1
  176. package/docs/api/interfaces/StorageListResult.md +1 -1
  177. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  178. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  179. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  180. package/docs/api/interfaces/StyleImport.md +1 -1
  181. package/docs/api/interfaces/SwitchProps.md +1 -1
  182. package/docs/api/interfaces/TabsContentProps.md +1 -1
  183. package/docs/api/interfaces/TabsListProps.md +1 -1
  184. package/docs/api/interfaces/TabsProps.md +1 -1
  185. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  186. package/docs/api/interfaces/TextareaProps.md +1 -1
  187. package/docs/api/interfaces/ToastActionElement.md +1 -1
  188. package/docs/api/interfaces/ToastProps.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  190. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  192. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  193. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  194. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  196. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  198. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  200. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  202. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  203. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  204. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  205. package/docs/api/interfaces/UserEventAccess.md +1 -1
  206. package/docs/api/interfaces/UserMenuProps.md +1 -1
  207. package/docs/api/interfaces/UserProfile.md +1 -1
  208. package/docs/api/modules.md +738 -42
  209. package/docs/api-reference/hooks.md +111 -0
  210. package/docs/api-reference/rpc-functions.md +1 -1
  211. package/docs/api-reference/utilities.md +184 -0
  212. package/docs/getting-started/installation-guide.md +75 -16
  213. package/docs/getting-started/quick-start.md +61 -11
  214. package/docs/implementation-guides/authentication.md +88 -12
  215. package/docs/implementation-guides/file-reference-system.md +2 -1
  216. package/docs/implementation-guides/file-upload-storage.md +21 -0
  217. package/docs/rbac/README.md +1 -0
  218. package/docs/rbac/compliance/compliance-guide.md +544 -0
  219. package/docs/rbac/getting-started.md +158 -33
  220. package/docs/standards/pace-core-compliance.md +432 -0
  221. package/eslint-config-pace-core.cjs +93 -0
  222. package/package.json +15 -3
  223. package/scripts/analyze-bundle.js +232 -0
  224. package/scripts/build-css.js +56 -0
  225. package/scripts/build-docs-incremental.js +1015 -0
  226. package/scripts/check-pace-core-compliance.cjs +2353 -0
  227. package/scripts/generate-docs.js +157 -0
  228. package/scripts/setup-build-cache.js +73 -0
  229. package/scripts/utils/command-runner.js +131 -0
  230. package/scripts/utils/env.js +33 -0
  231. package/scripts/utils/index.js +10 -0
  232. package/scripts/utils/logger.js +88 -0
  233. package/scripts/utils/path-helpers.js +37 -0
  234. package/scripts/validate-formats.js +133 -0
  235. package/scripts/validate-master.js +155 -0
  236. package/scripts/validate-pre-publish.js +140 -0
  237. package/scripts/validate-theme.js +142 -0
  238. package/src/components/Calendar/Calendar.tsx +8 -1
  239. package/src/components/Card/Card.tsx +47 -8
  240. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  241. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  242. package/src/components/DatePickerWithTimezone/README.md +135 -0
  243. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  244. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  245. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  246. package/src/components/DateTimeField/README.md +148 -0
  247. package/src/components/DateTimeField/index.ts +10 -0
  248. package/src/components/FileUpload/FileUpload.tsx +3 -0
  249. package/src/components/Header/Header.test.tsx +47 -18
  250. package/src/components/Header/Header.tsx +24 -6
  251. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  252. package/src/components/PaceAppLayout/README.md +9 -0
  253. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  254. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  255. package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
  256. package/src/components/index.ts +8 -0
  257. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  258. package/src/eslint-rules/pace-core-compliance.js +640 -0
  259. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  260. package/src/hooks/index.ts +2 -0
  261. package/src/hooks/useFileReference.test.ts +1 -0
  262. package/src/hooks/useFormDialog.ts +147 -0
  263. package/src/index.ts +27 -0
  264. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  265. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  266. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  267. package/src/rbac/compliance/database-validator.ts +165 -0
  268. package/src/rbac/compliance/index.ts +38 -0
  269. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  270. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  271. package/src/rbac/compliance/setup-validator.ts +131 -0
  272. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  273. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  274. package/src/rbac/docs/event-based-apps.md +285 -0
  275. package/src/rbac/errors.ts +11 -0
  276. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  277. package/src/rbac/index.ts +30 -0
  278. package/src/services/OrganisationService.ts +4 -0
  279. package/src/types/file-reference.ts +6 -0
  280. package/src/utils/__tests__/timezone.test.ts +345 -0
  281. package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
  282. package/src/utils/file-reference/index.ts +1 -0
  283. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  284. package/src/utils/formatting/formatting.ts +179 -0
  285. package/src/utils/index.ts +27 -1
  286. package/src/utils/location/index.ts +16 -0
  287. package/src/utils/location/location.test.ts +286 -0
  288. package/src/utils/location/location.ts +175 -0
  289. package/src/utils/timezone/index.ts +17 -0
  290. package/src/utils/timezone/timezone.test.ts +349 -0
  291. package/src/utils/timezone/timezone.ts +281 -0
  292. package/dist/chunk-CSOFYHAG.js.map +0 -1
  293. package/dist/chunk-FUEYYMX5.js.map +0 -1
  294. package/dist/chunk-HKIT6O7W.js +0 -198
  295. package/dist/chunk-HKIT6O7W.js.map +0 -1
  296. package/dist/chunk-KUEN3HFB.js +0 -94
  297. package/dist/chunk-KUEN3HFB.js.map +0 -1
  298. package/dist/chunk-M7W4CP3M.js.map +0 -1
  299. package/dist/chunk-PWAHJW4G.js.map +0 -1
  300. package/dist/chunk-QETLRQI6.js.map +0 -1
  301. package/dist/chunk-UHNYIBXL.js.map +0 -1
  302. package/dist/formatting-5wETwiGF.d.ts +0 -162
  303. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  304. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  305. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  306. /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
  307. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -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({
@@ -0,0 +1,285 @@
1
+ # Event-Based Apps with RBAC
2
+
3
+ This guide explains how to use the RBAC system for event-based applications where the organization context is automatically derived from the event context.
4
+
5
+ ## Overview
6
+
7
+ Event-based apps are applications that operate within the context of a specific event. Since events inherently belong to an organization, these apps don't need explicit organization context - it is automatically resolved from the event by the RBAC components.
8
+
9
+ ## Key Concepts
10
+
11
+ ### Automatic Organization Resolution
12
+ The RBAC components automatically resolve the organization from the event context when needed. You only need to provide the `eventId` (and optionally `appId`), and the system handles the rest.
13
+
14
+ ```typescript
15
+ // The components automatically resolve organisationId from eventId
16
+ const scope: Scope = {
17
+ eventId: 'event-123',
18
+ appId: 'app-456' // optional
19
+ // organisationId is automatically resolved
20
+ };
21
+ ```
22
+
23
+ ### Permission Hierarchy
24
+ For event-based apps, the permission hierarchy is:
25
+ 1. **Page-level permissions** (most specific)
26
+ 2. **Event-app permissions** (event + app specific)
27
+ 3. **Organization permissions** (automatically resolved from event)
28
+ 4. **Global permissions** (least specific)
29
+
30
+ ## Components
31
+
32
+ The same RBAC components work for both organization-based and event-based apps. They automatically detect the context and resolve the organization when needed.
33
+
34
+ ### PermissionEnforcer
35
+ Use this component for general permission enforcement in event-based apps:
36
+
37
+ ```tsx
38
+ import { PermissionEnforcer } from '@jmruthers/pace-core/rbac';
39
+
40
+ function EventDashboard() {
41
+ return (
42
+ <PermissionEnforcer
43
+ permissions={['read:events', 'update:participants']}
44
+ operation="dashboard"
45
+ >
46
+ <div>Event Dashboard Content</div>
47
+ </PermissionEnforcer>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### PagePermissionGuard
53
+ Use this component for page-level permission enforcement:
54
+
55
+ ```tsx
56
+ import { PagePermissionGuard } from '@jmruthers/pace-core/rbac';
57
+
58
+ function EventSettingsPage() {
59
+ return (
60
+ <PagePermissionGuard
61
+ pageName="settings"
62
+ operation="read"
63
+ >
64
+ <div>Event Settings</div>
65
+ </PagePermissionGuard>
66
+ );
67
+ }
68
+ ```
69
+
70
+ ### NavigationGuard
71
+ Use this component for navigation-level permission enforcement:
72
+
73
+ ```tsx
74
+ import { NavigationGuard } from '@jmruthers/pace-core/rbac';
75
+
76
+ const navigationItems = [
77
+ {
78
+ id: 'dashboard',
79
+ name: 'Dashboard',
80
+ path: '/event/dashboard',
81
+ permissions: ['read:events']
82
+ },
83
+ {
84
+ id: 'settings',
85
+ name: 'Settings',
86
+ path: '/event/settings',
87
+ permissions: ['update:events']
88
+ }
89
+ ];
90
+
91
+ function EventNavigation() {
92
+ return (
93
+ <nav>
94
+ {navigationItems.map(item => (
95
+ <NavigationGuard
96
+ key={item.id}
97
+ navigationItem={item}
98
+ >
99
+ <Link to={item.path}>{item.name}</Link>
100
+ </NavigationGuard>
101
+ ))}
102
+ </nav>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Hooks
108
+
109
+ ### useCan with Event Context
110
+ You can use the standard `useCan` hook with event-based scopes:
111
+
112
+ ```tsx
113
+ import { useCan } from '@jmruthers/pace-core/rbac';
114
+
115
+ function EventComponent() {
116
+ const { selectedEventId } = useUnifiedAuth();
117
+
118
+ const { can: canManageEvents } = useCan(
119
+ userId,
120
+ { eventId: selectedEventId },
121
+ 'update:events'
122
+ );
123
+
124
+ return (
125
+ <div>
126
+ {canManageEvents && (
127
+ <button>Manage Event</button>
128
+ )}
129
+ </div>
130
+ );
131
+ }
132
+ ```
133
+
134
+ ## Setup
135
+
136
+ ### 1. Event Context Provider
137
+ Ensure your app has event context available:
138
+
139
+ ```tsx
140
+ import { UnifiedAuthProvider } from '@jmruthers/pace-core';
141
+
142
+ function App() {
143
+ return (
144
+ <UnifiedAuthProvider>
145
+ {/* Your event-based app components */}
146
+ </UnifiedAuthProvider>
147
+ );
148
+ }
149
+ ```
150
+
151
+ ### 2. Event Selection
152
+ Make sure the event is selected in the auth context:
153
+
154
+ ```tsx
155
+ import { useUnifiedAuth } from '@jmruthers/pace-core';
156
+
157
+ function EventSelector() {
158
+ const { setSelectedEventId } = useUnifiedAuth();
159
+
160
+ const handleEventSelect = (eventId: string) => {
161
+ setSelectedEventId(eventId);
162
+ };
163
+
164
+ return (
165
+ <select onChange={(e) => handleEventSelect(e.target.value)}>
166
+ <option value="event-1">Event 1</option>
167
+ <option value="event-2">Event 2</option>
168
+ </select>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ## Permission Types
174
+
175
+ ### Event-App Permissions
176
+ These permissions are specific to an event and app combination:
177
+
178
+ ```typescript
179
+ const EVENT_APP_PERMISSIONS = {
180
+ // Event management
181
+ MANAGE_EVENT: 'update:event',
182
+ READ_EVENT: 'read:event',
183
+ UPDATE_EVENT: 'update:event',
184
+
185
+ // Participant management
186
+ MANAGE_PARTICIPANTS: 'update:participants',
187
+ READ_PARTICIPANTS: 'read:participants',
188
+ CREATE_PARTICIPANTS: 'create:participants',
189
+ UPDATE_PARTICIPANTS: 'update:participants',
190
+ DELETE_PARTICIPANTS: 'delete:participants',
191
+
192
+ // App-specific permissions
193
+ MANAGE_APP: 'update:app',
194
+ READ_APP: 'read:app',
195
+ UPDATE_APP: 'update:app',
196
+ };
197
+ ```
198
+
199
+ ### Page Permissions
200
+ Page permissions are specific to individual pages within an event app:
201
+
202
+ ```typescript
203
+ const PAGE_PERMISSIONS = {
204
+ DASHBOARD_READ: 'read:page.dashboard',
205
+ DASHBOARD_WRITE: 'write:page.dashboard',
206
+ SETTINGS_READ: 'read:page.settings',
207
+ SETTINGS_WRITE: 'write:page.settings',
208
+ PARTICIPANTS_READ: 'read:page.participants',
209
+ PARTICIPANTS_WRITE: 'write:page.participants',
210
+ };
211
+ ```
212
+
213
+ ## Best Practices
214
+
215
+ ### 1. Use Event-Based Components
216
+ Always use the event-based components (`EventPermissionEnforcer`, `EventPagePermissionGuard`, `EventNavigationGuard`) instead of the organization-based ones for event apps.
217
+
218
+ ### 2. Provide Explicit Scopes
219
+ When possible, provide explicit scopes to avoid context resolution overhead:
220
+
221
+ ```tsx
222
+ <EventPermissionEnforcer
223
+ scope={{ eventId: 'event-123', appId: 'app-456' }}
224
+ permissions={['read:events']}
225
+ >
226
+ <div>Content</div>
227
+ </EventPermissionEnforcer>
228
+ ```
229
+
230
+ ### 3. Handle Loading States
231
+ Event-based components automatically handle loading states while resolving the organization from the event:
232
+
233
+ ```tsx
234
+ <EventPermissionEnforcer
235
+ permissions={['read:events']}
236
+ loading={<div>Loading permissions...</div>}
237
+ >
238
+ <div>Content</div>
239
+ </EventPermissionEnforcer>
240
+ ```
241
+
242
+ ### 4. Error Handling
243
+ Handle cases where the organization cannot be resolved from the event:
244
+
245
+ ```tsx
246
+ <EventPermissionEnforcer
247
+ permissions={['read:events']}
248
+ onDenied={(permission, reason) => {
249
+ console.error(`Permission denied: ${permission}, reason: ${reason}`);
250
+ }}
251
+ >
252
+ <div>Content</div>
253
+ </EventPermissionEnforcer>
254
+ ```
255
+
256
+ ## Migration from Organization-Based Apps
257
+
258
+ If you're migrating from organization-based apps to event-based apps:
259
+
260
+ 1. Replace `PermissionEnforcer` with `EventPermissionEnforcer`
261
+ 2. Replace `PagePermissionGuard` with `EventPagePermissionGuard`
262
+ 3. Replace `NavigationGuard` with `EventNavigationGuard`
263
+ 4. Remove explicit organization context from your components
264
+ 5. Ensure event context is available in your app
265
+
266
+ ## Troubleshooting
267
+
268
+ ### Common Issues
269
+
270
+ 1. **"Event context is required" error**: Make sure `selectedEventId` is set in the auth context
271
+ 2. **"Could not resolve organization from event context" error**: Verify the event exists and has a valid `organisation_id`
272
+ 3. **Permission checks failing**: Ensure the user has the appropriate event-app roles assigned
273
+
274
+ ### Debug Mode
275
+
276
+ Enable debug mode to see detailed permission resolution:
277
+
278
+ ```typescript
279
+ import { createRBACConfig } from '@jmruthers/pace-core/rbac';
280
+
281
+ const config = createRBACConfig({
282
+ debug: true,
283
+ logLevel: 'debug'
284
+ });
285
+ ```