@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,478 @@
1
+ /**
2
+ * @file useFormDialog Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__/useFormDialog
5
+ * @since 0.1.0
6
+ *
7
+ * Comprehensive tests for the useFormDialog hook covering all critical functionality.
8
+ */
9
+
10
+ import { renderHook, act } from '@testing-library/react';
11
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
12
+ import { useFormDialog, type UseFormDialogOptions } from '../useFormDialog';
13
+
14
+ interface TestFormData {
15
+ id: string;
16
+ name: string;
17
+ email: string;
18
+ }
19
+
20
+ describe('useFormDialog', () => {
21
+ describe('Initial state', () => {
22
+ it('should initialize with closed state and null formData', () => {
23
+ const { result } = renderHook(() => useFormDialog());
24
+
25
+ expect(result.current.isOpen).toBe(false);
26
+ expect(result.current.formData).toBeNull();
27
+ });
28
+
29
+ it('should initialize with closed state for typed hook', () => {
30
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
31
+
32
+ expect(result.current.isOpen).toBe(false);
33
+ expect(result.current.formData).toBeNull();
34
+ });
35
+ });
36
+
37
+ describe('Opening dialog', () => {
38
+ it('should open dialog without data', () => {
39
+ const { result } = renderHook(() => useFormDialog());
40
+
41
+ act(() => {
42
+ result.current.openDialog();
43
+ });
44
+
45
+ expect(result.current.isOpen).toBe(true);
46
+ expect(result.current.formData).toBeNull();
47
+ });
48
+
49
+ it('should open dialog with data', () => {
50
+ const testData: TestFormData = {
51
+ id: '1',
52
+ name: 'John Doe',
53
+ email: 'john@example.com'
54
+ };
55
+
56
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
57
+
58
+ act(() => {
59
+ result.current.openDialog(testData);
60
+ });
61
+
62
+ expect(result.current.isOpen).toBe(true);
63
+ expect(result.current.formData).toEqual(testData);
64
+ });
65
+
66
+ it('should open dialog with null data explicitly', () => {
67
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
68
+
69
+ act(() => {
70
+ result.current.openDialog(null);
71
+ });
72
+
73
+ expect(result.current.isOpen).toBe(true);
74
+ expect(result.current.formData).toBeNull();
75
+ });
76
+ });
77
+
78
+ describe('Closing dialog', () => {
79
+ it('should close dialog and reset formData by default', () => {
80
+ const testData: TestFormData = {
81
+ id: '1',
82
+ name: 'John Doe',
83
+ email: 'john@example.com'
84
+ };
85
+
86
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
87
+
88
+ act(() => {
89
+ result.current.openDialog(testData);
90
+ });
91
+
92
+ expect(result.current.isOpen).toBe(true);
93
+ expect(result.current.formData).toEqual(testData);
94
+
95
+ act(() => {
96
+ result.current.closeDialog();
97
+ });
98
+
99
+ expect(result.current.isOpen).toBe(false);
100
+ expect(result.current.formData).toBeNull();
101
+ });
102
+
103
+ it('should close dialog without resetting formData when resetOnClose is false', () => {
104
+ const testData: TestFormData = {
105
+ id: '1',
106
+ name: 'John Doe',
107
+ email: 'john@example.com'
108
+ };
109
+
110
+ const { result } = renderHook(() =>
111
+ useFormDialog<TestFormData>({ resetOnClose: false })
112
+ );
113
+
114
+ act(() => {
115
+ result.current.openDialog(testData);
116
+ });
117
+
118
+ act(() => {
119
+ result.current.closeDialog();
120
+ });
121
+
122
+ expect(result.current.isOpen).toBe(false);
123
+ expect(result.current.formData).toEqual(testData);
124
+ });
125
+ });
126
+
127
+ describe('handleOpenChange', () => {
128
+ it('should update state when opening via handleOpenChange', () => {
129
+ const { result } = renderHook(() => useFormDialog());
130
+
131
+ act(() => {
132
+ result.current.handleOpenChange(true);
133
+ });
134
+
135
+ expect(result.current.isOpen).toBe(true);
136
+ });
137
+
138
+ it('should update state when closing via handleOpenChange', () => {
139
+ const { result } = renderHook(() => useFormDialog());
140
+
141
+ act(() => {
142
+ result.current.openDialog();
143
+ });
144
+
145
+ act(() => {
146
+ result.current.handleOpenChange(false);
147
+ });
148
+
149
+ expect(result.current.isOpen).toBe(false);
150
+ });
151
+
152
+ it('should reset formData when closing via handleOpenChange with resetOnClose true', () => {
153
+ const testData: TestFormData = {
154
+ id: '1',
155
+ name: 'John Doe',
156
+ email: 'john@example.com'
157
+ };
158
+
159
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
160
+
161
+ act(() => {
162
+ result.current.openDialog(testData);
163
+ });
164
+
165
+ act(() => {
166
+ result.current.handleOpenChange(false);
167
+ });
168
+
169
+ expect(result.current.isOpen).toBe(false);
170
+ expect(result.current.formData).toBeNull();
171
+ });
172
+
173
+ it('should not reset formData when closing via handleOpenChange with resetOnClose false', () => {
174
+ const testData: TestFormData = {
175
+ id: '1',
176
+ name: 'John Doe',
177
+ email: 'john@example.com'
178
+ };
179
+
180
+ const { result } = renderHook(() =>
181
+ useFormDialog<TestFormData>({ resetOnClose: false })
182
+ );
183
+
184
+ act(() => {
185
+ result.current.openDialog(testData);
186
+ });
187
+
188
+ act(() => {
189
+ result.current.handleOpenChange(false);
190
+ });
191
+
192
+ expect(result.current.isOpen).toBe(false);
193
+ expect(result.current.formData).toEqual(testData);
194
+ });
195
+ });
196
+
197
+ describe('onOpenChange callback', () => {
198
+ it('should call onOpenChange when opening dialog', () => {
199
+ const onOpenChange = vi.fn();
200
+ const { result } = renderHook(() =>
201
+ useFormDialog({ onOpenChange })
202
+ );
203
+
204
+ act(() => {
205
+ result.current.openDialog();
206
+ });
207
+
208
+ expect(onOpenChange).toHaveBeenCalledWith(true);
209
+ expect(onOpenChange).toHaveBeenCalledTimes(1);
210
+ });
211
+
212
+ it('should call onOpenChange when closing dialog', () => {
213
+ const onOpenChange = vi.fn();
214
+ const { result } = renderHook(() =>
215
+ useFormDialog({ onOpenChange })
216
+ );
217
+
218
+ act(() => {
219
+ result.current.openDialog();
220
+ });
221
+
222
+ act(() => {
223
+ result.current.closeDialog();
224
+ });
225
+
226
+ expect(onOpenChange).toHaveBeenCalledWith(false);
227
+ expect(onOpenChange).toHaveBeenCalledTimes(2); // Once for open, once for close
228
+ });
229
+
230
+ it('should call onOpenChange when using handleOpenChange', () => {
231
+ const onOpenChange = vi.fn();
232
+ const { result } = renderHook(() =>
233
+ useFormDialog({ onOpenChange })
234
+ );
235
+
236
+ act(() => {
237
+ result.current.handleOpenChange(true);
238
+ });
239
+
240
+ expect(onOpenChange).toHaveBeenCalledWith(true);
241
+
242
+ act(() => {
243
+ result.current.handleOpenChange(false);
244
+ });
245
+
246
+ expect(onOpenChange).toHaveBeenCalledWith(false);
247
+ expect(onOpenChange).toHaveBeenCalledTimes(2);
248
+ });
249
+
250
+ it('should not call onOpenChange if not provided', () => {
251
+ const { result } = renderHook(() => useFormDialog());
252
+
253
+ act(() => {
254
+ result.current.openDialog();
255
+ });
256
+
257
+ act(() => {
258
+ result.current.closeDialog();
259
+ });
260
+
261
+ // Should not throw
262
+ expect(result.current.isOpen).toBe(false);
263
+ });
264
+ });
265
+
266
+ describe('Controlled vs uncontrolled usage', () => {
267
+ it('should work in uncontrolled mode (using openDialog/closeDialog)', () => {
268
+ const { result } = renderHook(() => useFormDialog());
269
+
270
+ act(() => {
271
+ result.current.openDialog();
272
+ });
273
+ expect(result.current.isOpen).toBe(true);
274
+
275
+ act(() => {
276
+ result.current.closeDialog();
277
+ });
278
+ expect(result.current.isOpen).toBe(false);
279
+ });
280
+
281
+ it('should work in controlled mode (using handleOpenChange)', () => {
282
+ const { result } = renderHook(() => useFormDialog());
283
+
284
+ act(() => {
285
+ result.current.handleOpenChange(true);
286
+ });
287
+ expect(result.current.isOpen).toBe(true);
288
+
289
+ act(() => {
290
+ result.current.handleOpenChange(false);
291
+ });
292
+ expect(result.current.isOpen).toBe(false);
293
+ });
294
+
295
+ it('should work with mixed usage', () => {
296
+ const { result } = renderHook(() => useFormDialog());
297
+
298
+ act(() => {
299
+ result.current.openDialog();
300
+ });
301
+ expect(result.current.isOpen).toBe(true);
302
+
303
+ act(() => {
304
+ result.current.handleOpenChange(false);
305
+ });
306
+ expect(result.current.isOpen).toBe(false);
307
+
308
+ act(() => {
309
+ result.current.handleOpenChange(true);
310
+ });
311
+ expect(result.current.isOpen).toBe(true);
312
+
313
+ act(() => {
314
+ result.current.closeDialog();
315
+ });
316
+ expect(result.current.isOpen).toBe(false);
317
+ });
318
+ });
319
+
320
+ describe('Edge cases', () => {
321
+ it('should handle rapid open/close operations', () => {
322
+ const { result } = renderHook(() => useFormDialog());
323
+
324
+ act(() => {
325
+ result.current.openDialog();
326
+ result.current.closeDialog();
327
+ result.current.openDialog();
328
+ result.current.closeDialog();
329
+ });
330
+
331
+ expect(result.current.isOpen).toBe(false);
332
+ expect(result.current.formData).toBeNull();
333
+ });
334
+
335
+ it('should handle opening with undefined data', () => {
336
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
337
+
338
+ act(() => {
339
+ result.current.openDialog(undefined as unknown as TestFormData);
340
+ });
341
+
342
+ expect(result.current.isOpen).toBe(true);
343
+ // When undefined is passed, it becomes null in the state
344
+ expect(result.current.formData).toBeNull();
345
+ });
346
+
347
+ it('should handle replacing formData when dialog is already open', () => {
348
+ const initialData: TestFormData = {
349
+ id: '1',
350
+ name: 'John Doe',
351
+ email: 'john@example.com'
352
+ };
353
+
354
+ const newData: TestFormData = {
355
+ id: '2',
356
+ name: 'Jane Doe',
357
+ email: 'jane@example.com'
358
+ };
359
+
360
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
361
+
362
+ act(() => {
363
+ result.current.openDialog(initialData);
364
+ });
365
+
366
+ expect(result.current.formData).toEqual(initialData);
367
+
368
+ act(() => {
369
+ result.current.openDialog(newData);
370
+ });
371
+
372
+ expect(result.current.isOpen).toBe(true);
373
+ expect(result.current.formData).toEqual(newData);
374
+ });
375
+
376
+ it('should handle closing when already closed', () => {
377
+ const { result } = renderHook(() => useFormDialog());
378
+
379
+ expect(result.current.isOpen).toBe(false);
380
+
381
+ act(() => {
382
+ result.current.closeDialog();
383
+ });
384
+
385
+ expect(result.current.isOpen).toBe(false);
386
+ expect(result.current.formData).toBeNull();
387
+ });
388
+ });
389
+
390
+ describe('Type safety', () => {
391
+ it('should maintain type safety with generic type parameter', () => {
392
+ const testData: TestFormData = {
393
+ id: '1',
394
+ name: 'John Doe',
395
+ email: 'john@example.com'
396
+ };
397
+
398
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
399
+
400
+ act(() => {
401
+ result.current.openDialog(testData);
402
+ });
403
+
404
+ // TypeScript should infer the correct type
405
+ if (result.current.formData) {
406
+ expect(result.current.formData.id).toBe('1');
407
+ expect(result.current.formData.name).toBe('John Doe');
408
+ expect(result.current.formData.email).toBe('john@example.com');
409
+ }
410
+ });
411
+
412
+ it('should work with primitive types', () => {
413
+ const { result } = renderHook(() => useFormDialog<string>());
414
+
415
+ act(() => {
416
+ result.current.openDialog('test string');
417
+ });
418
+
419
+ expect(result.current.formData).toBe('test string');
420
+ });
421
+
422
+ it('should work with number types', () => {
423
+ const { result } = renderHook(() => useFormDialog<number>());
424
+
425
+ act(() => {
426
+ result.current.openDialog(42);
427
+ });
428
+
429
+ expect(result.current.formData).toBe(42);
430
+ });
431
+ });
432
+
433
+ describe('Options handling', () => {
434
+ it('should use default resetOnClose value of true', () => {
435
+ const testData: TestFormData = {
436
+ id: '1',
437
+ name: 'John Doe',
438
+ email: 'john@example.com'
439
+ };
440
+
441
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
442
+
443
+ act(() => {
444
+ result.current.openDialog(testData);
445
+ });
446
+
447
+ act(() => {
448
+ result.current.closeDialog();
449
+ });
450
+
451
+ // Should reset by default
452
+ expect(result.current.formData).toBeNull();
453
+ });
454
+
455
+ it('should respect resetOnClose option', () => {
456
+ const testData: TestFormData = {
457
+ id: '1',
458
+ name: 'John Doe',
459
+ email: 'john@example.com'
460
+ };
461
+
462
+ const { result } = renderHook(() =>
463
+ useFormDialog<TestFormData>({ resetOnClose: false })
464
+ );
465
+
466
+ act(() => {
467
+ result.current.openDialog(testData);
468
+ });
469
+
470
+ act(() => {
471
+ result.current.closeDialog();
472
+ });
473
+
474
+ // Should not reset when resetOnClose is false
475
+ expect(result.current.formData).toEqual(testData);
476
+ });
477
+ });
478
+ });
@@ -39,6 +39,8 @@ export type { OrganisationSecurityHook } from './useOrganisationSecurity';
39
39
 
40
40
  // === FORM HOOKS ===
41
41
  export { useZodForm } from './useZodForm';
42
+ export { useFormDialog } from './useFormDialog';
43
+ export type { UseFormDialogOptions, UseFormDialogReturn } from './useFormDialog';
42
44
 
43
45
 
44
46
  // === PERFORMANCE HOOKS ===
@@ -67,6 +67,7 @@ const mockFileUploadOptions = {
67
67
  organisation_id: 'test-org-123',
68
68
  app_id: 'test-app-123',
69
69
  category: FileCategory.GENERAL_DOCUMENTS,
70
+ pageContext: 'configuration',
70
71
  is_public: false
71
72
  };
72
73
 
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @file useFormDialog Hook
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks
5
+ * @since 0.1.0
6
+ *
7
+ * Generic React hook for managing form dialog state (open/close, form data, reset behavior).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * interface ContactFormData {
12
+ * name: string;
13
+ * email: string;
14
+ * }
15
+ *
16
+ * function ContactDialog() {
17
+ * const { isOpen, formData, openDialog, closeDialog, handleOpenChange } =
18
+ * useFormDialog<ContactFormData>();
19
+ *
20
+ * // Use in Dialog component
21
+ * // <Dialog open={isOpen} onOpenChange={handleOpenChange}>...</Dialog>
22
+ * }
23
+ * ```
24
+ */
25
+
26
+ import { useState, useCallback } from 'react';
27
+
28
+ /**
29
+ * Options for configuring the useFormDialog hook behavior
30
+ */
31
+ export interface UseFormDialogOptions<T = unknown> {
32
+ /**
33
+ * Callback invoked when the dialog open state changes
34
+ * @param open - The new open state
35
+ */
36
+ onOpenChange?: (open: boolean) => void;
37
+
38
+ /**
39
+ * Whether to reset form data when the dialog closes
40
+ * @default true
41
+ */
42
+ resetOnClose?: boolean;
43
+ }
44
+
45
+ /**
46
+ * Return type for the useFormDialog hook
47
+ */
48
+ export interface UseFormDialogReturn<T = unknown> {
49
+ /**
50
+ * Current open state of the dialog
51
+ */
52
+ isOpen: boolean;
53
+
54
+ /**
55
+ * Current form data (null when dialog is closed or no data provided)
56
+ */
57
+ formData: T | null;
58
+
59
+ /**
60
+ * Open the dialog with optional form data
61
+ * @param data - Optional form data to populate the dialog
62
+ */
63
+ openDialog: (data?: T | null) => void;
64
+
65
+ /**
66
+ * Close the dialog
67
+ */
68
+ closeDialog: () => void;
69
+
70
+ /**
71
+ * Handler for controlled dialog components (e.g., Radix UI Dialog)
72
+ * @param open - The new open state
73
+ */
74
+ handleOpenChange: (open: boolean) => void;
75
+ }
76
+
77
+ /**
78
+ * Generic React hook for managing form dialog state.
79
+ *
80
+ * Provides state management for form dialogs including:
81
+ * - Open/close state
82
+ * - Form data management
83
+ * - Automatic reset on close (configurable)
84
+ * - Controlled and uncontrolled usage patterns
85
+ *
86
+ * @param options - Configuration options for the hook
87
+ * @returns Object containing dialog state and control functions
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * // Basic usage
92
+ * const { isOpen, openDialog, closeDialog } = useFormDialog();
93
+ *
94
+ * // With form data
95
+ * interface UserData {
96
+ * id: string;
97
+ * name: string;
98
+ * }
99
+ * const { isOpen, formData, openDialog } = useFormDialog<UserData>();
100
+ *
101
+ * // Open with data
102
+ * openDialog({ id: '1', name: 'John' });
103
+ *
104
+ * // Controlled usage with callback
105
+ * const { handleOpenChange, isOpen } = useFormDialog({
106
+ * onOpenChange: (open) => console.log('Dialog state:', open),
107
+ * resetOnClose: false
108
+ * });
109
+ * ```
110
+ */
111
+ export function useFormDialog<T = unknown>({
112
+ onOpenChange,
113
+ resetOnClose = true,
114
+ }: UseFormDialogOptions<T> = {}): UseFormDialogReturn<T> {
115
+ const [isOpen, setIsOpen] = useState(false);
116
+ const [formData, setFormData] = useState<T | null>(null);
117
+
118
+ const openDialog = useCallback((data: T | null = null) => {
119
+ setFormData(data);
120
+ setIsOpen(true);
121
+ onOpenChange?.(true);
122
+ }, [onOpenChange]);
123
+
124
+ const closeDialog = useCallback(() => {
125
+ setIsOpen(false);
126
+ if (resetOnClose) {
127
+ setFormData(null);
128
+ }
129
+ onOpenChange?.(false);
130
+ }, [resetOnClose, onOpenChange]);
131
+
132
+ const handleOpenChange = useCallback((open: boolean) => {
133
+ setIsOpen(open);
134
+ if (!open && resetOnClose) {
135
+ setFormData(null);
136
+ }
137
+ onOpenChange?.(open);
138
+ }, [resetOnClose, onOpenChange]);
139
+
140
+ return {
141
+ isOpen,
142
+ formData,
143
+ openDialog,
144
+ closeDialog,
145
+ handleOpenChange,
146
+ };
147
+ }
package/src/index.ts CHANGED
@@ -189,6 +189,17 @@ export type { FormProps, FormFieldProps } from './components/Form/Form';
189
189
  export { LoginForm } from './components/LoginForm';
190
190
  export type { LoginFormProps } from './components/LoginForm';
191
191
 
192
+ // FORM HOOKS
193
+ export { useZodForm } from './hooks/useZodForm';
194
+ export { useFormDialog } from './hooks/useFormDialog';
195
+ export type { UseFormDialogOptions, UseFormDialogReturn } from './hooks/useFormDialog';
196
+
197
+ // VALIDATION - Re-export zod for schema creation
198
+ export { z } from 'zod';
199
+ // Common validation schemas
200
+ export { emailSchema, nameSchema, phoneSchema, urlSchema } from './utils/validation/common';
201
+ export { passwordSchema } from './utils/validation/passwordSchema';
202
+
192
203
  // LAYOUT COMPONENTS
193
204
  export { Header } from './components/Header/Header';
194
205
  export { Footer } from './components/Footer/Footer';
@@ -236,6 +247,10 @@ export { useEventTheme } from './hooks/useEventTheme';
236
247
  export { cn } from './utils/core/cn';
237
248
  export { setAppConfig, getAppConfig, getCurrentAppName, getCurrentAppId } from './utils/app/appConfig';
238
249
 
250
+ // LOGGING UTILITIES
251
+ export { Logger, logger, createLogger, LogLevel } from './utils/core/logger';
252
+ export type { LoggerConfig } from './utils/core/logger';
253
+
239
254
  // FORMATTING UTILITIES
240
255
  export {
241
256
  formatDate,
@@ -248,6 +263,18 @@ export {
248
263
  formatFileSize
249
264
  } from './utils/formatting/formatting';
250
265
 
266
+ // TIMEZONE UTILITIES
267
+ export {
268
+ formatInTimeZone,
269
+ getTimezoneAbbreviation,
270
+ formatTimeInTimeZone,
271
+ getUserTimeZone,
272
+ toZonedTime,
273
+ fromZonedTime,
274
+ roundToNearestMinutes,
275
+ getTimeZoneDifference
276
+ } from './utils/timezone';
277
+
251
278
  // STORAGE UTILITIES
252
279
  export { FileUpload } from './components/FileUpload';
253
280
  export type { FileUploadProps } from './components/FileUpload';
@@ -49,11 +49,12 @@ export function OrganisationServiceProvider({
49
49
  // Re-initialize service when user/session changes
50
50
  let isMounted = true;
51
51
 
52
- organisationService.initialize().catch(error => {
53
- if (isMounted) {
54
- logger.error('OrganisationServiceProvider', 'Failed to initialize organisation service:', error);
55
- }
56
- });
52
+ organisationService.initialize()
53
+ .catch(error => {
54
+ if (isMounted) {
55
+ logger.error('OrganisationServiceProvider', 'Failed to initialize organisation service:', error);
56
+ }
57
+ });
57
58
 
58
59
  return () => {
59
60
  isMounted = false;