@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,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
+ ```
@@ -4,6 +4,7 @@ import {
4
4
  OrganisationContextRequiredError,
5
5
  InvalidScopeError,
6
6
  MissingUserContextError,
7
+ RBACNotInitializedError,
7
8
  } from './types';
8
9
 
9
10
  export enum RBACErrorCategory {
@@ -154,3 +155,13 @@ export function mapErrorCategoryToSecurityEventType(category: RBACErrorCategory)
154
155
  return 'unknown_error';
155
156
  }
156
157
  }
158
+
159
+ // Re-export error classes for convenience
160
+ export {
161
+ RBACError,
162
+ PermissionDeniedError,
163
+ OrganisationContextRequiredError,
164
+ InvalidScopeError,
165
+ MissingUserContextError,
166
+ RBACNotInitializedError,
167
+ };
@@ -13,25 +13,46 @@
13
13
  * import { useRoleManagement } from '@jmruthers/pace-core/rbac';
14
14
  *
15
15
  * function UserRolesComponent() {
16
- * const { revokeEventAppRole, grantEventAppRole, isLoading, error } = useRoleManagement();
16
+ * const {
17
+ * revokeEventAppRole,
18
+ * grantEventAppRole,
19
+ * grantGlobalRole,
20
+ * revokeGlobalRole,
21
+ * grantOrganisationRole,
22
+ * revokeOrganisationRole,
23
+ * isLoading,
24
+ * error
25
+ * } = useRoleManagement();
17
26
  *
18
- * const handleRevokeRole = async (roleId: string, roleData: EventAppRoleData) => {
19
- * const result = await revokeEventAppRole({
20
- * userId: roleData.user_id,
21
- * organisationId: roleData.organisation_id,
22
- * eventId: roleData.event_id,
23
- * appId: roleData.app_id,
24
- * role: roleData.role
27
+ * // Grant a global role
28
+ * const handleGrantGlobalRole = async () => {
29
+ * const result = await grantGlobalRole({
30
+ * user_id: userId,
31
+ * role: 'super_admin'
25
32
  * });
33
+ * if (result.success) {
34
+ * toast({ title: 'Role granted successfully' });
35
+ * }
36
+ * };
26
37
  *
38
+ * // Grant an organisation role
39
+ * const handleGrantOrgRole = async () => {
40
+ * const result = await grantOrganisationRole({
41
+ * user_id: userId,
42
+ * organisation_id: orgId,
43
+ * role: 'org_admin'
44
+ * });
27
45
  * if (result.success) {
28
- * toast({ title: 'Role revoked successfully' });
29
- * } else {
30
- * toast({ title: 'Failed to revoke role', variant: 'destructive' });
46
+ * toast({ title: 'Role granted successfully' });
31
47
  * }
32
48
  * };
33
49
  *
34
- * return <button onClick={() => handleRevokeRole(roleId, roleData)}>Revoke Role</button>;
50
+ * return (
51
+ * <div>
52
+ * <button onClick={handleGrantGlobalRole}>Grant Super Admin</button>
53
+ * <button onClick={handleGrantOrgRole}>Grant Org Admin</button>
54
+ * </div>
55
+ * );
35
56
  * }
36
57
  * ```
37
58
  */
@@ -48,6 +69,17 @@ export interface EventAppRoleData {
48
69
  role: 'viewer' | 'participant' | 'planner' | 'event_admin';
49
70
  }
50
71
 
72
+ export interface OrganisationRoleData {
73
+ user_id: UUID;
74
+ organisation_id: UUID;
75
+ role: 'supporter' | 'member' | 'leader' | 'org_admin';
76
+ }
77
+
78
+ export interface GlobalRoleData {
79
+ user_id: UUID;
80
+ role: 'super_admin';
81
+ }
82
+
51
83
  export interface RevokeEventAppRoleParams extends EventAppRoleData {
52
84
  revoked_by?: UUID;
53
85
  }
@@ -58,6 +90,26 @@ export interface GrantEventAppRoleParams extends EventAppRoleData {
58
90
  valid_to?: string | null;
59
91
  }
60
92
 
93
+ export interface RevokeOrganisationRoleParams extends OrganisationRoleData {
94
+ revoked_by?: UUID;
95
+ }
96
+
97
+ export interface GrantOrganisationRoleParams extends OrganisationRoleData {
98
+ granted_by?: UUID;
99
+ valid_from?: string;
100
+ valid_to?: string | null;
101
+ }
102
+
103
+ export interface RevokeGlobalRoleParams extends GlobalRoleData {
104
+ revoked_by?: UUID;
105
+ }
106
+
107
+ export interface GrantGlobalRoleParams extends GlobalRoleData {
108
+ granted_by?: UUID;
109
+ valid_from?: string;
110
+ valid_to?: string | null;
111
+ }
112
+
61
113
  export interface RoleManagementResult {
62
114
  success: boolean;
63
115
  message?: string;
@@ -244,10 +296,238 @@ export function useRoleManagement() {
244
296
  }
245
297
  }, [user?.id, supabase]);
246
298
 
299
+ /**
300
+ * Grant a global role using the unified RPC function
301
+ *
302
+ * This function uses the `rbac_role_grant` RPC which:
303
+ * - Runs with SECURITY DEFINER privileges
304
+ * - Includes proper permission checks
305
+ * - Automatically populates audit fields (granted_by, timestamps)
306
+ * - Complies with Row-Level Security policies
307
+ *
308
+ * @param params - Role grant parameters
309
+ * @returns Promise resolving to operation result with role ID
310
+ */
311
+ const grantGlobalRole = useCallback(async (
312
+ params: GrantGlobalRoleParams
313
+ ): Promise<RoleManagementResult> => {
314
+ setIsLoading(true);
315
+ setError(null);
316
+
317
+ try {
318
+ const { data, error: rpcError } = await supabase.rpc('rbac_role_grant', {
319
+ p_user_id: params.user_id,
320
+ p_role_type: 'global',
321
+ p_role_name: params.role,
322
+ p_context_id: null, // Global roles don't need context
323
+ p_granted_by: params.granted_by || user?.id || undefined
324
+ });
325
+
326
+ if (rpcError) {
327
+ throw new Error(rpcError.message || 'Failed to grant role');
328
+ }
329
+
330
+ // rbac_role_grant returns a table with success, message, role_id, error_code
331
+ const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
332
+
333
+ if (!result || !result.success) {
334
+ return {
335
+ success: false,
336
+ error: result?.message || result?.error_code || 'Failed to grant role',
337
+ message: result?.message
338
+ };
339
+ }
340
+
341
+ return {
342
+ success: true,
343
+ message: result.message || 'Role granted successfully',
344
+ roleId: result.role_id
345
+ };
346
+ } catch (err) {
347
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
348
+ setError(err instanceof Error ? err : new Error(errorMessage));
349
+ return {
350
+ success: false,
351
+ error: errorMessage
352
+ };
353
+ } finally {
354
+ setIsLoading(false);
355
+ }
356
+ }, [user?.id, supabase]);
357
+
358
+ /**
359
+ * Revoke a global role using the unified RPC function
360
+ *
361
+ * This function uses the `rbac_role_revoke` RPC which:
362
+ * - Runs with SECURITY DEFINER privileges
363
+ * - Includes proper permission checks
364
+ * - Automatically populates audit fields (revoked_by, timestamps)
365
+ * - Complies with Row-Level Security policies
366
+ *
367
+ * @param params - Role revocation parameters
368
+ * @returns Promise resolving to operation result
369
+ */
370
+ const revokeGlobalRole = useCallback(async (
371
+ params: RevokeGlobalRoleParams
372
+ ): Promise<RoleManagementResult> => {
373
+ setIsLoading(true);
374
+ setError(null);
375
+
376
+ try {
377
+ const { data, error: rpcError } = await supabase.rpc('rbac_role_revoke', {
378
+ p_user_id: params.user_id,
379
+ p_role_type: 'global',
380
+ p_role_name: params.role,
381
+ p_context_id: null, // Global roles don't need context
382
+ p_revoked_by: params.revoked_by || user?.id || undefined
383
+ });
384
+
385
+ if (rpcError) {
386
+ throw new Error(rpcError.message || 'Failed to revoke role');
387
+ }
388
+
389
+ // rbac_role_revoke returns a table with success, message, revoked_count, error_code
390
+ const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
391
+
392
+ return {
393
+ success: result?.success === true,
394
+ message: result?.message || undefined,
395
+ error: result?.success === false ? (result?.message || result?.error_code || 'Unknown error') : undefined
396
+ };
397
+ } catch (err) {
398
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
399
+ setError(err instanceof Error ? err : new Error(errorMessage));
400
+ return {
401
+ success: false,
402
+ error: errorMessage
403
+ };
404
+ } finally {
405
+ setIsLoading(false);
406
+ }
407
+ }, [user?.id, supabase]);
408
+
409
+ /**
410
+ * Grant an organisation role using the unified RPC function
411
+ *
412
+ * This function uses the `rbac_role_grant` RPC which:
413
+ * - Runs with SECURITY DEFINER privileges
414
+ * - Includes proper permission checks
415
+ * - Automatically populates audit fields (granted_by, timestamps)
416
+ * - Complies with Row-Level Security policies
417
+ *
418
+ * @param params - Role grant parameters
419
+ * @returns Promise resolving to operation result with role ID
420
+ */
421
+ const grantOrganisationRole = useCallback(async (
422
+ params: GrantOrganisationRoleParams
423
+ ): Promise<RoleManagementResult> => {
424
+ setIsLoading(true);
425
+ setError(null);
426
+
427
+ try {
428
+ const { data, error: rpcError } = await supabase.rpc('rbac_role_grant', {
429
+ p_user_id: params.user_id,
430
+ p_role_type: 'organisation',
431
+ p_role_name: params.role,
432
+ p_context_id: params.organisation_id, // Organisation ID as context
433
+ p_granted_by: params.granted_by || user?.id || undefined
434
+ });
435
+
436
+ if (rpcError) {
437
+ throw new Error(rpcError.message || 'Failed to grant role');
438
+ }
439
+
440
+ // rbac_role_grant returns a table with success, message, role_id, error_code
441
+ const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
442
+
443
+ if (!result || !result.success) {
444
+ return {
445
+ success: false,
446
+ error: result?.message || result?.error_code || 'Failed to grant role',
447
+ message: result?.message
448
+ };
449
+ }
450
+
451
+ return {
452
+ success: true,
453
+ message: result.message || 'Role granted successfully',
454
+ roleId: result.role_id
455
+ };
456
+ } catch (err) {
457
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
458
+ setError(err instanceof Error ? err : new Error(errorMessage));
459
+ return {
460
+ success: false,
461
+ error: errorMessage
462
+ };
463
+ } finally {
464
+ setIsLoading(false);
465
+ }
466
+ }, [user?.id, supabase]);
467
+
468
+ /**
469
+ * Revoke an organisation role using the unified RPC function
470
+ *
471
+ * This function uses the `rbac_role_revoke` RPC which:
472
+ * - Runs with SECURITY DEFINER privileges
473
+ * - Includes proper permission checks
474
+ * - Automatically populates audit fields (revoked_by, timestamps)
475
+ * - Complies with Row-Level Security policies
476
+ *
477
+ * @param params - Role revocation parameters
478
+ * @returns Promise resolving to operation result
479
+ */
480
+ const revokeOrganisationRole = useCallback(async (
481
+ params: RevokeOrganisationRoleParams
482
+ ): Promise<RoleManagementResult> => {
483
+ setIsLoading(true);
484
+ setError(null);
485
+
486
+ try {
487
+ const { data, error: rpcError } = await supabase.rpc('rbac_role_revoke', {
488
+ p_user_id: params.user_id,
489
+ p_role_type: 'organisation',
490
+ p_role_name: params.role,
491
+ p_context_id: params.organisation_id, // Organisation ID as context
492
+ p_revoked_by: params.revoked_by || user?.id || undefined
493
+ });
494
+
495
+ if (rpcError) {
496
+ throw new Error(rpcError.message || 'Failed to revoke role');
497
+ }
498
+
499
+ // rbac_role_revoke returns a table with success, message, revoked_count, error_code
500
+ const result = Array.isArray(data) && data.length > 0 ? data[0] : null;
501
+
502
+ return {
503
+ success: result?.success === true,
504
+ message: result?.message || undefined,
505
+ error: result?.success === false ? (result?.message || result?.error_code || 'Unknown error') : undefined
506
+ };
507
+ } catch (err) {
508
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
509
+ setError(err instanceof Error ? err : new Error(errorMessage));
510
+ return {
511
+ success: false,
512
+ error: errorMessage
513
+ };
514
+ } finally {
515
+ setIsLoading(false);
516
+ }
517
+ }, [user?.id, supabase]);
518
+
247
519
  return {
520
+ // Event app roles (existing)
248
521
  revokeEventAppRole,
249
522
  grantEventAppRole,
250
523
  revokeRoleById,
524
+ // Global roles (new)
525
+ grantGlobalRole,
526
+ revokeGlobalRole,
527
+ // Organisation roles (new)
528
+ grantOrganisationRole,
529
+ revokeOrganisationRole,
530
+ // Shared state
251
531
  isLoading,
252
532
  error
253
533
  };
package/src/rbac/index.ts CHANGED
@@ -113,3 +113,33 @@ export {
113
113
 
114
114
  // Permissions
115
115
  export * from './permissions';
116
+
117
+ // Compliance
118
+ export {
119
+ isRBACInitialized,
120
+ validateRBACSetup,
121
+ getSetupIssues,
122
+ type SetupIssue,
123
+ type ComplianceResult,
124
+ } from './compliance/setup-validator';
125
+
126
+ export {
127
+ checkRuntimeCompliance,
128
+ validateAndWarn,
129
+ type RuntimeComplianceResult,
130
+ } from './compliance/runtime-compliance';
131
+
132
+ export {
133
+ validateDatabaseConfiguration,
134
+ type DatabaseComplianceResult,
135
+ type DatabaseIssue,
136
+ } from './compliance/database-validator';
137
+
138
+ export {
139
+ getQuickFixes,
140
+ getCustomAuthCodeFixes,
141
+ getDuplicateConfigFixes,
142
+ getUnprotectedPageFixes,
143
+ getDirectSupabaseAuthFixes,
144
+ type QuickFix,
145
+ } from './compliance/quick-fix-suggestions';
@@ -519,6 +519,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
519
519
 
520
520
  // Auto-select organisation: try persisted, then primary, then first
521
521
  let initialOrg: Organisation | null = null;
522
+ let selectionMethod: 'persisted' | 'admin' | 'first' = 'first';
522
523
 
523
524
  // 1. Try to restore from localStorage
524
525
  try {
@@ -530,6 +531,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
530
531
  const validPersistedOrg = activeOrgs.find(org => org.id === persistedOrg.id);
531
532
  if (validPersistedOrg) {
532
533
  initialOrg = validPersistedOrg;
534
+ selectionMethod = 'persisted';
533
535
  } else {
534
536
  logger.warn("OrganisationService", "Persisted organisation not found in active orgs, clearing cache");
535
537
  localStorage.removeItem('pace-core-selected-organisation');
@@ -552,6 +554,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
552
554
  const foundOrg = organisations.find((org) => org.id === adminMembership.organisation_id);
553
555
  if (foundOrg) {
554
556
  initialOrg = foundOrg;
557
+ selectionMethod = 'admin';
555
558
  }
556
559
  }
557
560
  }
@@ -559,6 +562,7 @@ export class OrganisationService extends BaseService implements IOrganisationSer
559
562
  // 3. Fall back to first organisation
560
563
  if (!initialOrg) {
561
564
  initialOrg = activeOrgs[0];
565
+ selectionMethod = 'first';
562
566
  }
563
567
 
564
568
  if (!initialOrg) {