@btst/stack 1.5.2 → 1.6.0

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 (201) hide show
  1. package/dist/node_modules/.pnpm/@dnd-kit_accessibility@3.1.1_react@19.2.0/node_modules/@dnd-kit/accessibility/dist/accessibility.esm.cjs +68 -0
  2. package/dist/node_modules/.pnpm/@dnd-kit_accessibility@3.1.1_react@19.2.0/node_modules/@dnd-kit/accessibility/dist/accessibility.esm.mjs +60 -0
  3. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +3937 -0
  4. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +3907 -0
  5. package/dist/node_modules/.pnpm/@dnd-kit_modifiers@9.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/modifiers/dist/modifiers.esm.cjs +30 -0
  6. package/dist/node_modules/.pnpm/@dnd-kit_modifiers@9.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/modifiers/dist/modifiers.esm.mjs +28 -0
  7. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.cjs +675 -0
  8. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +661 -0
  9. package/dist/node_modules/.pnpm/@dnd-kit_utilities@3.2.2_react@19.2.0/node_modules/@dnd-kit/utilities/dist/utilities.esm.cjs +358 -0
  10. package/dist/node_modules/.pnpm/@dnd-kit_utilities@3.2.2_react@19.2.0/node_modules/@dnd-kit/utilities/dist/utilities.esm.mjs +332 -0
  11. package/dist/node_modules/.pnpm/@radix-ui_react-tabs@1.1.13_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_865f042350eb43f3338b0fffb33f6246/node_modules/@radix-ui/react-tabs/dist/index.cjs +211 -0
  12. package/dist/node_modules/.pnpm/@radix-ui_react-tabs@1.1.13_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react@1_865f042350eb43f3338b0fffb33f6246/node_modules/@radix-ui/react-tabs/dist/index.mjs +188 -0
  13. package/dist/packages/better-stack/src/plugins/cms/api/plugin.cjs +3 -2
  14. package/dist/packages/better-stack/src/plugins/cms/api/plugin.mjs +3 -2
  15. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.cjs +15 -15
  16. package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.mjs +16 -16
  17. package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.cjs +588 -0
  18. package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.mjs +586 -0
  19. package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +131 -0
  20. package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +129 -0
  21. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.cjs +32 -0
  22. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.mjs +30 -0
  23. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.cjs +21 -0
  24. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.mjs +19 -0
  25. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.cjs +34 -0
  26. package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.mjs +32 -0
  27. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.cjs +20 -0
  28. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.mjs +18 -0
  29. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.cjs +19 -0
  30. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.cjs +186 -0
  31. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.mjs +184 -0
  32. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.mjs +17 -0
  33. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.cjs +19 -0
  34. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +165 -0
  35. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +163 -0
  36. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.mjs +17 -0
  37. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.cjs +19 -0
  38. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +177 -0
  39. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +175 -0
  40. package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.mjs +17 -0
  41. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.cjs +17 -0
  42. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.mjs +15 -0
  43. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.cjs +16 -0
  44. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.mjs +14 -0
  45. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +27 -0
  46. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +25 -0
  47. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.cjs +39 -0
  48. package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.mjs +37 -0
  49. package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.cjs +551 -0
  50. package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.mjs +537 -0
  51. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.cjs +36 -0
  52. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.mjs +34 -0
  53. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.cjs +19 -0
  54. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.mjs +17 -0
  55. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.cjs +21 -0
  56. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.mjs +19 -0
  57. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.cjs +19 -0
  58. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.mjs +17 -0
  59. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.cjs +14 -0
  60. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.mjs +12 -0
  61. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.cjs +17 -0
  62. package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.mjs +15 -0
  63. package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.cjs +278 -0
  64. package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.mjs +276 -0
  65. package/dist/packages/better-stack/src/plugins/form-builder/db.cjs +99 -0
  66. package/dist/packages/better-stack/src/plugins/form-builder/db.mjs +97 -0
  67. package/dist/packages/better-stack/src/plugins/form-builder/schemas.cjs +82 -0
  68. package/dist/packages/better-stack/src/plugins/form-builder/schemas.mjs +74 -0
  69. package/dist/packages/better-stack/src/plugins/form-builder/utils.cjs +37 -0
  70. package/dist/packages/better-stack/src/plugins/form-builder/utils.mjs +29 -0
  71. package/dist/packages/ui/src/components/auto-form/index.cjs +2 -12
  72. package/dist/packages/ui/src/components/auto-form/index.mjs +2 -9
  73. package/dist/packages/ui/src/components/auto-form/stepped-auto-form.cjs +377 -0
  74. package/dist/packages/ui/src/components/auto-form/stepped-auto-form.mjs +368 -0
  75. package/dist/packages/ui/src/components/auto-form/utils.cjs +1 -56
  76. package/dist/packages/ui/src/components/auto-form/utils.mjs +2 -56
  77. package/dist/packages/ui/src/components/form-builder/canvas.cjs +111 -0
  78. package/dist/packages/ui/src/components/form-builder/canvas.mjs +109 -0
  79. package/dist/packages/ui/src/components/form-builder/components/index.cjs +570 -0
  80. package/dist/packages/ui/src/components/form-builder/components/index.mjs +553 -0
  81. package/dist/packages/ui/src/components/form-builder/edit-field-dialog.cjs +131 -0
  82. package/dist/packages/ui/src/components/form-builder/edit-field-dialog.mjs +129 -0
  83. package/dist/packages/ui/src/components/form-builder/form-preview.cjs +73 -0
  84. package/dist/packages/ui/src/components/form-builder/form-preview.mjs +71 -0
  85. package/dist/packages/ui/src/components/form-builder/index.cjs +353 -0
  86. package/dist/packages/ui/src/components/form-builder/index.mjs +344 -0
  87. package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.cjs +263 -0
  88. package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.mjs +261 -0
  89. package/dist/packages/ui/src/components/form-builder/palette.cjs +52 -0
  90. package/dist/packages/ui/src/components/form-builder/palette.mjs +49 -0
  91. package/dist/packages/ui/src/components/form-builder/schema-utils.cjs +120 -0
  92. package/dist/packages/ui/src/components/form-builder/schema-utils.mjs +114 -0
  93. package/dist/packages/ui/src/components/form-builder/sortable-field.cjs +151 -0
  94. package/dist/packages/ui/src/components/form-builder/sortable-field.mjs +148 -0
  95. package/dist/packages/ui/src/components/form-builder/step-tabs.cjs +180 -0
  96. package/dist/packages/ui/src/components/form-builder/step-tabs.mjs +178 -0
  97. package/dist/packages/ui/src/components/form-builder/types.cjs +7 -0
  98. package/dist/packages/ui/src/components/form-builder/types.mjs +5 -0
  99. package/dist/packages/ui/src/components/form-builder/validation-schemas.cjs +67 -0
  100. package/dist/packages/ui/src/components/form-builder/validation-schemas.mjs +56 -0
  101. package/dist/packages/ui/src/components/tabs.cjs +70 -0
  102. package/dist/packages/ui/src/components/tabs.mjs +65 -0
  103. package/dist/packages/ui/src/lib/schema-converter.cjs +130 -0
  104. package/dist/packages/ui/src/lib/schema-converter.mjs +124 -0
  105. package/dist/plugins/blog/api/index.d.cts +1 -1
  106. package/dist/plugins/blog/api/index.d.mts +1 -1
  107. package/dist/plugins/blog/api/index.d.ts +1 -1
  108. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  109. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  110. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  111. package/dist/plugins/blog/client/index.d.cts +1 -1
  112. package/dist/plugins/blog/client/index.d.mts +1 -1
  113. package/dist/plugins/blog/client/index.d.ts +1 -1
  114. package/dist/plugins/blog/query-keys.d.cts +2 -2
  115. package/dist/plugins/blog/query-keys.d.mts +2 -2
  116. package/dist/plugins/blog/query-keys.d.ts +2 -2
  117. package/dist/plugins/cms/client/index.cjs +6 -0
  118. package/dist/plugins/cms/client/index.d.cts +6 -113
  119. package/dist/plugins/cms/client/index.d.mts +6 -113
  120. package/dist/plugins/cms/client/index.d.ts +6 -113
  121. package/dist/plugins/cms/client/index.mjs +1 -0
  122. package/dist/plugins/form-builder/api/index.cjs +7 -0
  123. package/dist/plugins/form-builder/api/index.d.cts +141 -0
  124. package/dist/plugins/form-builder/api/index.d.mts +141 -0
  125. package/dist/plugins/form-builder/api/index.d.ts +141 -0
  126. package/dist/plugins/form-builder/api/index.mjs +1 -0
  127. package/dist/plugins/form-builder/client/components/index.cjs +29 -0
  128. package/dist/plugins/form-builder/client/components/index.d.cts +93 -0
  129. package/dist/plugins/form-builder/client/components/index.d.mts +93 -0
  130. package/dist/plugins/form-builder/client/components/index.d.ts +93 -0
  131. package/dist/plugins/form-builder/client/components/index.mjs +18 -0
  132. package/dist/plugins/form-builder/client/hooks/index.cjs +19 -0
  133. package/dist/plugins/form-builder/client/hooks/index.d.cts +154 -0
  134. package/dist/plugins/form-builder/client/hooks/index.d.mts +154 -0
  135. package/dist/plugins/form-builder/client/hooks/index.d.ts +154 -0
  136. package/dist/plugins/form-builder/client/hooks/index.mjs +1 -0
  137. package/dist/plugins/form-builder/client/index.cjs +13 -0
  138. package/dist/plugins/form-builder/client/index.d.cts +381 -0
  139. package/dist/plugins/form-builder/client/index.d.mts +381 -0
  140. package/dist/plugins/form-builder/client/index.d.ts +381 -0
  141. package/dist/plugins/form-builder/client/index.mjs +2 -0
  142. package/dist/plugins/form-builder/client.css +3 -0
  143. package/dist/plugins/form-builder/query-keys.cjs +143 -0
  144. package/dist/plugins/form-builder/query-keys.d.cts +74 -0
  145. package/dist/plugins/form-builder/query-keys.d.mts +74 -0
  146. package/dist/plugins/form-builder/query-keys.d.ts +74 -0
  147. package/dist/plugins/form-builder/query-keys.mjs +141 -0
  148. package/dist/plugins/form-builder/style.css +19 -0
  149. package/dist/shared/stack.AX5nZ6A3.d.cts +86 -0
  150. package/dist/shared/stack.AX5nZ6A3.d.mts +86 -0
  151. package/dist/shared/stack.AX5nZ6A3.d.ts +86 -0
  152. package/dist/shared/stack.BIh2AXaW.d.cts +123 -0
  153. package/dist/shared/stack.BIh2AXaW.d.mts +123 -0
  154. package/dist/shared/stack.BIh2AXaW.d.ts +123 -0
  155. package/dist/shared/stack.DzH_wcvr.d.cts +195 -0
  156. package/dist/shared/stack.DzH_wcvr.d.mts +195 -0
  157. package/dist/shared/stack.DzH_wcvr.d.ts +195 -0
  158. package/package.json +54 -1
  159. package/src/plugins/cms/api/plugin.ts +9 -4
  160. package/src/plugins/cms/client/components/forms/content-form.tsx +23 -25
  161. package/src/plugins/cms/client/index.ts +11 -0
  162. package/src/plugins/form-builder/api/index.ts +1 -0
  163. package/src/plugins/form-builder/api/plugin.ts +776 -0
  164. package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +253 -0
  165. package/src/plugins/form-builder/client/components/index.tsx +24 -0
  166. package/src/plugins/form-builder/client/components/loading/form-builder-skeleton.tsx +42 -0
  167. package/src/plugins/form-builder/client/components/loading/form-list-skeleton.tsx +25 -0
  168. package/src/plugins/form-builder/client/components/loading/index.tsx +3 -0
  169. package/src/plugins/form-builder/client/components/loading/submissions-skeleton.tsx +40 -0
  170. package/src/plugins/form-builder/client/components/pages/404-page.tsx +28 -0
  171. package/src/plugins/form-builder/client/components/pages/form-builder-page.internal.tsx +253 -0
  172. package/src/plugins/form-builder/client/components/pages/form-builder-page.tsx +26 -0
  173. package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +231 -0
  174. package/src/plugins/form-builder/client/components/pages/form-list-page.tsx +22 -0
  175. package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +268 -0
  176. package/src/plugins/form-builder/client/components/pages/submissions-page.tsx +26 -0
  177. package/src/plugins/form-builder/client/components/shared/default-error.tsx +30 -0
  178. package/src/plugins/form-builder/client/components/shared/empty-state.tsx +26 -0
  179. package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +32 -0
  180. package/src/plugins/form-builder/client/components/shared/pagination.tsx +52 -0
  181. package/src/plugins/form-builder/client/hooks/form-builder-hooks.tsx +799 -0
  182. package/src/plugins/form-builder/client/hooks/index.tsx +1 -0
  183. package/src/plugins/form-builder/client/index.ts +22 -0
  184. package/src/plugins/form-builder/client/localization/form-builder-common.ts +36 -0
  185. package/src/plugins/form-builder/client/localization/form-builder-editor.ts +18 -0
  186. package/src/plugins/form-builder/client/localization/form-builder-list.ts +17 -0
  187. package/src/plugins/form-builder/client/localization/form-builder-submissions.ts +17 -0
  188. package/src/plugins/form-builder/client/localization/form-builder-toasts.ts +10 -0
  189. package/src/plugins/form-builder/client/localization/index.ts +15 -0
  190. package/src/plugins/form-builder/client/overrides.ts +146 -0
  191. package/src/plugins/form-builder/client/plugin.tsx +488 -0
  192. package/src/plugins/form-builder/client.css +3 -0
  193. package/src/plugins/form-builder/db.ts +99 -0
  194. package/src/plugins/form-builder/query-keys.ts +198 -0
  195. package/src/plugins/form-builder/schemas.ts +122 -0
  196. package/src/plugins/form-builder/style.css +19 -0
  197. package/src/plugins/form-builder/types.ts +317 -0
  198. package/src/plugins/form-builder/utils.ts +63 -0
  199. package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.cts} +1 -1
  200. package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.mts} +1 -1
  201. package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.ts} +1 -1
@@ -0,0 +1,799 @@
1
+ "use client";
2
+
3
+ import {
4
+ useQuery,
5
+ useMutation,
6
+ useQueryClient,
7
+ useSuspenseQuery,
8
+ useInfiniteQuery,
9
+ useSuspenseInfiniteQuery,
10
+ type InfiniteData,
11
+ } from "@tanstack/react-query";
12
+ import { createApiClient } from "@btst/stack/plugins/client";
13
+ import { usePluginOverrides } from "@btst/stack/context";
14
+ import type { FormBuilderApiRouter } from "../../api";
15
+ import type {
16
+ SerializedForm,
17
+ PaginatedForms,
18
+ SerializedFormSubmission,
19
+ SerializedFormSubmissionWithData,
20
+ PaginatedFormSubmissions,
21
+ } from "../../types";
22
+ import type { FormBuilderPluginOverrides } from "../overrides";
23
+ import { createFormBuilderQueryKeys } from "../../query-keys";
24
+
25
+ // Type guard for better-call error responses
26
+ function isErrorResponse(
27
+ response: unknown,
28
+ ): response is { error: unknown; data?: never } {
29
+ if (typeof response !== "object" || response === null) {
30
+ return false;
31
+ }
32
+ const obj = response as Record<string, unknown>;
33
+ return "error" in obj && obj.error !== null && obj.error !== undefined;
34
+ }
35
+
36
+ // Helper to convert error to a proper Error object with meaningful message
37
+ function toError(error: unknown): Error {
38
+ if (error instanceof Error) {
39
+ return error;
40
+ }
41
+
42
+ if (typeof error === "object" && error !== null) {
43
+ const errorObj = error as Record<string, unknown>;
44
+ const message =
45
+ (typeof errorObj.message === "string" ? errorObj.message : null) ||
46
+ (typeof errorObj.error === "string" ? errorObj.error : null) ||
47
+ JSON.stringify(error);
48
+
49
+ const err = new Error(message);
50
+ Object.assign(err, error);
51
+ return err;
52
+ }
53
+
54
+ return new Error(String(error));
55
+ }
56
+
57
+ /**
58
+ * Shared React Query configuration for all Form Builder queries
59
+ * Prevents automatic refetching to avoid hydration mismatches in SSR
60
+ */
61
+ const SHARED_QUERY_CONFIG = {
62
+ retry: false,
63
+ refetchOnWindowFocus: false,
64
+ refetchOnMount: false,
65
+ refetchOnReconnect: false,
66
+ staleTime: 1000 * 60 * 5, // 5 minutes
67
+ gcTime: 1000 * 60 * 10, // 10 minutes
68
+ } as const;
69
+
70
+ // ========== Forms Hooks (Admin) ==========
71
+
72
+ export interface UseFormsOptions {
73
+ /** Filter by status */
74
+ status?: "active" | "inactive" | "archived";
75
+ /** Number of items per page (default: 20) */
76
+ limit?: number;
77
+ /** Whether to enable the query (default: true) */
78
+ enabled?: boolean;
79
+ }
80
+
81
+ export interface UseFormsResult {
82
+ forms: SerializedForm[];
83
+ total: number;
84
+ isLoading: boolean;
85
+ error: Error | null;
86
+ loadMore: () => void;
87
+ hasMore: boolean;
88
+ isLoadingMore: boolean;
89
+ refetch: () => void;
90
+ }
91
+
92
+ /**
93
+ * Hook for fetching paginated forms (admin)
94
+ */
95
+ export function useForms(options: UseFormsOptions = {}): UseFormsResult {
96
+ const { apiBaseURL, apiBasePath, headers } =
97
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
98
+ const client = createApiClient<FormBuilderApiRouter>({
99
+ baseURL: apiBaseURL,
100
+ basePath: apiBasePath,
101
+ });
102
+ const queries = createFormBuilderQueryKeys(client, headers);
103
+ const { status, limit = 20, enabled = true } = options;
104
+
105
+ const baseQuery = queries.forms.list({ status, limit, offset: 0 });
106
+
107
+ const {
108
+ data,
109
+ isLoading,
110
+ error,
111
+ fetchNextPage,
112
+ hasNextPage,
113
+ isFetchingNextPage,
114
+ refetch,
115
+ } = useInfiniteQuery({
116
+ queryKey: baseQuery.queryKey,
117
+ queryFn: async ({ pageParam = 0 }) => {
118
+ const response: unknown = await client("/forms", {
119
+ method: "GET",
120
+ query: { status, limit, offset: pageParam },
121
+ headers,
122
+ });
123
+ if (isErrorResponse(response)) {
124
+ throw toError(response.error);
125
+ }
126
+ return (response as { data?: unknown }).data as PaginatedForms;
127
+ },
128
+ ...SHARED_QUERY_CONFIG,
129
+ initialPageParam: 0,
130
+ getNextPageParam: (lastPage, allPages) => {
131
+ if (!lastPage || typeof lastPage !== "object") return undefined;
132
+ const items = (lastPage as PaginatedForms)?.items;
133
+ if (!Array.isArray(items) || items.length < limit) return undefined;
134
+ const loadedCount = (allPages || []).reduce(
135
+ (sum, page) =>
136
+ sum +
137
+ (Array.isArray((page as PaginatedForms)?.items)
138
+ ? (page as PaginatedForms).items.length
139
+ : 0),
140
+ 0,
141
+ );
142
+ const total = (lastPage as PaginatedForms)?.total ?? 0;
143
+ if (loadedCount >= total) return undefined;
144
+ return loadedCount;
145
+ },
146
+ enabled,
147
+ });
148
+
149
+ const pages = (data as InfiniteData<PaginatedForms, number> | undefined)
150
+ ?.pages;
151
+ const forms = (pages?.flatMap((page) =>
152
+ Array.isArray(page?.items) ? page.items : [],
153
+ ) ?? []) as SerializedForm[];
154
+ const total = pages?.[0]?.total ?? 0;
155
+
156
+ return {
157
+ forms,
158
+ total,
159
+ isLoading,
160
+ error,
161
+ loadMore: fetchNextPage,
162
+ hasMore: !!hasNextPage,
163
+ isLoadingMore: isFetchingNextPage,
164
+ refetch,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Suspense variant of useForms
170
+ */
171
+ export function useSuspenseForms(options: UseFormsOptions = {}): {
172
+ forms: SerializedForm[];
173
+ total: number;
174
+ loadMore: () => Promise<unknown>;
175
+ hasMore: boolean;
176
+ isLoadingMore: boolean;
177
+ refetch: () => Promise<unknown>;
178
+ } {
179
+ const { apiBaseURL, apiBasePath, headers } =
180
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
181
+ const client = createApiClient<FormBuilderApiRouter>({
182
+ baseURL: apiBaseURL,
183
+ basePath: apiBasePath,
184
+ });
185
+ const queries = createFormBuilderQueryKeys(client, headers);
186
+ const { status, limit = 20 } = options;
187
+
188
+ const baseQuery = queries.forms.list({ status, limit, offset: 0 });
189
+
190
+ const {
191
+ data,
192
+ fetchNextPage,
193
+ hasNextPage,
194
+ isFetchingNextPage,
195
+ refetch,
196
+ error,
197
+ isFetching,
198
+ } = useSuspenseInfiniteQuery({
199
+ queryKey: baseQuery.queryKey,
200
+ queryFn: async ({ pageParam = 0 }) => {
201
+ const response: unknown = await client("/forms", {
202
+ method: "GET",
203
+ query: { status, limit, offset: pageParam },
204
+ headers,
205
+ });
206
+ if (isErrorResponse(response)) {
207
+ throw toError(response.error);
208
+ }
209
+ return (response as { data?: unknown }).data as PaginatedForms;
210
+ },
211
+ ...SHARED_QUERY_CONFIG,
212
+ initialPageParam: 0,
213
+ getNextPageParam: (lastPage, allPages) => {
214
+ if (!lastPage || typeof lastPage !== "object") return undefined;
215
+ const items = (lastPage as PaginatedForms)?.items;
216
+ if (!Array.isArray(items) || items.length < limit) return undefined;
217
+ const loadedCount = (allPages || []).reduce(
218
+ (sum, page) =>
219
+ sum +
220
+ (Array.isArray((page as PaginatedForms)?.items)
221
+ ? (page as PaginatedForms).items.length
222
+ : 0),
223
+ 0,
224
+ );
225
+ const total = (lastPage as PaginatedForms)?.total ?? 0;
226
+ if (loadedCount >= total) return undefined;
227
+ return loadedCount;
228
+ },
229
+ });
230
+
231
+ if (error && !isFetching) {
232
+ throw error;
233
+ }
234
+
235
+ const pages = data.pages as PaginatedForms[];
236
+ const forms = (pages?.flatMap((page) =>
237
+ Array.isArray(page?.items) ? page.items : [],
238
+ ) ?? []) as SerializedForm[];
239
+ const total = pages?.[0]?.total ?? 0;
240
+
241
+ return {
242
+ forms,
243
+ total,
244
+ loadMore: fetchNextPage,
245
+ hasMore: !!hasNextPage,
246
+ isLoadingMore: isFetchingNextPage,
247
+ refetch,
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Hook for fetching a form by ID (admin)
253
+ */
254
+ export function useFormById(id: string): {
255
+ form: SerializedForm | null;
256
+ isLoading: boolean;
257
+ error: Error | null;
258
+ refetch: () => void;
259
+ } {
260
+ const { apiBaseURL, apiBasePath, headers } =
261
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
262
+ const client = createApiClient<FormBuilderApiRouter>({
263
+ baseURL: apiBaseURL,
264
+ basePath: apiBasePath,
265
+ });
266
+ const queries = createFormBuilderQueryKeys(client, headers);
267
+ const baseQuery = queries.forms.byId(id);
268
+
269
+ const { data, isLoading, error, refetch } = useQuery({
270
+ ...baseQuery,
271
+ ...SHARED_QUERY_CONFIG,
272
+ enabled: !!id,
273
+ });
274
+
275
+ return {
276
+ form: data ?? null,
277
+ isLoading,
278
+ error,
279
+ refetch,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Suspense variant of useFormById
285
+ */
286
+ export function useSuspenseFormById(id: string): {
287
+ form: SerializedForm | null;
288
+ refetch: () => Promise<unknown>;
289
+ } {
290
+ const { apiBaseURL, apiBasePath, headers } =
291
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
292
+ const client = createApiClient<FormBuilderApiRouter>({
293
+ baseURL: apiBaseURL,
294
+ basePath: apiBasePath,
295
+ });
296
+ const queries = createFormBuilderQueryKeys(client, headers);
297
+ const baseQuery = queries.forms.byId(id);
298
+
299
+ const { data, refetch, error, isFetching } = useSuspenseQuery({
300
+ ...baseQuery,
301
+ ...SHARED_QUERY_CONFIG,
302
+ });
303
+
304
+ if (error && !isFetching) {
305
+ throw error;
306
+ }
307
+
308
+ return {
309
+ form: data ?? null,
310
+ refetch,
311
+ };
312
+ }
313
+
314
+ /**
315
+ * Hook for fetching a form by slug (public)
316
+ */
317
+ export function useFormBySlug(slug: string): {
318
+ form: SerializedForm | null;
319
+ isLoading: boolean;
320
+ error: Error | null;
321
+ refetch: () => void;
322
+ } {
323
+ const { apiBaseURL, apiBasePath, headers } =
324
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
325
+ const client = createApiClient<FormBuilderApiRouter>({
326
+ baseURL: apiBaseURL,
327
+ basePath: apiBasePath,
328
+ });
329
+ const queries = createFormBuilderQueryKeys(client, headers);
330
+ const baseQuery = queries.forms.bySlug(slug);
331
+
332
+ const { data, isLoading, error, refetch } = useQuery({
333
+ ...baseQuery,
334
+ ...SHARED_QUERY_CONFIG,
335
+ enabled: !!slug,
336
+ });
337
+
338
+ return {
339
+ form: data ?? null,
340
+ isLoading,
341
+ error,
342
+ refetch,
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Suspense variant of useFormBySlug
348
+ */
349
+ export function useSuspenseFormBySlug(slug: string): {
350
+ form: SerializedForm | null;
351
+ refetch: () => Promise<unknown>;
352
+ } {
353
+ const { apiBaseURL, apiBasePath, headers } =
354
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
355
+ const client = createApiClient<FormBuilderApiRouter>({
356
+ baseURL: apiBaseURL,
357
+ basePath: apiBasePath,
358
+ });
359
+ const queries = createFormBuilderQueryKeys(client, headers);
360
+ const baseQuery = queries.forms.bySlug(slug);
361
+
362
+ const { data, refetch, error, isFetching } = useSuspenseQuery({
363
+ ...baseQuery,
364
+ ...SHARED_QUERY_CONFIG,
365
+ });
366
+
367
+ if (error && !isFetching) {
368
+ throw error;
369
+ }
370
+
371
+ return {
372
+ form: data ?? null,
373
+ refetch,
374
+ };
375
+ }
376
+
377
+ // ========== Form Mutations ==========
378
+
379
+ export interface CreateFormInput {
380
+ name: string;
381
+ slug: string;
382
+ description?: string;
383
+ schema: string;
384
+ successMessage?: string;
385
+ redirectUrl?: string;
386
+ status?: "active" | "inactive" | "archived";
387
+ }
388
+
389
+ export interface UpdateFormInput {
390
+ name?: string;
391
+ slug?: string;
392
+ description?: string;
393
+ schema?: string;
394
+ successMessage?: string;
395
+ redirectUrl?: string;
396
+ status?: "active" | "inactive" | "archived";
397
+ }
398
+
399
+ /**
400
+ * Hook for creating a form
401
+ */
402
+ export function useCreateForm() {
403
+ const { refresh, apiBaseURL, apiBasePath, headers } =
404
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
405
+ const client = createApiClient<FormBuilderApiRouter>({
406
+ baseURL: apiBaseURL,
407
+ basePath: apiBasePath,
408
+ });
409
+ const queryClient = useQueryClient();
410
+ const queries = createFormBuilderQueryKeys(client, headers);
411
+
412
+ return useMutation<SerializedForm, Error, CreateFormInput>({
413
+ mutationKey: [...queries.forms._def, "create"],
414
+ mutationFn: async (data) => {
415
+ const response: unknown = await client("@post/forms", {
416
+ method: "POST",
417
+ body: data,
418
+ headers,
419
+ });
420
+ if (isErrorResponse(response)) {
421
+ throw toError(response.error);
422
+ }
423
+ return (response as { data?: unknown }).data as SerializedForm;
424
+ },
425
+ onSuccess: async () => {
426
+ await queryClient.invalidateQueries({
427
+ queryKey: queries.forms._def,
428
+ });
429
+ if (refresh) {
430
+ await refresh();
431
+ }
432
+ },
433
+ });
434
+ }
435
+
436
+ /**
437
+ * Hook for updating a form
438
+ */
439
+ export function useUpdateForm() {
440
+ const { refresh, apiBaseURL, apiBasePath, headers } =
441
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
442
+ const client = createApiClient<FormBuilderApiRouter>({
443
+ baseURL: apiBaseURL,
444
+ basePath: apiBasePath,
445
+ });
446
+ const queryClient = useQueryClient();
447
+ const queries = createFormBuilderQueryKeys(client, headers);
448
+
449
+ return useMutation<
450
+ SerializedForm,
451
+ Error,
452
+ { id: string; data: UpdateFormInput }
453
+ >({
454
+ mutationKey: [...queries.forms._def, "update"],
455
+ mutationFn: async ({ id, data }) => {
456
+ const response: unknown = await client("@put/forms/:id", {
457
+ method: "PUT",
458
+ params: { id },
459
+ body: data,
460
+ headers,
461
+ });
462
+ if (isErrorResponse(response)) {
463
+ throw toError(response.error);
464
+ }
465
+ return (response as { data?: unknown }).data as SerializedForm;
466
+ },
467
+ onSuccess: async (updated) => {
468
+ if (updated) {
469
+ queryClient.setQueryData(
470
+ queries.forms.byId(updated.id).queryKey,
471
+ updated,
472
+ );
473
+ queryClient.setQueryData(
474
+ queries.forms.bySlug(updated.slug).queryKey,
475
+ updated,
476
+ );
477
+ }
478
+ await queryClient.invalidateQueries({
479
+ queryKey: queries.forms._def,
480
+ });
481
+ if (refresh) {
482
+ await refresh();
483
+ }
484
+ },
485
+ });
486
+ }
487
+
488
+ /**
489
+ * Hook for deleting a form
490
+ */
491
+ export function useDeleteForm() {
492
+ const { refresh, apiBaseURL, apiBasePath, headers } =
493
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
494
+ const client = createApiClient<FormBuilderApiRouter>({
495
+ baseURL: apiBaseURL,
496
+ basePath: apiBasePath,
497
+ });
498
+ const queryClient = useQueryClient();
499
+ const queries = createFormBuilderQueryKeys(client, headers);
500
+
501
+ return useMutation<{ success: boolean }, Error, string>({
502
+ mutationKey: [...queries.forms._def, "delete"],
503
+ mutationFn: async (id) => {
504
+ const response: unknown = await client("@delete/forms/:id", {
505
+ method: "DELETE",
506
+ params: { id },
507
+ headers,
508
+ });
509
+ if (isErrorResponse(response)) {
510
+ throw toError(response.error);
511
+ }
512
+ return (response as { data?: unknown }).data as { success: boolean };
513
+ },
514
+ onSuccess: async () => {
515
+ await queryClient.invalidateQueries({
516
+ queryKey: queries.forms._def,
517
+ });
518
+ if (refresh) {
519
+ await refresh();
520
+ }
521
+ },
522
+ });
523
+ }
524
+
525
+ // ========== Form Submission Hooks ==========
526
+
527
+ /**
528
+ * Hook for submitting a form (public)
529
+ */
530
+ export function useSubmitForm(slug: string) {
531
+ const { apiBaseURL, apiBasePath, headers } =
532
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
533
+ const client = createApiClient<FormBuilderApiRouter>({
534
+ baseURL: apiBaseURL,
535
+ basePath: apiBasePath,
536
+ });
537
+ const queries = createFormBuilderQueryKeys(client, headers);
538
+
539
+ return useMutation<
540
+ SerializedFormSubmission & {
541
+ form: { successMessage?: string; redirectUrl?: string };
542
+ },
543
+ Error,
544
+ { data: Record<string, unknown> }
545
+ >({
546
+ mutationKey: [...queries.forms._def, slug, "submit"],
547
+ mutationFn: async ({ data }) => {
548
+ const response: unknown = await client("@post/forms/:slug/submit", {
549
+ method: "POST",
550
+ params: { slug },
551
+ body: { data },
552
+ headers,
553
+ });
554
+ if (isErrorResponse(response)) {
555
+ throw toError(response.error);
556
+ }
557
+ return (response as { data?: unknown })
558
+ .data as SerializedFormSubmission & {
559
+ form: { successMessage?: string; redirectUrl?: string };
560
+ };
561
+ },
562
+ });
563
+ }
564
+
565
+ // ========== Submissions Management Hooks (Admin) ==========
566
+
567
+ export interface UseSubmissionsOptions {
568
+ /** Number of items per page (default: 20) */
569
+ limit?: number;
570
+ /** Whether to enable the query (default: true) */
571
+ enabled?: boolean;
572
+ }
573
+
574
+ export interface UseSubmissionsResult {
575
+ submissions: SerializedFormSubmissionWithData[];
576
+ total: number;
577
+ isLoading: boolean;
578
+ error: Error | null;
579
+ loadMore: () => void;
580
+ hasMore: boolean;
581
+ isLoadingMore: boolean;
582
+ refetch: () => void;
583
+ }
584
+
585
+ /**
586
+ * Hook for fetching paginated submissions for a form (admin)
587
+ */
588
+ export function useSubmissions(
589
+ formId: string,
590
+ options: UseSubmissionsOptions = {},
591
+ ): UseSubmissionsResult {
592
+ const { apiBaseURL, apiBasePath, headers } =
593
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
594
+ const client = createApiClient<FormBuilderApiRouter>({
595
+ baseURL: apiBaseURL,
596
+ basePath: apiBasePath,
597
+ });
598
+ const queries = createFormBuilderQueryKeys(client, headers);
599
+ const { limit = 20, enabled = true } = options;
600
+
601
+ const baseQuery = queries.formSubmissions.list({
602
+ formId,
603
+ limit,
604
+ offset: 0,
605
+ });
606
+
607
+ const {
608
+ data,
609
+ isLoading,
610
+ error,
611
+ fetchNextPage,
612
+ hasNextPage,
613
+ isFetchingNextPage,
614
+ refetch,
615
+ } = useInfiniteQuery({
616
+ queryKey: baseQuery.queryKey,
617
+ queryFn: async ({ pageParam = 0 }) => {
618
+ const response: unknown = await client("/forms/:formId/submissions", {
619
+ method: "GET",
620
+ params: { formId },
621
+ query: { limit, offset: pageParam },
622
+ headers,
623
+ });
624
+ if (isErrorResponse(response)) {
625
+ throw toError(response.error);
626
+ }
627
+ return (response as { data?: unknown }).data as PaginatedFormSubmissions;
628
+ },
629
+ ...SHARED_QUERY_CONFIG,
630
+ initialPageParam: 0,
631
+ getNextPageParam: (lastPage, allPages) => {
632
+ if (!lastPage || typeof lastPage !== "object") return undefined;
633
+ const items = (lastPage as PaginatedFormSubmissions)?.items;
634
+ if (!Array.isArray(items) || items.length < limit) return undefined;
635
+ const loadedCount = (allPages || []).reduce(
636
+ (sum, page) =>
637
+ sum +
638
+ (Array.isArray((page as PaginatedFormSubmissions)?.items)
639
+ ? (page as PaginatedFormSubmissions).items.length
640
+ : 0),
641
+ 0,
642
+ );
643
+ const total = (lastPage as PaginatedFormSubmissions)?.total ?? 0;
644
+ if (loadedCount >= total) return undefined;
645
+ return loadedCount;
646
+ },
647
+ enabled: enabled && !!formId,
648
+ });
649
+
650
+ const pages = (
651
+ data as InfiniteData<PaginatedFormSubmissions, number> | undefined
652
+ )?.pages;
653
+ const submissions = (pages?.flatMap((page) =>
654
+ Array.isArray(page?.items) ? page.items : [],
655
+ ) ?? []) as SerializedFormSubmissionWithData[];
656
+ const total = pages?.[0]?.total ?? 0;
657
+
658
+ return {
659
+ submissions,
660
+ total,
661
+ isLoading,
662
+ error,
663
+ loadMore: fetchNextPage,
664
+ hasMore: !!hasNextPage,
665
+ isLoadingMore: isFetchingNextPage,
666
+ refetch,
667
+ };
668
+ }
669
+
670
+ /**
671
+ * Suspense variant of useSubmissions
672
+ */
673
+ export function useSuspenseSubmissions(
674
+ formId: string,
675
+ options: UseSubmissionsOptions = {},
676
+ ): {
677
+ submissions: SerializedFormSubmissionWithData[];
678
+ total: number;
679
+ loadMore: () => Promise<unknown>;
680
+ hasMore: boolean;
681
+ isLoadingMore: boolean;
682
+ refetch: () => Promise<unknown>;
683
+ } {
684
+ const { apiBaseURL, apiBasePath, headers } =
685
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
686
+ const client = createApiClient<FormBuilderApiRouter>({
687
+ baseURL: apiBaseURL,
688
+ basePath: apiBasePath,
689
+ });
690
+ const queries = createFormBuilderQueryKeys(client, headers);
691
+ const { limit = 20 } = options;
692
+
693
+ const baseQuery = queries.formSubmissions.list({
694
+ formId,
695
+ limit,
696
+ offset: 0,
697
+ });
698
+
699
+ const {
700
+ data,
701
+ fetchNextPage,
702
+ hasNextPage,
703
+ isFetchingNextPage,
704
+ refetch,
705
+ error,
706
+ isFetching,
707
+ } = useSuspenseInfiniteQuery({
708
+ queryKey: baseQuery.queryKey,
709
+ queryFn: async ({ pageParam = 0 }) => {
710
+ const response: unknown = await client("/forms/:formId/submissions", {
711
+ method: "GET",
712
+ params: { formId },
713
+ query: { limit, offset: pageParam },
714
+ headers,
715
+ });
716
+ if (isErrorResponse(response)) {
717
+ throw toError(response.error);
718
+ }
719
+ return (response as { data?: unknown }).data as PaginatedFormSubmissions;
720
+ },
721
+ ...SHARED_QUERY_CONFIG,
722
+ initialPageParam: 0,
723
+ getNextPageParam: (lastPage, allPages) => {
724
+ if (!lastPage || typeof lastPage !== "object") return undefined;
725
+ const items = (lastPage as PaginatedFormSubmissions)?.items;
726
+ if (!Array.isArray(items) || items.length < limit) return undefined;
727
+ const loadedCount = (allPages || []).reduce(
728
+ (sum, page) =>
729
+ sum +
730
+ (Array.isArray((page as PaginatedFormSubmissions)?.items)
731
+ ? (page as PaginatedFormSubmissions).items.length
732
+ : 0),
733
+ 0,
734
+ );
735
+ const total = (lastPage as PaginatedFormSubmissions)?.total ?? 0;
736
+ if (loadedCount >= total) return undefined;
737
+ return loadedCount;
738
+ },
739
+ });
740
+
741
+ if (error && !isFetching) {
742
+ throw error;
743
+ }
744
+
745
+ const pages = data.pages as PaginatedFormSubmissions[];
746
+ const submissions = (pages?.flatMap((page) =>
747
+ Array.isArray(page?.items) ? page.items : [],
748
+ ) ?? []) as SerializedFormSubmissionWithData[];
749
+ const total = pages?.[0]?.total ?? 0;
750
+
751
+ return {
752
+ submissions,
753
+ total,
754
+ loadMore: fetchNextPage,
755
+ hasMore: !!hasNextPage,
756
+ isLoadingMore: isFetchingNextPage,
757
+ refetch,
758
+ };
759
+ }
760
+
761
+ /**
762
+ * Hook for deleting a submission
763
+ */
764
+ export function useDeleteSubmission(formId: string) {
765
+ const { refresh, apiBaseURL, apiBasePath, headers } =
766
+ usePluginOverrides<FormBuilderPluginOverrides>("form-builder");
767
+ const client = createApiClient<FormBuilderApiRouter>({
768
+ baseURL: apiBaseURL,
769
+ basePath: apiBasePath,
770
+ });
771
+ const queryClient = useQueryClient();
772
+ const queries = createFormBuilderQueryKeys(client, headers);
773
+
774
+ return useMutation<{ success: boolean }, Error, string>({
775
+ mutationKey: [...queries.formSubmissions._def, formId, "delete"],
776
+ mutationFn: async (subId) => {
777
+ const response: unknown = await client(
778
+ "@delete/forms/:formId/submissions/:subId",
779
+ {
780
+ method: "DELETE",
781
+ params: { formId, subId },
782
+ headers,
783
+ },
784
+ );
785
+ if (isErrorResponse(response)) {
786
+ throw toError(response.error);
787
+ }
788
+ return (response as { data?: unknown }).data as { success: boolean };
789
+ },
790
+ onSuccess: async () => {
791
+ await queryClient.invalidateQueries({
792
+ queryKey: queries.formSubmissions._def,
793
+ });
794
+ if (refresh) {
795
+ await refresh();
796
+ }
797
+ },
798
+ });
799
+ }