@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
@@ -1829,37 +1829,51 @@ A comprehensive file upload component with drag-and-drop support, validation, an
1829
1829
  ```typescript
1830
1830
  interface FileUploadProps {
1831
1831
  supabase: SupabaseClient;
1832
- appName: string;
1833
- orgId: string;
1834
- onUploadComplete?: (fileRef: FileReference) => void;
1835
- onUploadStart?: (file: File) => void;
1832
+ table_name: string;
1833
+ record_id: string;
1834
+ organisation_id: string;
1835
+ app_id?: string; // Optional - will be resolved from app name if not provided
1836
+ category: FileCategory; // File category for metadata (stored in file_metadata JSONB field)
1837
+ folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
1838
+ pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
1836
1839
  accept?: string;
1837
1840
  maxSize?: number;
1838
1841
  multiple?: boolean;
1839
1842
  disabled?: boolean;
1843
+ isPublic?: boolean; // Whether files should be uploaded to public-files bucket
1840
1844
  className?: string;
1845
+ showPreview?: boolean; // Show image preview for accepted files
1846
+ showProgress?: boolean; // Show upload progress bar
1847
+ onUploadSuccess?: (result: FileUploadResult) => void;
1848
+ onUploadError?: (error: string, file?: File) => void;
1849
+ onProgress?: (progress: UploadProgress) => void;
1841
1850
  children?: React.ReactNode;
1842
1851
  }
1843
1852
  ```
1844
1853
 
1854
+ **Note:** The `category` prop is used for metadata purposes (stored in the `file_metadata` JSONB field), while the `folder` prop determines the actual storage path: `{orgId}/{folder}/{timestamp-uuid-filename}`. You can use the same value for both (e.g., `category={FileCategory.PROFILE_PHOTOS}` and `folder="profile_photos"`), or use different values if needed.
1855
+
1845
1856
  #### Usage
1846
1857
 
1847
1858
  ```tsx
1848
- import { FileUpload, FileCategory, logger } from '@jmruthers/pace-core';
1859
+ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
1849
1860
 
1850
1861
  function MyFileUpload() {
1851
- const handleUpload = (fileRef: FileReference) => {
1852
- logger.debug('FileUpload', 'File uploaded:', { fileId: fileRef.id, fileName: fileRef.name });
1853
- };
1854
-
1855
1862
  return (
1856
1863
  <FileUpload
1857
1864
  supabase={supabase}
1858
- appName="my-app"
1859
- orgId={organisationId}
1860
- onUploadComplete={handleUpload}
1865
+ table_name="pace_person"
1866
+ record_id={personId}
1867
+ organisation_id={organisationId}
1868
+ category={FileCategory.PROFILE_PHOTOS}
1869
+ folder="profile_photos"
1870
+ pageContext="configuration"
1861
1871
  accept="image/*"
1862
1872
  maxSize={2 * 1024 * 1024} // 2MB
1873
+ onUploadSuccess={(result) => {
1874
+ console.log('Uploaded:', result.file_reference);
1875
+ console.log('URL:', result.file_url);
1876
+ }}
1863
1877
  >
1864
1878
  <div className="border-2 border-dashed border-main-300 rounded-lg p-8 text-center">
1865
1879
  <p>Drag and drop files here or click to browse</p>
@@ -486,6 +486,117 @@ function UserForm() {
486
486
  }
487
487
  ```
488
488
 
489
+ ### useFormDialog
490
+
491
+ Generic React hook for managing form dialog state (open/close, form data, reset behavior).
492
+
493
+ ```typescript
494
+ interface UseFormDialogOptions<T = unknown> {
495
+ onOpenChange?: (open: boolean) => void;
496
+ resetOnClose?: boolean; // Default: true
497
+ }
498
+
499
+ interface UseFormDialogReturn<T = unknown> {
500
+ isOpen: boolean;
501
+ formData: T | null;
502
+ openDialog: (data?: T | null) => void;
503
+ closeDialog: () => void;
504
+ handleOpenChange: (open: boolean) => void;
505
+ }
506
+
507
+ function useFormDialog<T = unknown>(
508
+ options?: UseFormDialogOptions<T>
509
+ ): UseFormDialogReturn<T>;
510
+ ```
511
+
512
+ #### Usage
513
+
514
+ ```tsx
515
+ import { useFormDialog } from '@jmruthers/pace-core';
516
+ import { Dialog, DialogContent, DialogTrigger } from '@jmruthers/pace-core';
517
+
518
+ interface ContactFormData {
519
+ id: string;
520
+ name: string;
521
+ email: string;
522
+ }
523
+
524
+ function ContactDialog() {
525
+ const { isOpen, formData, openDialog, closeDialog, handleOpenChange } =
526
+ useFormDialog<ContactFormData>();
527
+
528
+ return (
529
+ <>
530
+ <button onClick={() => openDialog({ id: '1', name: 'John', email: 'john@example.com' })}>
531
+ Edit Contact
532
+ </button>
533
+ <Dialog open={isOpen} onOpenChange={handleOpenChange}>
534
+ <DialogContent>
535
+ {formData && <p>Editing: {formData.name}</p>}
536
+ {/* Form content */}
537
+ </DialogContent>
538
+ </Dialog>
539
+ </>
540
+ );
541
+ }
542
+ ```
543
+
544
+ #### Basic Usage
545
+
546
+ ```tsx
547
+ // Simple dialog without form data
548
+ const { isOpen, openDialog, closeDialog } = useFormDialog();
549
+
550
+ // Open dialog
551
+ openDialog();
552
+
553
+ // Close dialog
554
+ closeDialog();
555
+ ```
556
+
557
+ #### With Form Data
558
+
559
+ ```tsx
560
+ interface UserData {
561
+ id: string;
562
+ name: string;
563
+ }
564
+
565
+ const { isOpen, formData, openDialog } = useFormDialog<UserData>();
566
+
567
+ // Open with data
568
+ openDialog({ id: '1', name: 'John' });
569
+
570
+ // Access form data
571
+ if (formData) {
572
+ console.log(formData.name);
573
+ }
574
+ ```
575
+
576
+ #### Controlled Usage
577
+
578
+ ```tsx
579
+ const { handleOpenChange, isOpen } = useFormDialog({
580
+ onOpenChange: (open) => {
581
+ console.log('Dialog state:', open);
582
+ },
583
+ resetOnClose: false // Keep form data after closing
584
+ });
585
+ ```
586
+
587
+ #### Options
588
+
589
+ - `onOpenChange?: (open: boolean) => void` - Callback invoked when dialog state changes
590
+ - `resetOnClose?: boolean` - Whether to reset form data when dialog closes (default: `true`)
591
+
592
+ #### Return Values
593
+
594
+ - `isOpen: boolean` - Current open state of the dialog
595
+ - `formData: T | null` - Current form data (null when closed or no data provided)
596
+ - `openDialog: (data?: T | null) => void` - Open dialog with optional form data
597
+ - `closeDialog: () => void` - Close dialog
598
+ - `handleOpenChange: (open: boolean) => void` - Handler for controlled dialog components
599
+
489
600
  ## Utility Hooks
490
601
 
491
602
  ### useDebounce
@@ -120,7 +120,7 @@ Documented in [File Reference System Guide](../implementation-guides/file-refere
120
120
  - `data_file_reference_count_get` - Get count of file references for a record
121
121
 
122
122
  **Write Operations (exception to naming standard):**
123
- - `data_file_reference_create` - Create a file reference (uses `data_*` prefix for consistency within file reference system)
123
+ - `data_file_reference_create` - Create a file reference with context-aware permission checks. Requires `p_page_context` parameter to check appropriate page-level permissions (e.g., 'create:page.configuration' or 'update:page.configuration'). Uses `data_*` prefix for consistency within file reference system.
124
124
  - `data_file_reference_delete` - Delete a file reference (uses `data_*` prefix for consistency within file reference system)
125
125
 
126
126
  ### RBAC System
@@ -369,6 +369,190 @@ import { getDateRange } from '@jmruthers/pace-core';
369
369
  const dateRange = getDateRange(startDate, endDate, 'day');
370
370
  ```
371
371
 
372
+ ## Timezone Utilities
373
+
374
+ Comprehensive timezone conversion and formatting utilities using `date-fns-tz` and native `Intl` APIs. All functions handle DST transitions and return safe error values ('Invalid date' strings) rather than throwing exceptions.
375
+
376
+ ### formatInTimeZone
377
+
378
+ Format a date in a specific timezone using date-fns format strings.
379
+
380
+ ```typescript
381
+ function formatInTimeZone(
382
+ date: Date | string | number,
383
+ timeZone: string,
384
+ formatStr: string
385
+ ): string;
386
+ ```
387
+
388
+ #### Usage
389
+
390
+ ```typescript
391
+ import { formatInTimeZone } from '@jmruthers/pace-core';
392
+
393
+ // Format UTC date in specific timezone
394
+ formatInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York', 'MMM dd, yyyy HH:mm');
395
+ // "Jan 15, 2024 05:00"
396
+
397
+ // With ISO string
398
+ formatInTimeZone('2024-01-15T10:00:00Z', 'Europe/London', 'yyyy-MM-dd');
399
+ // "2024-01-15"
400
+ ```
401
+
402
+ ### getTimezoneAbbreviation
403
+
404
+ Get the timezone abbreviation (EST, PST, etc.) for a date in a specific timezone.
405
+
406
+ ```typescript
407
+ function getTimezoneAbbreviation(date: Date, timeZone: string): string;
408
+ ```
409
+
410
+ #### Usage
411
+
412
+ ```typescript
413
+ import { getTimezoneAbbreviation } from '@jmruthers/pace-core';
414
+
415
+ getTimezoneAbbreviation(new Date('2024-01-15T10:00:00Z'), 'America/New_York');
416
+ // "EST" or "EDT" depending on DST
417
+ ```
418
+
419
+ ### formatTimeInTimeZone
420
+
421
+ Format time only in a specific timezone (HH:mm format).
422
+
423
+ ```typescript
424
+ function formatTimeInTimeZone(date: Date | string, timeZone: string): string;
425
+ ```
426
+
427
+ #### Usage
428
+
429
+ ```typescript
430
+ import { formatTimeInTimeZone } from '@jmruthers/pace-core';
431
+
432
+ formatTimeInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York');
433
+ // "05:00"
434
+ ```
435
+
436
+ ### getUserTimeZone
437
+
438
+ Get the user's browser timezone.
439
+
440
+ ```typescript
441
+ function getUserTimeZone(): string;
442
+ ```
443
+
444
+ #### Usage
445
+
446
+ ```typescript
447
+ import { getUserTimeZone } from '@jmruthers/pace-core';
448
+
449
+ const userTz = getUserTimeZone();
450
+ // "America/New_York" (or user's actual timezone)
451
+ ```
452
+
453
+ ### toZonedTime
454
+
455
+ Convert a UTC date to a specific timezone's local time representation.
456
+
457
+ ```typescript
458
+ function toZonedTime(date: Date, timezone: string): Date;
459
+ ```
460
+
461
+ #### Usage
462
+
463
+ ```typescript
464
+ import { toZonedTime } from '@jmruthers/pace-core';
465
+
466
+ const utcDate = new Date('2024-01-15T10:00:00Z');
467
+ const localDate = toZonedTime(utcDate, 'America/New_York');
468
+ // Returns Date object representing Jan 15, 2024 05:00 in local time
469
+ ```
470
+
471
+ ### fromZonedTime
472
+
473
+ Convert a local time in a specific timezone to UTC.
474
+
475
+ ```typescript
476
+ function fromZonedTime(localDate: Date, timezone: string): Date;
477
+ ```
478
+
479
+ #### Usage
480
+
481
+ ```typescript
482
+ import { fromZonedTime } from '@jmruthers/pace-core';
483
+
484
+ const localInput = new Date(2024, 0, 15, 10, 0); // Jan 15, 2024 10:00 AM (local)
485
+ const utcDate = fromZonedTime(localInput, 'America/New_York');
486
+ // Returns Date object representing Jan 15, 2024 15:00 UTC
487
+ ```
488
+
489
+ ### roundToNearestMinutes
490
+
491
+ Round a date to the nearest X minutes.
492
+
493
+ ```typescript
494
+ function roundToNearestMinutes(date: Date, minutesStep?: number): Date;
495
+ ```
496
+
497
+ #### Usage
498
+
499
+ ```typescript
500
+ import { roundToNearestMinutes } from '@jmruthers/pace-core';
501
+
502
+ const date = new Date('2024-01-15T10:23:00Z');
503
+ roundToNearestMinutes(date, 5);
504
+ // Returns Date object for 10:25:00
505
+
506
+ roundToNearestMinutes(date, 15);
507
+ // Returns Date object for 10:30:00
508
+ ```
509
+
510
+ **Note:** `minutesStep` must be one of: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, or 30. Other values will be rounded to the nearest valid step.
511
+
512
+ ### getTimeZoneDifference
513
+
514
+ Calculate the time difference between two timezones in hours.
515
+
516
+ ```typescript
517
+ function getTimeZoneDifference(fromTimeZone: string, toTimeZone: string): number;
518
+ ```
519
+
520
+ #### Usage
521
+
522
+ ```typescript
523
+ import { getTimeZoneDifference } from '@jmruthers/pace-core';
524
+
525
+ getTimeZoneDifference('America/New_York', 'America/Los_Angeles');
526
+ // Returns -3 (EST is 3 hours ahead of PST)
527
+
528
+ getTimeZoneDifference('UTC', 'America/New_York');
529
+ // Returns -5 (or -4 during DST)
530
+ ```
531
+
532
+ #### Complete Example
533
+
534
+ ```typescript
535
+ import {
536
+ toZonedTime,
537
+ fromZonedTime,
538
+ formatInTimeZone,
539
+ getUserTimeZone
540
+ } from '@jmruthers/pace-core';
541
+
542
+ // Convert UTC to local timezone
543
+ const utcDate = new Date('2024-01-15T10:00:00Z');
544
+ const userTz = getUserTimeZone();
545
+ const localDate = toZonedTime(utcDate, userTz);
546
+
547
+ // Format with timezone
548
+ const formatted = formatInTimeZone(utcDate, userTz, 'MMM dd, yyyy HH:mm (zzz)');
549
+ // "Jan 15, 2024 05:00 (EST)"
550
+
551
+ // Convert local time to UTC
552
+ const localInput = new Date(2024, 0, 15, 10, 0);
553
+ const utcResult = fromZonedTime(localInput, userTz);
554
+ ```
555
+
372
556
  ## String Utilities
373
557
 
374
558
  ### truncateText
@@ -56,7 +56,10 @@ npm uninstall tailwindcss@^3.0.0 postcss autoprefixer
56
56
 
57
57
  ### Step 3: Configure Vite
58
58
 
59
- **⚠️ CRITICAL**: Without this configuration, PACE Core components will not be styled.
59
+ **⚠️ CRITICAL**: This configuration is required for:
60
+ 1. **Tailwind CSS scanning** - Ensures PACE Core components are styled
61
+ 2. **React context consistency** - Prevents "useUnifiedAuth must be used within a UnifiedAuthProvider" errors
62
+ 3. **Router context availability** - Prevents "useNavigate() may be used only in the context of a <Router>" errors
60
63
 
61
64
  Create or update `vite.config.ts`:
62
65
 
@@ -64,6 +67,7 @@ Create or update `vite.config.ts`:
64
67
  import { defineConfig } from 'vite'
65
68
  import react from '@vitejs/plugin-react'
66
69
  import tailwindcss from '@tailwindcss/vite'
70
+ import path from 'path'
67
71
 
68
72
  export default defineConfig({
69
73
  plugins: [
@@ -76,6 +80,27 @@ export default defineConfig({
76
80
  ]
77
81
  })
78
82
  ],
83
+ resolve: {
84
+ alias: {
85
+ "@": path.resolve(__dirname, "./src"),
86
+ },
87
+ // CRITICAL: Dedupe React and React Router to ensure single instances
88
+ // This prevents Router context issues when pace-core uses react-router-dom
89
+ dedupe: ['react', 'react-dom', 'react-router-dom']
90
+ },
91
+ // CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
92
+ // Pre-bundling creates separate React instances, causing context errors
93
+ optimizeDeps: {
94
+ include: [
95
+ 'react',
96
+ 'react-dom',
97
+ 'react/jsx-runtime',
98
+ '@radix-ui/react-slot'
99
+ ],
100
+ // CRITICAL: Exclude pace-core and react-router-dom from pre-bundling
101
+ // This ensures pace-core uses the same React instance as your app
102
+ exclude: ['@jmruthers/pace-core', 'react-router-dom']
103
+ },
79
104
  server: {
80
105
  port: 3000,
81
106
  open: true,
@@ -279,27 +304,61 @@ VITE_SUPABASE_URL=https://your-project.supabase.co
279
304
  VITE_SUPABASE_ANON_KEY=your-publishable-key-or-anon-key-here
280
305
  ```
281
306
 
282
- Wrap your app with providers:
307
+ Wrap your app with providers in the correct nesting order:
283
308
 
284
309
  ```tsx
285
- import { UnifiedAuthProvider } from '@jmruthers/pace-core';
310
+ // src/main.tsx - CRITICAL: Correct provider nesting order
311
+ import React from 'react';
312
+ import ReactDOM from 'react-dom/client';
313
+ import { BrowserRouter } from 'react-router-dom';
314
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
315
+ import { UnifiedAuthProvider, OrganisationProvider, setupRBAC } from '@jmruthers/pace-core';
286
316
  import { supabase } from './lib/supabase';
317
+ import App from './App';
287
318
 
288
- function App() {
289
- return (
290
- <UnifiedAuthProvider
291
- supabaseClient={supabase}
292
- appName="my-app"
293
- idleTimeoutMs={30 * 60 * 1000}
294
- warnBeforeMs={5 * 60 * 1000}
295
- onIdleLogout={() => window.location.href = '/login'}
296
- >
297
- <YourApp />
298
- </UnifiedAuthProvider>
299
- );
300
- }
319
+ // Import CSS
320
+ import '@jmruthers/pace-core/src/styles/core.css';
321
+
322
+ // ⚠️ REQUIRED: Setup RBAC before rendering
323
+ setupRBAC(supabase);
324
+
325
+ const queryClient = new QueryClient();
326
+
327
+ ReactDOM.createRoot(document.getElementById('root')!).render(
328
+ <React.StrictMode>
329
+ {/* CRITICAL: Correct nesting order */}
330
+ <QueryClientProvider client={queryClient}>
331
+ <BrowserRouter>
332
+ <UnifiedAuthProvider
333
+ supabaseClient={supabase}
334
+ appName="my-app"
335
+ idleTimeoutMs={30 * 60 * 1000} // 30 minutes
336
+ warnBeforeMs={5 * 60 * 1000} // 5 minutes warning
337
+ onIdleLogout={() => window.location.href = '/login'}
338
+ >
339
+ <OrganisationProvider>
340
+ <App />
341
+ </OrganisationProvider>
342
+ </UnifiedAuthProvider>
343
+ </BrowserRouter>
344
+ </QueryClientProvider>
345
+ </React.StrictMode>
346
+ );
301
347
  ```
302
348
 
349
+ **⚠️ CRITICAL Provider Nesting Order:**
350
+ 1. `QueryClientProvider` (outermost)
351
+ 2. `BrowserRouter`
352
+ 3. `UnifiedAuthProvider`
353
+ 4. `OrganisationProvider`
354
+ 5. `App` (innermost)
355
+
356
+ **Common mistakes to avoid:**
357
+ - ❌ `BrowserRouter` inside `UnifiedAuthProvider` (causes Router context errors)
358
+ - ❌ `UnifiedAuthProvider` wrapping `BrowserRouter` (causes context errors)
359
+ - ❌ Missing `BrowserRouter` (causes `useNavigate` errors)
360
+ - ❌ Missing `QueryClientProvider` (required for React Query features)
361
+
303
362
  ## 🚨 Breaking Changes (v0.5.65+)
304
363
 
305
364
  **CRITICAL**: As of v0.5.65+, inactivity timeouts are **mandatory** in `UnifiedAuthProvider`.
@@ -45,12 +45,15 @@ npm install -D tailwindcss@^4.0.0 @tailwindcss/vite
45
45
 
46
46
  ## Step 3: Configure Vite
47
47
 
48
+ **⚠️ CRITICAL**: This configuration prevents React context and Router context errors.
49
+
48
50
  Create or update `vite.config.ts`:
49
51
 
50
52
  ```typescript
51
53
  import { defineConfig } from 'vite';
52
54
  import react from '@vitejs/plugin-react';
53
55
  import tailwindcss from '@tailwindcss/vite';
56
+ import path from 'path';
54
57
 
55
58
  export default defineConfig({
56
59
  plugins: [
@@ -62,6 +65,23 @@ export default defineConfig({
62
65
  ]
63
66
  })
64
67
  ],
68
+ resolve: {
69
+ alias: {
70
+ "@": path.resolve(__dirname, "./src"),
71
+ },
72
+ // CRITICAL: Dedupe React and React Router to ensure single instances
73
+ dedupe: ['react', 'react-dom', 'react-router-dom']
74
+ },
75
+ // CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
76
+ optimizeDeps: {
77
+ include: [
78
+ 'react',
79
+ 'react-dom',
80
+ 'react/jsx-runtime'
81
+ ],
82
+ // CRITICAL: Exclude pace-core and react-router-dom from pre-bundling
83
+ exclude: ['@jmruthers/pace-core', 'react-router-dom']
84
+ },
65
85
  server: {
66
86
  port: 3000,
67
87
  open: true,
@@ -135,39 +155,66 @@ CREATE POLICY "Users can delete their own tasks" ON tasks
135
155
 
136
156
  ## Step 7: Create Your App
137
157
 
158
+ **⚠️ CRITICAL**: Provider nesting order matters! Follow this exact structure.
159
+
138
160
  Update `src/main.tsx`:
139
161
 
140
162
  ```tsx
141
163
  import React from 'react';
142
164
  import ReactDOM from 'react-dom/client';
143
165
  import { BrowserRouter } from 'react-router-dom';
144
- import { UnifiedAuthProvider } from '@jmruthers/pace-core';
166
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
167
+ import { UnifiedAuthProvider, OrganisationProvider, setupRBAC } from '@jmruthers/pace-core';
145
168
  import { supabase } from './lib/supabase';
146
169
  import App from './App';
147
170
  import './app.css';
148
171
 
172
+ // ⚠️ REQUIRED: Setup RBAC before rendering
173
+ setupRBAC(supabase);
174
+
175
+ const queryClient = new QueryClient();
176
+
149
177
  ReactDOM.createRoot(document.getElementById('root')!).render(
150
178
  <React.StrictMode>
151
- <UnifiedAuthProvider
152
- supabaseClient={supabase}
153
- appName="My Task App"
154
- idleTimeoutMs={30 * 60 * 1000} // 30 minutes
155
- warnBeforeMs={60 * 1000} // 1 minute warning
156
- onIdleLogout={() => window.location.assign('/login')}
157
- >
179
+ {/* CRITICAL: Correct nesting order */}
180
+ <QueryClientProvider client={queryClient}>
158
181
  <BrowserRouter>
159
- <App />
182
+ <UnifiedAuthProvider
183
+ supabaseClient={supabase}
184
+ appName="My Task App"
185
+ idleTimeoutMs={30 * 60 * 1000} // 30 minutes
186
+ warnBeforeMs={60 * 1000} // 1 minute warning
187
+ onIdleLogout={() => window.location.assign('/login')}
188
+ >
189
+ <OrganisationProvider>
190
+ <App />
191
+ </OrganisationProvider>
192
+ </UnifiedAuthProvider>
160
193
  </BrowserRouter>
161
- </UnifiedAuthProvider>
194
+ </QueryClientProvider>
162
195
  </React.StrictMode>
163
196
  );
164
197
  ```
165
198
 
199
+ **Provider Nesting Order (CRITICAL):**
200
+ 1. `React.StrictMode` (outermost)
201
+ 2. `QueryClientProvider`
202
+ 3. `BrowserRouter`
203
+ 4. `UnifiedAuthProvider`
204
+ 5. `OrganisationProvider`
205
+ 6. `App` (innermost)
206
+
207
+ **Why this order matters:**
208
+ - `BrowserRouter` must wrap `UnifiedAuthProvider` to provide Router context
209
+ - `UnifiedAuthProvider` must be inside `BrowserRouter` to use Router hooks
210
+ - `QueryClientProvider` wraps everything for React Query support
211
+ - Wrong nesting causes "useNavigate() may be used only in the context of a <Router>" errors
212
+
166
213
  Create `src/App.tsx`:
167
214
 
168
215
  ```tsx
169
216
  import { Routes, Route, Navigate } from 'react-router-dom';
170
- import { useUnifiedAuth, PaceLoginPage, PaceAppLayout, ProtectedRoute } from '@jmruthers/pace-core';
217
+ import { PaceLoginPage, PaceAppLayout, ProtectedRoute, useUnifiedAuth } from '@jmruthers/pace-core';
171
218
  import { TasksPage } from './pages/TasksPage';
172
219
  import { DashboardPage } from './pages/DashboardPage';
173
220
 
@@ -180,6 +227,7 @@ function App() {
180
227
  path="/login"
181
228
  element={isAuthenticated ? <Navigate to="/" replace /> : <PaceLoginPage appName="My Task App" />}
182
229
  />
230
+ {/* ProtectedRoute from pace-core package - handles auth and event selection */}
183
231
  <Route element={<ProtectedRoute />}>
184
232
  <Route element={<PaceAppLayout appName="My Task App" />}>
185
233
  <Route path="/" element={<DashboardPage />} />
@@ -193,6 +241,8 @@ function App() {
193
241
  export default App;
194
242
  ```
195
243
 
244
+ **Note:** `ProtectedRoute` is imported from `@jmruthers/pace-core` - do not create a local version. The package version handles authentication, session restoration, and optional event selection.
245
+
196
246
  ## Step 8: Create the Dashboard Page
197
247
 
198
248
  Create `src/pages/DashboardPage.tsx`: