@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,148 @@
1
+ # DateTimeField Component
2
+
3
+ Form input component for datetime values with automatic UTC ↔ timezone conversion.
4
+
5
+ ## Overview
6
+
7
+ The `DateTimeField` component provides a datetime input field that automatically handles conversion between UTC (for storage) and a specified timezone (for display). It prevents unwanted conversions during user editing and displays timezone information when not using UTC.
8
+
9
+ ## Features
10
+
11
+ - Automatic UTC ↔ timezone conversion
12
+ - Prevents unwanted conversions during user editing
13
+ - Shows timezone information when not UTC
14
+ - Supports both ISO string and Date object values
15
+ - Uses native `datetime-local` input type
16
+ - Accessible form field with proper labels
17
+ - Error state support
18
+ - Helper text support
19
+
20
+ ## Usage
21
+
22
+ ### Basic Example
23
+
24
+ ```tsx
25
+ import { DateTimeField } from '@jmruthers/pace-core/components';
26
+ import { useState } from 'react';
27
+
28
+ function EventForm() {
29
+ const [startTime, setStartTime] = useState<string>();
30
+
31
+ return (
32
+ <DateTimeField
33
+ label="Start Time"
34
+ value={startTime}
35
+ onChange={setStartTime}
36
+ timezone="America/New_York"
37
+ required
38
+ />
39
+ );
40
+ }
41
+ ```
42
+
43
+ ### With Date Object
44
+
45
+ ```tsx
46
+ import { DateTimeField } from '@jmruthers/pace-core/components';
47
+ import { useState } from 'react';
48
+
49
+ function EventForm() {
50
+ const [startTime, setStartTime] = useState<Date>();
51
+
52
+ return (
53
+ <DateTimeField
54
+ label="Start Time"
55
+ value={startTime}
56
+ onChange={setStartTime}
57
+ timezone="America/New_York"
58
+ returnAsDate
59
+ />
60
+ );
61
+ }
62
+ ```
63
+
64
+ ### With Error State
65
+
66
+ ```tsx
67
+ <DateTimeField
68
+ label="Start Time"
69
+ value={startTime}
70
+ onChange={setStartTime}
71
+ timezone="America/New_York"
72
+ error="Please select a valid start time"
73
+ required
74
+ />
75
+ ```
76
+
77
+ ### With Helper Text
78
+
79
+ ```tsx
80
+ <DateTimeField
81
+ label="Start Time"
82
+ value={startTime}
83
+ onChange={setStartTime}
84
+ timezone="America/New_York"
85
+ helperText="Select the event start time in your local timezone"
86
+ />
87
+ ```
88
+
89
+ ## API
90
+
91
+ ### Props
92
+
93
+ | Prop | Type | Default | Description |
94
+ |------|------|---------|-------------|
95
+ | `label` | `string` | **required** | Field label |
96
+ | `value` | `string \| Date \| undefined` | - | UTC date value (ISO string, Date object, or undefined) |
97
+ | `onChange` | `(value: string \| Date \| undefined) => void` | **required** | Change handler that receives UTC value |
98
+ | `timezone` | `string` | `'UTC'` | Target timezone for display (IANA timezone string) |
99
+ | `required` | `boolean` | `false` | Whether the field is required |
100
+ | `className` | `string` | - | Additional CSS classes |
101
+ | `returnAsDate` | `boolean` | `false` | If true, onChange returns Date object instead of ISO string |
102
+ | `id` | `string` | - | Input id (auto-generated if not provided) |
103
+ | `helperText` | `string` | - | Helper text to display below the label |
104
+ | `error` | `string` | - | Error message to display |
105
+
106
+ ## Behavior
107
+
108
+ ### Timezone Conversion
109
+
110
+ The component automatically converts between UTC (for storage) and the specified timezone (for display):
111
+
112
+ - **Display**: UTC values are converted to the specified timezone for display in the input
113
+ - **Input**: User-entered values (in the timezone) are converted back to UTC before calling `onChange`
114
+
115
+ ### Editing Prevention
116
+
117
+ The component tracks editing state to prevent unwanted conversions while the user is typing. Conversions only occur when:
118
+ - The component receives a new `value` prop (controlled component)
119
+ - The user finishes editing (blur event)
120
+
121
+ ### Timezone Display
122
+
123
+ - When `timezone` is `'UTC'`: No timezone indicator is shown
124
+ - When `timezone` matches the user's browser timezone: Shows "Local"
125
+ - When `timezone` is different: Shows the timezone name (e.g., "America/New_York")
126
+
127
+ ## Accessibility
128
+
129
+ - Proper label association with `htmlFor`
130
+ - Required field indicators for screen readers
131
+ - Error messages with `role="alert"`
132
+ - Keyboard navigation support
133
+ - Focus management
134
+
135
+ ## Edge Cases
136
+
137
+ - **Invalid dates**: Returns empty string in input, calls `onChange(undefined)`
138
+ - **Null/undefined values**: Displays empty input
139
+ - **Invalid timezone**: Falls back to UTC behavior
140
+
141
+ ## Dependencies
142
+
143
+ - `date-fns` - Date formatting and parsing
144
+ - `date-fns-tz` - Timezone conversion
145
+ - `@jmruthers/pace-core/components/Label` - Label component
146
+ - `@jmruthers/pace-core/components/Input` - Input component
147
+ - `@jmruthers/pace-core/utils/timezone` - Timezone utilities
148
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file DateTimeField Component Exports
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DateTimeField
5
+ * @since 0.1.0
6
+ */
7
+
8
+ export { DateTimeField } from './DateTimeField';
9
+ export type { DateTimeFieldProps } from './DateTimeField';
10
+
@@ -22,6 +22,7 @@ export interface FileUploadProps {
22
22
  organisation_id: string;
23
23
  app_id?: string; // Optional - will be resolved from app name if not provided
24
24
  category: FileCategory;
25
+ pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
25
26
  accept?: string;
26
27
  maxSize?: number;
27
28
  multiple?: boolean;
@@ -50,6 +51,7 @@ export function FileUpload({
50
51
  organisation_id,
51
52
  app_id,
52
53
  category,
54
+ pageContext,
53
55
  accept = '*/*',
54
56
  maxSize = 10 * 1024 * 1024, // 10MB default
55
57
  multiple = false,
@@ -284,6 +286,7 @@ export function FileUpload({
284
286
  organisation_id,
285
287
  app_id: resolvedAppId ? assertAppId(resolvedAppId) : assertAppId(''),
286
288
  category,
289
+ pageContext,
287
290
  is_public: isPublic
288
291
  }, file);
289
292
 
@@ -60,6 +60,14 @@ vi.mock('../EventSelector', () => ({
60
60
  ),
61
61
  }));
62
62
 
63
+ vi.mock('../OrganisationSelector', () => ({
64
+ OrganisationSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
65
+ <div data-testid={testId || 'org-selector'} className={className}>
66
+ <button>{placeholder}</button>
67
+ </div>
68
+ ),
69
+ }));
70
+
63
71
  // Test data
64
72
  const mockUser: User = {
65
73
  id: '123',
@@ -312,16 +320,8 @@ describe('Header Component', () => {
312
320
  expect(screen.getByRole('button', { name: 'Select event' })).toBeInTheDocument();
313
321
  });
314
322
 
315
- it('renders placeholder when showEventSelector is false', () => {
316
- const { container } = renderWithProviders(<Header showEventSelector={false} />);
317
-
318
- const placeholder = container.querySelector('del.invisible');
319
- expect(placeholder).toBeInTheDocument();
320
- expect(placeholder).toHaveTextContent('Event Selector N/A');
321
- });
322
-
323
- it('preserves grid layout when event selector is hidden', () => {
324
- const { container } = renderWithProviders(
323
+ it('preserves layout when event selector is hidden', () => {
324
+ renderWithProviders(
325
325
  <Header
326
326
  showEventSelector={false}
327
327
  user={mockUser}
@@ -333,15 +333,47 @@ describe('Header Component', () => {
333
333
  expect(nav).toBeInTheDocument();
334
334
  expect(nav).toBeVisible();
335
335
 
336
- // Placeholder should maintain grid structure
337
- const placeholder = container.querySelector('del.invisible');
338
- expect(placeholder).toBeInTheDocument();
336
+ // Event selector should not be rendered
337
+ expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
339
338
 
340
339
  // User menu should still be present and positioned correctly
341
340
  expect(screen.getByTestId('user-menu')).toBeInTheDocument();
342
341
  });
343
342
  });
344
343
 
344
+ // Organisation Selector tests
345
+ describe('Organisation Selector', () => {
346
+ it('does not render organisation selector by default', () => {
347
+ renderWithProviders(<Header />);
348
+
349
+ expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
350
+ });
351
+
352
+ it('renders organisation selector when showOrgSelector is true', () => {
353
+ renderWithProviders(<Header showOrgSelector={true} />);
354
+
355
+ expect(screen.getByTestId('org-selector')).toBeInTheDocument();
356
+ });
357
+
358
+ it('does not render organisation selector when showOrgSelector is false', () => {
359
+ renderWithProviders(<Header showOrgSelector={false} />);
360
+
361
+ expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
362
+ });
363
+
364
+ it('can render both organisation and event selectors together', () => {
365
+ renderWithProviders(
366
+ <Header
367
+ showOrgSelector={true}
368
+ showEventSelector={true}
369
+ />
370
+ );
371
+
372
+ expect(screen.getByTestId('org-selector')).toBeInTheDocument();
373
+ expect(screen.getByTestId('event-selector')).toBeInTheDocument();
374
+ });
375
+ });
376
+
345
377
  // Custom actions tests
346
378
  describe('Custom Actions', () => {
347
379
  it('renders custom actions when provided', () => {
@@ -560,7 +592,7 @@ describe('Header Component', () => {
560
592
  });
561
593
 
562
594
  it('renders minimal configuration', () => {
563
- const { container } = renderWithProviders(
595
+ renderWithProviders(
564
596
  <Header
565
597
  showEventSelector={false}
566
598
  showUserMenu={false}
@@ -573,10 +605,7 @@ describe('Header Component', () => {
573
605
  expect(screen.queryByTestId('navigation-menu')).not.toBeInTheDocument();
574
606
  expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
575
607
  expect(screen.queryByTestId('user-menu')).not.toBeInTheDocument();
576
-
577
- // Should have placeholder for event selector
578
- const placeholder = container.querySelector('del.invisible');
579
- expect(placeholder).toBeInTheDocument();
608
+ expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
580
609
  });
581
610
  });
582
611
 
@@ -5,13 +5,14 @@
5
5
  * @since 0.1.0
6
6
  *
7
7
  * A comprehensive header component for application layouts with navigation,
8
- * user menu, event selector, and customizable branding.
8
+ * user menu, organisation selector, event selector, and customizable branding.
9
9
  *
10
10
  * Features:
11
11
  * - Customizable logo (URL or component)
12
12
  * - Clickable logo that routes to dashboard (configurable)
13
13
  * - Navigation menu integration
14
14
  * - User menu with authentication
15
+ * - Organisation selector for multi-organisation applications
15
16
  * - Event selector for multi-tenant applications
16
17
  * - Custom actions support
17
18
  * - Responsive design
@@ -82,6 +83,7 @@
82
83
  * - Tailwind CSS - Styling
83
84
  * - NavigationMenu component
84
85
  * - UserMenu component
86
+ * - OrganisationSelector component
85
87
  * - EventSelector component
86
88
  */
87
89
 
@@ -90,6 +92,7 @@ import { Link } from 'react-router-dom';
90
92
  import { User } from '@supabase/supabase-js';
91
93
  import { cn } from '../../utils/core/cn';
92
94
  import { EventSelector } from '../EventSelector';
95
+ import { OrganisationSelector } from '../OrganisationSelector';
93
96
  import { UserMenu } from '../UserMenu';
94
97
  import { NavigationMenu } from '../NavigationMenu';
95
98
  import type { NavigationItem } from '../NavigationMenu';
@@ -121,6 +124,8 @@ export interface HeaderProps {
121
124
  className?: string;
122
125
  /** Show/hide event selector */
123
126
  showEventSelector?: boolean;
127
+ /** Show/hide organisation selector */
128
+ showOrgSelector?: boolean;
124
129
  /** Show/hide user menu */
125
130
  showUserMenu?: boolean;
126
131
  /** Current path for navigation highlighting */
@@ -244,6 +249,7 @@ export function Header({
244
249
  userMenu,
245
250
  className,
246
251
  showEventSelector = true,
252
+ showOrgSelector = false,
247
253
  showUserMenu = true,
248
254
  currentPath,
249
255
  onNavigate,
@@ -311,19 +317,30 @@ export function Header({
311
317
  )}
312
318
 
313
319
 
314
- {/* Right side: Event Selector, Actions, and User Menu */}
320
+ {/* Right side: Organisation Selector, Event Selector, Actions, and User Menu */}
321
+ <div className="flex items-center gap-4 justify-end">
322
+ {/* Organisation Selector */}
323
+ {showOrgSelector ? (
324
+ <OrganisationSelector
325
+ placeholder="Select organisation"
326
+ className="w-64"
327
+ data-testid="org-selector"
328
+ compact={true}
329
+ />
330
+ ) : null}
315
331
 
316
332
  {/* Event Selector */}
317
333
  {showEventSelector ? (
318
334
  <EventSelector
319
335
  placeholder="Select event"
320
- className="justify-self-end w-96"
336
+ className="w-96"
321
337
  data-testid="event-selector"
322
338
  />
323
- ) : (
324
- <del className="justify-self-end invisible">Event Selector N/A</del>
325
- )}
339
+ ) : null}
340
+ </div>
326
341
 
342
+ {/* Custom Actions and User Menu */}
343
+ <div className="flex items-center gap-4">
327
344
  {/* Custom Actions */}
328
345
  {actions}
329
346
 
@@ -340,6 +357,7 @@ export function Header({
340
357
  />
341
358
  )
342
359
  )}
360
+ </div>
343
361
 
344
362
  </nav>
345
363
  </header>
@@ -128,6 +128,8 @@ export interface PaceAppLayoutProps {
128
128
  navItems?: NavigationItem[];
129
129
  /** Show/hide event selector in the header */
130
130
  showEventSelector?: boolean;
131
+ /** Show/hide organisation selector in the header */
132
+ showOrgSelector?: boolean;
131
133
  /** Custom actions to display in the header (between event selector and user menu) */
132
134
  headerActions?: React.ReactNode;
133
135
  /** Custom logo component (overrides default logo) */
@@ -344,6 +346,7 @@ export function PaceAppLayout({
344
346
  appName,
345
347
  navItems,
346
348
  showEventSelector,
349
+ showOrgSelector,
347
350
  headerActions,
348
351
  customLogo,
349
352
  logoHref = '/dashboard',
@@ -573,16 +576,25 @@ export function PaceAppLayout({
573
576
  const hasOrganisationContext = currentScope.organisationId;
574
577
  const hasUser = !!user?.id;
575
578
 
576
- // If we have user + organisation, show items immediately (optimistic rendering)
577
- // Then filter them when permissions are fully loaded
578
- if (hasUser && hasOrganisationContext) {
579
- // We have minimum required context - proceed with filtering
580
- // Don't wait for appId or eventId - we can filter with organisationId alone
581
- // Permission checks will work with resource permissions even without appId
582
- } else if (!hasUser || !hasOrganisationContext) {
583
- // Missing required context - wait for it
579
+ // BUG FIX: When showOrgSelector is enabled, show navigation optimistically while waiting
580
+ // for organisation selection. Only hide navigation if user is not loaded.
581
+ // This prevents navigation from disappearing after initial render while waiting for org selection.
582
+ if (!hasUser) {
583
+ // User not loaded yet - show all items until user is ready
584
+ if (isMounted) {
585
+ setFilteredMenuItems(baseMenuItems);
586
+ }
587
+ return;
588
+ }
589
+
590
+ // If no organisation context yet, show items optimistically if org selector is enabled
591
+ // This allows users to see navigation while they select an organisation
592
+ // Only proceed with permission filtering once organisation is selected
593
+ if (!hasOrganisationContext) {
584
594
  if (isMounted) {
585
- setFilteredMenuItems([]);
595
+ // Show items optimistically when org selector is enabled, otherwise show all items
596
+ // This prevents navigation from disappearing while waiting for organisation selection
597
+ setFilteredMenuItems(baseMenuItems);
586
598
  }
587
599
  return;
588
600
  }
@@ -612,15 +624,6 @@ export function PaceAppLayout({
612
624
  }
613
625
  }
614
626
 
615
- // If no organisation context yet, show all items until context is ready
616
- // This prevents navigation from being empty while context loads
617
- if (!currentScope.organisationId) {
618
- if (isMounted) {
619
- setFilteredMenuItems(baseMenuItems);
620
- }
621
- return;
622
- }
623
-
624
627
  // Organisation context is ready - now filter items based on permissions
625
628
  // OPTIMIZATION: Use batch permission map instead of individual checks to avoid rate limits
626
629
  // This makes 1 call instead of N calls (where N = number of navigation items)
@@ -645,7 +648,9 @@ export function PaceAppLayout({
645
648
  const filtered = baseMenuItems.map((item) => {
646
649
  if (!item.href) return { item, hasAccess: true };
647
650
 
648
- const pageId = pageIdMapping[item.href] || item.href.slice(1) || 'home';
651
+ // Extract page ID from href: remove leading slash, fallback to 'dashboard' for root
652
+ // This matches database page names in rbac_app_pages
653
+ const pageId = pageIdMapping[item.href] || (item.href === '/' ? 'dashboard' : item.href.slice(1)) || 'dashboard';
649
654
  const permission = routePermissions[item.href] || defaultPermission;
650
655
  const fullPermission: Permission = permission.includes(':')
651
656
  ? (permission as Permission)
@@ -663,6 +668,9 @@ export function PaceAppLayout({
663
668
  .filter(({ hasAccess }) => hasAccess)
664
669
  .map(({ item }) => item);
665
670
 
671
+ // SECURITY: Never show all items if permission check fails - this would be a security risk
672
+ // If all items are filtered out, it means the user doesn't have permission to see any navigation
673
+ // This is the correct behavior - better to show nothing than show unauthorized items
666
674
  setFilteredMenuItems(accessibleItems);
667
675
  } catch (error) {
668
676
  // On error, fall back to showing all items (graceful degradation)
@@ -679,7 +687,7 @@ export function PaceAppLayout({
679
687
  return () => {
680
688
  isMounted = false;
681
689
  };
682
- }, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId]);
690
+ }, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId, selectedOrganisation?.id]);
683
691
 
684
692
  // NEW: Phase 2 - Enhanced Routing Features
685
693
  // Check route access for role-based routing
@@ -883,6 +891,7 @@ export function PaceAppLayout({
883
891
  }
884
892
  }}
885
893
  showEventSelector={showEventSelector}
894
+ showOrgSelector={showOrgSelector}
886
895
  showUserMenu={showUserMenu}
887
896
  className={headerClassName || "sticky top-0 z-[40] w-full"}
888
897
  />
@@ -17,6 +17,7 @@ A comprehensive application layout component that provides a consistent structur
17
17
  - Flexible content area
18
18
  - Branding support
19
19
  - **Event selector control** - can be hidden for non-event applications
20
+ - **Organisation selector control** - can be shown for multi-organisation applications
20
21
  - **Header customization** - custom logo, actions, user menu, and styling
21
22
  - **Clickable logo** - logo automatically routes to dashboard page (configurable)
22
23
 
@@ -27,6 +28,7 @@ A comprehensive application layout component that provides a consistent structur
27
28
  | `appName` | `string` | required | The name of the application to be displayed in the header |
28
29
  | `navItems` | `NavigationItem[]` | optional | Navigation items for the header menu. If not provided, uses default navigation |
29
30
  | `showEventSelector` | `boolean` | `true` | Show/hide event selector in the header |
31
+ | `showOrgSelector` | `boolean` | `false` | Show/hide organisation selector in the header |
30
32
  | `headerActions` | `React.ReactNode` | optional | Custom actions to display in the header (between event selector and user menu) |
31
33
  | `customLogo` | `React.ReactNode` | optional | Custom logo component (overrides default logo) |
32
34
  | `logoHref` | `string` | `'/dashboard'` | URL to navigate to when logo is clicked (e.g., '/dashboard', '/home') |
@@ -243,6 +245,13 @@ function App() {
243
245
  - Single-tenant applications
244
246
  - Administrative dashboards that don't depend on events
245
247
 
248
+ ### Show Organisation Selector (`showOrgSelector={true}`)
249
+ - Multi-organisation applications
250
+ - Apps where users need to switch between organisations
251
+ - Organisation management tools
252
+ - Multi-tenant applications with organisation context
253
+ - Apps requiring organisation-scoped data access
254
+
246
255
  ### Custom Header Components
247
256
  - Applications requiring custom branding
248
257
  - Apps with specific header actions (search, notifications, etc.)
@@ -166,7 +166,7 @@ export interface PaceLoginPageProps {
166
166
  */
167
167
  export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
168
168
  appName = 'Pace',
169
- onSuccessRedirectPath = '/user-dashboard',
169
+ onSuccessRedirectPath = '/',
170
170
  requireAppAccess = false
171
171
  }) => {
172
172
  const { signIn, isAuthenticated, isLoading, authError, user, supabase } = useUnifiedAuth();