@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,231 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { usePluginOverrides, useBasePath } from "@btst/stack/context";
5
+ import { Button } from "@workspace/ui/components/button";
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "@workspace/ui/components/table";
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuTrigger,
19
+ } from "@workspace/ui/components/dropdown-menu";
20
+ import {
21
+ AlertDialog,
22
+ AlertDialogAction,
23
+ AlertDialogCancel,
24
+ AlertDialogContent,
25
+ AlertDialogDescription,
26
+ AlertDialogFooter,
27
+ AlertDialogHeader,
28
+ AlertDialogTitle,
29
+ } from "@workspace/ui/components/alert-dialog";
30
+ import { MoreHorizontal, Plus, Pencil, Trash2, FileText } from "lucide-react";
31
+ import { toast } from "sonner";
32
+
33
+ import {
34
+ useSuspenseForms,
35
+ useDeleteForm,
36
+ } from "../../hooks/form-builder-hooks";
37
+ import type { FormBuilderPluginOverrides } from "../../overrides";
38
+ import { FORM_BUILDER_LOCALIZATION } from "../../localization";
39
+ import { PageWrapper } from "../shared/page-wrapper";
40
+ import { EmptyState } from "../shared/empty-state";
41
+ import { Pagination } from "../shared/pagination";
42
+
43
+ export function FormListPage() {
44
+ const { navigate, Link, localization } = usePluginOverrides<
45
+ FormBuilderPluginOverrides,
46
+ Partial<FormBuilderPluginOverrides>
47
+ >("form-builder", {
48
+ localization: FORM_BUILDER_LOCALIZATION,
49
+ });
50
+ const basePath = useBasePath();
51
+ const { forms, total, hasMore, isLoadingMore, loadMore, refetch } =
52
+ useSuspenseForms();
53
+ const deleteMutation = useDeleteForm();
54
+
55
+ const [deleteId, setDeleteId] = useState<string | null>(null);
56
+
57
+ const loc = localization || FORM_BUILDER_LOCALIZATION;
58
+ const LinkComponent = Link || "a";
59
+
60
+ const handleDelete = async () => {
61
+ if (!deleteId) return;
62
+
63
+ try {
64
+ await deleteMutation.mutateAsync(deleteId);
65
+ toast.success(loc.FORM_BUILDER_TOAST_DELETE_SUCCESS);
66
+ setDeleteId(null);
67
+ await refetch();
68
+ } catch (error) {
69
+ toast.error(loc.FORM_BUILDER_TOAST_ERROR);
70
+ }
71
+ };
72
+
73
+ const getStatusBadge = (status: string) => {
74
+ const colors: Record<string, string> = {
75
+ active:
76
+ "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
77
+ inactive:
78
+ "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
79
+ archived: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200",
80
+ };
81
+ return (
82
+ <span
83
+ className={`px-2 py-1 rounded-full text-xs font-medium ${colors[status] || colors.inactive}`}
84
+ >
85
+ {status}
86
+ </span>
87
+ );
88
+ };
89
+
90
+ return (
91
+ <PageWrapper testId="form-list-page">
92
+ <div className="w-full max-w-5xl space-y-6">
93
+ <div className="flex items-center justify-between">
94
+ <div>
95
+ <h1 className="text-2xl font-bold">
96
+ {loc.FORM_BUILDER_LIST_TITLE}
97
+ </h1>
98
+ <p className="text-muted-foreground">
99
+ {loc.FORM_BUILDER_LIST_SUBTITLE}
100
+ </p>
101
+ </div>
102
+ <Button asChild>
103
+ <LinkComponent href={`${basePath}/forms/new`}>
104
+ <Plus className="mr-2 h-4 w-4" />
105
+ {loc.FORM_BUILDER_BUTTON_NEW_FORM}
106
+ </LinkComponent>
107
+ </Button>
108
+ </div>
109
+
110
+ {forms.length === 0 ? (
111
+ <EmptyState
112
+ title={loc.FORM_BUILDER_LIST_EMPTY}
113
+ description={loc.FORM_BUILDER_LIST_EMPTY_DESCRIPTION}
114
+ action={
115
+ <Button asChild>
116
+ <LinkComponent href={`${basePath}/forms/new`}>
117
+ <Plus className="mr-2 h-4 w-4" />
118
+ {loc.FORM_BUILDER_BUTTON_NEW_FORM}
119
+ </LinkComponent>
120
+ </Button>
121
+ }
122
+ />
123
+ ) : (
124
+ <>
125
+ <div className="rounded-lg border">
126
+ <Table>
127
+ <TableHeader>
128
+ <TableRow>
129
+ <TableHead>{loc.FORM_BUILDER_LIST_COLUMN_NAME}</TableHead>
130
+ <TableHead>{loc.FORM_BUILDER_LIST_COLUMN_SLUG}</TableHead>
131
+ <TableHead>{loc.FORM_BUILDER_LIST_COLUMN_STATUS}</TableHead>
132
+ <TableHead>
133
+ {loc.FORM_BUILDER_LIST_COLUMN_CREATED}
134
+ </TableHead>
135
+ <TableHead className="w-12">
136
+ {loc.FORM_BUILDER_LIST_COLUMN_ACTIONS}
137
+ </TableHead>
138
+ </TableRow>
139
+ </TableHeader>
140
+ <TableBody>
141
+ {forms.map((form) => (
142
+ <TableRow key={form.id}>
143
+ <TableCell className="font-medium">{form.name}</TableCell>
144
+ <TableCell className="text-muted-foreground font-mono text-sm">
145
+ {form.slug}
146
+ </TableCell>
147
+ <TableCell>{getStatusBadge(form.status)}</TableCell>
148
+ <TableCell className="text-muted-foreground">
149
+ {new Date(form.createdAt).toLocaleDateString()}
150
+ </TableCell>
151
+ <TableCell>
152
+ <DropdownMenu>
153
+ <DropdownMenuTrigger asChild>
154
+ <Button variant="ghost" size="icon">
155
+ <MoreHorizontal className="h-4 w-4" />
156
+ <span className="sr-only">Actions</span>
157
+ </Button>
158
+ </DropdownMenuTrigger>
159
+ <DropdownMenuContent align="end">
160
+ <DropdownMenuItem
161
+ onClick={() =>
162
+ navigate?.(`${basePath}/forms/${form.id}/edit`)
163
+ }
164
+ >
165
+ <Pencil className="mr-2 h-4 w-4" />
166
+ {loc.FORM_BUILDER_LIST_ACTION_EDIT}
167
+ </DropdownMenuItem>
168
+ <DropdownMenuItem
169
+ onClick={() =>
170
+ navigate?.(
171
+ `${basePath}/forms/${form.id}/submissions`,
172
+ )
173
+ }
174
+ >
175
+ <FileText className="mr-2 h-4 w-4" />
176
+ {loc.FORM_BUILDER_LIST_ACTION_SUBMISSIONS}
177
+ </DropdownMenuItem>
178
+ <DropdownMenuItem
179
+ className="text-destructive"
180
+ onClick={() => setDeleteId(form.id)}
181
+ >
182
+ <Trash2 className="mr-2 h-4 w-4" />
183
+ {loc.FORM_BUILDER_LIST_ACTION_DELETE}
184
+ </DropdownMenuItem>
185
+ </DropdownMenuContent>
186
+ </DropdownMenu>
187
+ </TableCell>
188
+ </TableRow>
189
+ ))}
190
+ </TableBody>
191
+ </Table>
192
+ </div>
193
+
194
+ <Pagination
195
+ total={total}
196
+ showing={forms.length}
197
+ hasMore={hasMore}
198
+ isLoadingMore={isLoadingMore}
199
+ onLoadMore={loadMore}
200
+ />
201
+ </>
202
+ )}
203
+ </div>
204
+
205
+ {/* Delete confirmation dialog */}
206
+ <AlertDialog open={!!deleteId} onOpenChange={() => setDeleteId(null)}>
207
+ <AlertDialogContent>
208
+ <AlertDialogHeader>
209
+ <AlertDialogTitle>Delete Form</AlertDialogTitle>
210
+ <AlertDialogDescription>
211
+ {loc.FORM_BUILDER_EDITOR_DELETE_CONFIRM}
212
+ </AlertDialogDescription>
213
+ </AlertDialogHeader>
214
+ <AlertDialogFooter>
215
+ <AlertDialogCancel>
216
+ {loc.FORM_BUILDER_BUTTON_CANCEL}
217
+ </AlertDialogCancel>
218
+ <AlertDialogAction
219
+ onClick={handleDelete}
220
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
221
+ >
222
+ {deleteMutation.isPending
223
+ ? loc.FORM_BUILDER_STATUS_DELETING
224
+ : loc.FORM_BUILDER_BUTTON_DELETE}
225
+ </AlertDialogAction>
226
+ </AlertDialogFooter>
227
+ </AlertDialogContent>
228
+ </AlertDialog>
229
+ </PageWrapper>
230
+ );
231
+ }
@@ -0,0 +1,22 @@
1
+ "use client";
2
+
3
+ import { lazy, Suspense } from "react";
4
+ import { FormListSkeleton } from "../loading/form-list-skeleton";
5
+ import { ErrorBoundary } from "react-error-boundary";
6
+ import { DefaultError } from "../shared/default-error";
7
+
8
+ const FormListPage = lazy(() =>
9
+ import("./form-list-page.internal").then((m) => ({
10
+ default: m.FormListPage,
11
+ })),
12
+ );
13
+
14
+ export function FormListPageComponent() {
15
+ return (
16
+ <ErrorBoundary FallbackComponent={DefaultError}>
17
+ <Suspense fallback={<FormListSkeleton />}>
18
+ <FormListPage />
19
+ </Suspense>
20
+ </ErrorBoundary>
21
+ );
22
+ }
@@ -0,0 +1,268 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { usePluginOverrides, useBasePath } from "@btst/stack/context";
5
+ import { Button } from "@workspace/ui/components/button";
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "@workspace/ui/components/table";
14
+ import {
15
+ AlertDialog,
16
+ AlertDialogAction,
17
+ AlertDialogCancel,
18
+ AlertDialogContent,
19
+ AlertDialogDescription,
20
+ AlertDialogFooter,
21
+ AlertDialogHeader,
22
+ AlertDialogTitle,
23
+ } from "@workspace/ui/components/alert-dialog";
24
+ import {
25
+ Dialog,
26
+ DialogContent,
27
+ DialogHeader,
28
+ DialogTitle,
29
+ } from "@workspace/ui/components/dialog";
30
+ import { ArrowLeft, Trash2, Eye } from "lucide-react";
31
+ import { toast } from "sonner";
32
+
33
+ import {
34
+ useSuspenseFormById,
35
+ useSuspenseSubmissions,
36
+ useDeleteSubmission,
37
+ } from "../../hooks/form-builder-hooks";
38
+ import type { FormBuilderPluginOverrides } from "../../overrides";
39
+ import { FORM_BUILDER_LOCALIZATION } from "../../localization";
40
+ import type { SerializedFormSubmissionWithData } from "../../../types";
41
+ import { PageWrapper } from "../shared/page-wrapper";
42
+ import { EmptyState } from "../shared/empty-state";
43
+ import { Pagination } from "../shared/pagination";
44
+
45
+ export interface SubmissionsPageProps {
46
+ formId: string;
47
+ }
48
+
49
+ export function SubmissionsPage({ formId }: SubmissionsPageProps) {
50
+ const { navigate, Link, localization } = usePluginOverrides<
51
+ FormBuilderPluginOverrides,
52
+ Partial<FormBuilderPluginOverrides>
53
+ >("form-builder", {
54
+ localization: FORM_BUILDER_LOCALIZATION,
55
+ });
56
+ const basePath = useBasePath();
57
+
58
+ const { form } = useSuspenseFormById(formId);
59
+ const { submissions, total, hasMore, isLoadingMore, loadMore, refetch } =
60
+ useSuspenseSubmissions(formId);
61
+ const deleteMutation = useDeleteSubmission(formId);
62
+
63
+ const [deleteId, setDeleteId] = useState<string | null>(null);
64
+ const [viewSubmission, setViewSubmission] =
65
+ useState<SerializedFormSubmissionWithData | null>(null);
66
+
67
+ const loc = localization || FORM_BUILDER_LOCALIZATION;
68
+ const LinkComponent = Link || "a";
69
+
70
+ const handleDelete = async () => {
71
+ if (!deleteId) return;
72
+
73
+ try {
74
+ await deleteMutation.mutateAsync(deleteId);
75
+ toast.success(loc.FORM_BUILDER_TOAST_SUBMISSION_DELETED);
76
+ setDeleteId(null);
77
+ await refetch();
78
+ } catch (error) {
79
+ toast.error(loc.FORM_BUILDER_TOAST_ERROR);
80
+ }
81
+ };
82
+
83
+ const formatSubmissionData = (data: Record<string, unknown>) => {
84
+ const entries = Object.entries(data).slice(0, 3);
85
+ return entries
86
+ .map(([key, value]) => {
87
+ const strValue =
88
+ typeof value === "string" ? value : JSON.stringify(value);
89
+ const truncated =
90
+ strValue.length > 30 ? `${strValue.slice(0, 30)}...` : strValue;
91
+ return `${key}: ${truncated}`;
92
+ })
93
+ .join(", ");
94
+ };
95
+
96
+ return (
97
+ <PageWrapper testId="submissions-page">
98
+ <div className="w-full max-w-5xl space-y-6">
99
+ <div className="flex items-center gap-4">
100
+ <Button variant="ghost" size="icon" asChild>
101
+ <LinkComponent href={`${basePath}/forms`}>
102
+ <ArrowLeft className="h-4 w-4" />
103
+ </LinkComponent>
104
+ </Button>
105
+ <div>
106
+ <h1 className="text-2xl font-bold">
107
+ {form?.name || loc.FORM_BUILDER_SUBMISSIONS_TITLE}
108
+ </h1>
109
+ <p className="text-muted-foreground">
110
+ {loc.FORM_BUILDER_SUBMISSIONS_SUBTITLE}
111
+ </p>
112
+ </div>
113
+ </div>
114
+
115
+ {submissions.length === 0 ? (
116
+ <EmptyState
117
+ title={loc.FORM_BUILDER_SUBMISSIONS_EMPTY}
118
+ description={loc.FORM_BUILDER_SUBMISSIONS_EMPTY_DESCRIPTION}
119
+ />
120
+ ) : (
121
+ <>
122
+ <div className="rounded-lg border">
123
+ <Table>
124
+ <TableHeader>
125
+ <TableRow>
126
+ <TableHead className="w-24">
127
+ {loc.FORM_BUILDER_SUBMISSIONS_COLUMN_ID}
128
+ </TableHead>
129
+ <TableHead>
130
+ {loc.FORM_BUILDER_SUBMISSIONS_COLUMN_DATA}
131
+ </TableHead>
132
+ <TableHead>
133
+ {loc.FORM_BUILDER_SUBMISSIONS_COLUMN_SUBMITTED_AT}
134
+ </TableHead>
135
+ <TableHead>
136
+ {loc.FORM_BUILDER_SUBMISSIONS_COLUMN_IP_ADDRESS}
137
+ </TableHead>
138
+ <TableHead className="w-24">
139
+ {loc.FORM_BUILDER_SUBMISSIONS_COLUMN_ACTIONS}
140
+ </TableHead>
141
+ </TableRow>
142
+ </TableHeader>
143
+ <TableBody>
144
+ {submissions.map((sub) => (
145
+ <TableRow key={sub.id}>
146
+ <TableCell className="font-mono text-xs">
147
+ {sub.id.slice(0, 8)}...
148
+ </TableCell>
149
+ <TableCell className="max-w-xs truncate text-sm text-muted-foreground">
150
+ {formatSubmissionData(sub.parsedData)}
151
+ </TableCell>
152
+ <TableCell className="text-muted-foreground">
153
+ {new Date(sub.submittedAt).toLocaleString()}
154
+ </TableCell>
155
+ <TableCell className="text-muted-foreground font-mono text-xs">
156
+ {sub.ipAddress || "-"}
157
+ </TableCell>
158
+ <TableCell>
159
+ <div className="flex gap-1">
160
+ <Button
161
+ variant="ghost"
162
+ size="icon"
163
+ onClick={() => setViewSubmission(sub)}
164
+ >
165
+ <Eye className="h-4 w-4" />
166
+ <span className="sr-only">View</span>
167
+ </Button>
168
+ <Button
169
+ variant="ghost"
170
+ size="icon"
171
+ className="text-destructive"
172
+ onClick={() => setDeleteId(sub.id)}
173
+ >
174
+ <Trash2 className="h-4 w-4" />
175
+ <span className="sr-only">Delete</span>
176
+ </Button>
177
+ </div>
178
+ </TableCell>
179
+ </TableRow>
180
+ ))}
181
+ </TableBody>
182
+ </Table>
183
+ </div>
184
+
185
+ <Pagination
186
+ total={total}
187
+ showing={submissions.length}
188
+ hasMore={hasMore}
189
+ isLoadingMore={isLoadingMore}
190
+ onLoadMore={loadMore}
191
+ />
192
+ </>
193
+ )}
194
+ </div>
195
+
196
+ {/* View submission dialog */}
197
+ <Dialog
198
+ open={!!viewSubmission}
199
+ onOpenChange={() => setViewSubmission(null)}
200
+ >
201
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-auto">
202
+ <DialogHeader>
203
+ <DialogTitle>Submission Details</DialogTitle>
204
+ </DialogHeader>
205
+ {viewSubmission && (
206
+ <div className="space-y-4">
207
+ <div className="grid grid-cols-2 gap-4 text-sm">
208
+ <div>
209
+ <span className="text-muted-foreground">ID:</span>
210
+ <p className="font-mono truncate">{viewSubmission.id}</p>
211
+ </div>
212
+ <div>
213
+ <span className="text-muted-foreground">Submitted:</span>
214
+ <p className="truncate">
215
+ {new Date(viewSubmission.submittedAt).toLocaleString()}
216
+ </p>
217
+ </div>
218
+ <div>
219
+ <span className="text-muted-foreground">IP Address:</span>
220
+ <p className="font-mono truncate">
221
+ {viewSubmission.ipAddress || "-"}
222
+ </p>
223
+ </div>
224
+ <div>
225
+ <span className="text-muted-foreground">User Agent:</span>
226
+ <p className="text-xs truncate">
227
+ {viewSubmission.userAgent || "-"}
228
+ </p>
229
+ </div>
230
+ </div>
231
+ <div>
232
+ <span className="text-muted-foreground text-sm">Data:</span>
233
+ <pre className="mt-2 p-4 bg-muted rounded-lg text-sm overflow-auto">
234
+ {JSON.stringify(viewSubmission.parsedData, null, 2)}
235
+ </pre>
236
+ </div>
237
+ </div>
238
+ )}
239
+ </DialogContent>
240
+ </Dialog>
241
+
242
+ {/* Delete confirmation dialog */}
243
+ <AlertDialog open={!!deleteId} onOpenChange={() => setDeleteId(null)}>
244
+ <AlertDialogContent>
245
+ <AlertDialogHeader>
246
+ <AlertDialogTitle>Delete Submission</AlertDialogTitle>
247
+ <AlertDialogDescription>
248
+ {loc.FORM_BUILDER_SUBMISSIONS_DELETE_CONFIRM}
249
+ </AlertDialogDescription>
250
+ </AlertDialogHeader>
251
+ <AlertDialogFooter>
252
+ <AlertDialogCancel>
253
+ {loc.FORM_BUILDER_BUTTON_CANCEL}
254
+ </AlertDialogCancel>
255
+ <AlertDialogAction
256
+ onClick={handleDelete}
257
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
258
+ >
259
+ {deleteMutation.isPending
260
+ ? loc.FORM_BUILDER_STATUS_DELETING
261
+ : loc.FORM_BUILDER_BUTTON_DELETE}
262
+ </AlertDialogAction>
263
+ </AlertDialogFooter>
264
+ </AlertDialogContent>
265
+ </AlertDialog>
266
+ </PageWrapper>
267
+ );
268
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { lazy, Suspense } from "react";
4
+ import { SubmissionsSkeleton } from "../loading/submissions-skeleton";
5
+ import { ErrorBoundary } from "react-error-boundary";
6
+ import { DefaultError } from "../shared/default-error";
7
+
8
+ const SubmissionsPage = lazy(() =>
9
+ import("./submissions-page.internal").then((m) => ({
10
+ default: m.SubmissionsPage,
11
+ })),
12
+ );
13
+
14
+ export interface SubmissionsPageProps {
15
+ formId: string;
16
+ }
17
+
18
+ export function SubmissionsPageComponent({ formId }: SubmissionsPageProps) {
19
+ return (
20
+ <ErrorBoundary FallbackComponent={DefaultError}>
21
+ <Suspense fallback={<SubmissionsSkeleton />}>
22
+ <SubmissionsPage formId={formId} />
23
+ </Suspense>
24
+ </ErrorBoundary>
25
+ );
26
+ }
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { AlertCircle } from "lucide-react";
4
+ import { Button } from "@workspace/ui/components/button";
5
+
6
+ interface DefaultErrorProps {
7
+ error: Error;
8
+ resetErrorBoundary?: () => void;
9
+ }
10
+
11
+ export function DefaultError({ error, resetErrorBoundary }: DefaultErrorProps) {
12
+ return (
13
+ <div className="flex flex-col items-center justify-center py-12 px-4 text-center">
14
+ <div className="rounded-full bg-destructive/10 p-4 mb-4">
15
+ <AlertCircle className="h-8 w-8 text-destructive" />
16
+ </div>
17
+ <h3 className="text-lg font-medium text-foreground mb-2">
18
+ Something went wrong
19
+ </h3>
20
+ <p className="text-sm text-muted-foreground mb-4 max-w-sm">
21
+ {error.message || "An unexpected error occurred"}
22
+ </p>
23
+ {resetErrorBoundary && (
24
+ <Button variant="outline" onClick={resetErrorBoundary}>
25
+ Try again
26
+ </Button>
27
+ )}
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,26 @@
1
+ "use client";
2
+
3
+ import { Inbox } from "lucide-react";
4
+
5
+ interface EmptyStateProps {
6
+ title: string;
7
+ description?: string;
8
+ action?: React.ReactNode;
9
+ }
10
+
11
+ export function EmptyState({ title, description, action }: EmptyStateProps) {
12
+ return (
13
+ <div className="flex flex-col items-center justify-center py-12 px-4 text-center">
14
+ <div className="rounded-full bg-muted p-4 mb-4">
15
+ <Inbox className="h-8 w-8 text-muted-foreground" />
16
+ </div>
17
+ <h3 className="text-lg font-medium text-foreground mb-2">{title}</h3>
18
+ {description && (
19
+ <p className="text-sm text-muted-foreground mb-4 max-w-sm">
20
+ {description}
21
+ </p>
22
+ )}
23
+ {action}
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { usePluginOverrides } from "@btst/stack/context";
4
+ import { PageWrapper as SharedPageWrapper } from "@workspace/ui/components/page-wrapper";
5
+ import type { FormBuilderPluginOverrides } from "../../overrides";
6
+
7
+ export function PageWrapper({
8
+ children,
9
+ className,
10
+ testId,
11
+ }: {
12
+ children: React.ReactNode;
13
+ className?: string;
14
+ testId?: string;
15
+ }) {
16
+ const { showAttribution } = usePluginOverrides<
17
+ FormBuilderPluginOverrides,
18
+ Partial<FormBuilderPluginOverrides>
19
+ >("form-builder", {
20
+ showAttribution: true,
21
+ });
22
+
23
+ return (
24
+ <SharedPageWrapper
25
+ className={className}
26
+ testId={testId}
27
+ showAttribution={showAttribution}
28
+ >
29
+ {children}
30
+ </SharedPageWrapper>
31
+ );
32
+ }
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { Button } from "@workspace/ui/components/button";
4
+ import { ChevronRight } from "lucide-react";
5
+
6
+ interface PaginationProps {
7
+ total: number;
8
+ showing: number;
9
+ hasMore: boolean;
10
+ isLoadingMore: boolean;
11
+ onLoadMore: () => void;
12
+ labels?: {
13
+ showing?: string;
14
+ previous?: string;
15
+ next?: string;
16
+ };
17
+ }
18
+
19
+ export function Pagination({
20
+ total,
21
+ showing,
22
+ hasMore,
23
+ isLoadingMore,
24
+ onLoadMore,
25
+ labels = {},
26
+ }: PaginationProps) {
27
+ const {
28
+ showing: showingLabel = "Showing {count} of {total}",
29
+ next = "Load More",
30
+ } = labels;
31
+
32
+ const showingText = showingLabel
33
+ .replace("{count}", String(showing))
34
+ .replace("{total}", String(total));
35
+
36
+ return (
37
+ <div className="flex items-center justify-between py-4">
38
+ <p className="text-sm text-muted-foreground">{showingText}</p>
39
+ {hasMore && (
40
+ <Button
41
+ variant="outline"
42
+ size="sm"
43
+ onClick={onLoadMore}
44
+ disabled={isLoadingMore}
45
+ >
46
+ {isLoadingMore ? "Loading..." : next}
47
+ <ChevronRight className="ml-2 h-4 w-4" />
48
+ </Button>
49
+ )}
50
+ </div>
51
+ );
52
+ }