@btst/stack 1.5.2 → 1.7.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.
- package/dist/api/index.cjs +7 -1
- package/dist/api/index.d.cts +2 -2
- package/dist/api/index.d.mts +2 -2
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.mjs +7 -1
- package/dist/client/index.d.cts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/dist/packages/better-stack/src/plugins/cms/api/plugin.cjs +3 -2
- package/dist/packages/better-stack/src/plugins/cms/api/plugin.mjs +3 -2
- package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.cjs +15 -15
- package/dist/packages/better-stack/src/plugins/cms/client/components/forms/content-form.mjs +16 -16
- package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.cjs +588 -0
- package/dist/packages/better-stack/src/plugins/form-builder/api/plugin.mjs +586 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +131 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +129 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.cjs +32 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-builder-skeleton.mjs +30 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.cjs +21 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/form-list-skeleton.mjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.cjs +34 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/loading/submissions-skeleton.mjs +32 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.cjs +20 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/404-page.mjs +18 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.cjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.cjs +186 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.internal.mjs +184 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-builder-page.mjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.cjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +165 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +163 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/form-list-page.mjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.cjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +177 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +175 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/pages/submissions-page.mjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.cjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/default-error.mjs +15 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.cjs +16 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/empty-state.mjs +14 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +27 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +25 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.cjs +39 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/components/shared/pagination.mjs +37 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.cjs +551 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/hooks/form-builder-hooks.mjs +537 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.cjs +36 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-common.mjs +34 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.cjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-editor.mjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.cjs +21 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-list.mjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.cjs +19 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-submissions.mjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.cjs +14 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/form-builder-toasts.mjs +12 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.cjs +17 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/localization/index.mjs +15 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.cjs +278 -0
- package/dist/packages/better-stack/src/plugins/form-builder/client/plugin.mjs +276 -0
- package/dist/packages/better-stack/src/plugins/form-builder/db.cjs +99 -0
- package/dist/packages/better-stack/src/plugins/form-builder/db.mjs +97 -0
- package/dist/packages/better-stack/src/plugins/form-builder/schemas.cjs +82 -0
- package/dist/packages/better-stack/src/plugins/form-builder/schemas.mjs +74 -0
- package/dist/packages/better-stack/src/plugins/form-builder/utils.cjs +37 -0
- package/dist/packages/better-stack/src/plugins/form-builder/utils.mjs +29 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/generator.cjs +300 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/generator.mjs +284 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/plugin.cjs +115 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/plugin.mjs +113 -0
- package/dist/packages/better-stack/src/plugins/open-api/db.cjs +7 -0
- package/dist/packages/better-stack/src/plugins/open-api/db.mjs +5 -0
- package/dist/packages/better-stack/src/plugins/open-api/logo.cjs +8 -0
- package/dist/packages/better-stack/src/plugins/open-api/logo.mjs +6 -0
- package/dist/packages/ui/src/components/auto-form/index.cjs +2 -12
- package/dist/packages/ui/src/components/auto-form/index.mjs +2 -9
- package/dist/packages/ui/src/components/auto-form/stepped-auto-form.cjs +377 -0
- package/dist/packages/ui/src/components/auto-form/stepped-auto-form.mjs +368 -0
- package/dist/packages/ui/src/components/auto-form/utils.cjs +1 -56
- package/dist/packages/ui/src/components/auto-form/utils.mjs +2 -56
- package/dist/packages/ui/src/components/form-builder/canvas.cjs +111 -0
- package/dist/packages/ui/src/components/form-builder/canvas.mjs +109 -0
- package/dist/packages/ui/src/components/form-builder/components/index.cjs +570 -0
- package/dist/packages/ui/src/components/form-builder/components/index.mjs +553 -0
- package/dist/packages/ui/src/components/form-builder/edit-field-dialog.cjs +131 -0
- package/dist/packages/ui/src/components/form-builder/edit-field-dialog.mjs +129 -0
- package/dist/packages/ui/src/components/form-builder/form-preview.cjs +73 -0
- package/dist/packages/ui/src/components/form-builder/form-preview.mjs +71 -0
- package/dist/packages/ui/src/components/form-builder/index.cjs +353 -0
- package/dist/packages/ui/src/components/form-builder/index.mjs +344 -0
- package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.cjs +263 -0
- package/dist/packages/ui/src/components/form-builder/nested-field-editor-dialog.mjs +261 -0
- package/dist/packages/ui/src/components/form-builder/palette.cjs +52 -0
- package/dist/packages/ui/src/components/form-builder/palette.mjs +49 -0
- package/dist/packages/ui/src/components/form-builder/schema-utils.cjs +120 -0
- package/dist/packages/ui/src/components/form-builder/schema-utils.mjs +114 -0
- package/dist/packages/ui/src/components/form-builder/sortable-field.cjs +151 -0
- package/dist/packages/ui/src/components/form-builder/sortable-field.mjs +148 -0
- package/dist/packages/ui/src/components/form-builder/step-tabs.cjs +180 -0
- package/dist/packages/ui/src/components/form-builder/step-tabs.mjs +178 -0
- package/dist/packages/ui/src/components/form-builder/types.cjs +7 -0
- package/dist/packages/ui/src/components/form-builder/types.mjs +5 -0
- package/dist/packages/ui/src/components/form-builder/validation-schemas.cjs +67 -0
- package/dist/packages/ui/src/components/form-builder/validation-schemas.mjs +56 -0
- package/dist/packages/ui/src/components/tabs.cjs +70 -0
- package/dist/packages/ui/src/components/tabs.mjs +65 -0
- package/dist/packages/ui/src/lib/schema-converter.cjs +130 -0
- package/dist/packages/ui/src/lib/schema-converter.mjs +124 -0
- package/dist/plugins/api/index.d.cts +2 -2
- package/dist/plugins/api/index.d.mts +2 -2
- package/dist/plugins/api/index.d.ts +2 -2
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/client/index.d.cts +2 -2
- package/dist/plugins/client/index.d.mts +2 -2
- package/dist/plugins/client/index.d.ts +2 -2
- package/dist/plugins/cms/client/index.cjs +6 -0
- package/dist/plugins/cms/client/index.d.cts +6 -113
- package/dist/plugins/cms/client/index.d.mts +6 -113
- package/dist/plugins/cms/client/index.d.ts +6 -113
- package/dist/plugins/cms/client/index.mjs +1 -0
- package/dist/plugins/form-builder/api/index.cjs +7 -0
- package/dist/plugins/form-builder/api/index.d.cts +141 -0
- package/dist/plugins/form-builder/api/index.d.mts +141 -0
- package/dist/plugins/form-builder/api/index.d.ts +141 -0
- package/dist/plugins/form-builder/api/index.mjs +1 -0
- package/dist/plugins/form-builder/client/components/index.cjs +29 -0
- package/dist/plugins/form-builder/client/components/index.d.cts +93 -0
- package/dist/plugins/form-builder/client/components/index.d.mts +93 -0
- package/dist/plugins/form-builder/client/components/index.d.ts +93 -0
- package/dist/plugins/form-builder/client/components/index.mjs +18 -0
- package/dist/plugins/form-builder/client/hooks/index.cjs +19 -0
- package/dist/plugins/form-builder/client/hooks/index.d.cts +154 -0
- package/dist/plugins/form-builder/client/hooks/index.d.mts +154 -0
- package/dist/plugins/form-builder/client/hooks/index.d.ts +154 -0
- package/dist/plugins/form-builder/client/hooks/index.mjs +1 -0
- package/dist/plugins/form-builder/client/index.cjs +13 -0
- package/dist/plugins/form-builder/client/index.d.cts +381 -0
- package/dist/plugins/form-builder/client/index.d.mts +381 -0
- package/dist/plugins/form-builder/client/index.d.ts +381 -0
- package/dist/plugins/form-builder/client/index.mjs +2 -0
- package/dist/plugins/form-builder/client.css +3 -0
- package/dist/plugins/form-builder/query-keys.cjs +143 -0
- package/dist/plugins/form-builder/query-keys.d.cts +74 -0
- package/dist/plugins/form-builder/query-keys.d.mts +74 -0
- package/dist/plugins/form-builder/query-keys.d.ts +74 -0
- package/dist/plugins/form-builder/query-keys.mjs +141 -0
- package/dist/plugins/form-builder/style.css +19 -0
- package/dist/plugins/open-api/api/index.cjs +9 -0
- package/dist/plugins/open-api/api/index.d.cts +95 -0
- package/dist/plugins/open-api/api/index.d.mts +95 -0
- package/dist/plugins/open-api/api/index.d.ts +95 -0
- package/dist/plugins/open-api/api/index.mjs +2 -0
- package/dist/shared/stack.AX5nZ6A3.d.cts +86 -0
- package/dist/shared/stack.AX5nZ6A3.d.mts +86 -0
- package/dist/shared/stack.AX5nZ6A3.d.ts +86 -0
- package/dist/shared/stack.BIh2AXaW.d.cts +123 -0
- package/dist/shared/stack.BIh2AXaW.d.mts +123 -0
- package/dist/shared/stack.BIh2AXaW.d.ts +123 -0
- package/dist/shared/{stack.ByOugz9d.d.cts → stack.CSce37mX.d.cts} +15 -2
- package/dist/shared/{stack.ByOugz9d.d.mts → stack.CSce37mX.d.mts} +15 -2
- package/dist/shared/{stack.ByOugz9d.d.ts → stack.CSce37mX.d.ts} +15 -2
- package/dist/shared/stack.DzH_wcvr.d.cts +195 -0
- package/dist/shared/stack.DzH_wcvr.d.mts +195 -0
- package/dist/shared/stack.DzH_wcvr.d.ts +195 -0
- package/package.json +67 -1
- package/src/api/index.ts +14 -2
- package/src/plugins/cms/api/plugin.ts +9 -4
- package/src/plugins/cms/client/components/forms/content-form.tsx +23 -25
- package/src/plugins/cms/client/index.ts +11 -0
- package/src/plugins/form-builder/api/index.ts +1 -0
- package/src/plugins/form-builder/api/plugin.ts +776 -0
- package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +253 -0
- package/src/plugins/form-builder/client/components/index.tsx +24 -0
- package/src/plugins/form-builder/client/components/loading/form-builder-skeleton.tsx +42 -0
- package/src/plugins/form-builder/client/components/loading/form-list-skeleton.tsx +25 -0
- package/src/plugins/form-builder/client/components/loading/index.tsx +3 -0
- package/src/plugins/form-builder/client/components/loading/submissions-skeleton.tsx +40 -0
- package/src/plugins/form-builder/client/components/pages/404-page.tsx +28 -0
- package/src/plugins/form-builder/client/components/pages/form-builder-page.internal.tsx +253 -0
- package/src/plugins/form-builder/client/components/pages/form-builder-page.tsx +26 -0
- package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +231 -0
- package/src/plugins/form-builder/client/components/pages/form-list-page.tsx +22 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +268 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.tsx +26 -0
- package/src/plugins/form-builder/client/components/shared/default-error.tsx +30 -0
- package/src/plugins/form-builder/client/components/shared/empty-state.tsx +26 -0
- package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +32 -0
- package/src/plugins/form-builder/client/components/shared/pagination.tsx +52 -0
- package/src/plugins/form-builder/client/hooks/form-builder-hooks.tsx +799 -0
- package/src/plugins/form-builder/client/hooks/index.tsx +1 -0
- package/src/plugins/form-builder/client/index.ts +22 -0
- package/src/plugins/form-builder/client/localization/form-builder-common.ts +36 -0
- package/src/plugins/form-builder/client/localization/form-builder-editor.ts +18 -0
- package/src/plugins/form-builder/client/localization/form-builder-list.ts +17 -0
- package/src/plugins/form-builder/client/localization/form-builder-submissions.ts +17 -0
- package/src/plugins/form-builder/client/localization/form-builder-toasts.ts +10 -0
- package/src/plugins/form-builder/client/localization/index.ts +15 -0
- package/src/plugins/form-builder/client/overrides.ts +146 -0
- package/src/plugins/form-builder/client/plugin.tsx +488 -0
- package/src/plugins/form-builder/client.css +3 -0
- package/src/plugins/form-builder/db.ts +99 -0
- package/src/plugins/form-builder/query-keys.ts +198 -0
- package/src/plugins/form-builder/schemas.ts +122 -0
- package/src/plugins/form-builder/style.css +19 -0
- package/src/plugins/form-builder/types.ts +317 -0
- package/src/plugins/form-builder/utils.ts +63 -0
- package/src/plugins/open-api/api/generator.ts +433 -0
- package/src/plugins/open-api/api/index.ts +8 -0
- package/src/plugins/open-api/api/plugin.ts +243 -0
- package/src/plugins/open-api/db.ts +7 -0
- package/src/plugins/open-api/logo.ts +7 -0
- package/src/types.ts +15 -1
- package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.cts} +1 -1
- package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.mts} +1 -1
- package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.ts} +1 -1
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
// NO "use client" here! This file runs on both server and client.
|
|
2
|
+
import { lazy } from "react";
|
|
3
|
+
import {
|
|
4
|
+
defineClientPlugin,
|
|
5
|
+
createApiClient,
|
|
6
|
+
} from "@btst/stack/plugins/client";
|
|
7
|
+
import { createRoute } from "@btst/yar";
|
|
8
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
9
|
+
import type { FormBuilderApiRouter } from "../api";
|
|
10
|
+
import { createFormBuilderQueryKeys } from "../query-keys";
|
|
11
|
+
|
|
12
|
+
// Lazy load page components for code splitting
|
|
13
|
+
const FormListPageComponent = lazy(() =>
|
|
14
|
+
import("./components/pages/form-list-page").then((m) => ({
|
|
15
|
+
default: m.FormListPageComponent,
|
|
16
|
+
})),
|
|
17
|
+
);
|
|
18
|
+
const FormBuilderPageComponent = lazy(() =>
|
|
19
|
+
import("./components/pages/form-builder-page").then((m) => ({
|
|
20
|
+
default: m.FormBuilderPageComponent,
|
|
21
|
+
})),
|
|
22
|
+
);
|
|
23
|
+
const SubmissionsPageComponent = lazy(() =>
|
|
24
|
+
import("./components/pages/submissions-page").then((m) => ({
|
|
25
|
+
default: m.SubmissionsPageComponent,
|
|
26
|
+
})),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Context passed to loader hooks
|
|
31
|
+
*/
|
|
32
|
+
export interface LoaderContext {
|
|
33
|
+
/** Current route path */
|
|
34
|
+
path: string;
|
|
35
|
+
/** Route parameters (e.g., { id: "123" }) */
|
|
36
|
+
params?: Record<string, string>;
|
|
37
|
+
/** Whether rendering on server (true) or client (false) */
|
|
38
|
+
isSSR: boolean;
|
|
39
|
+
/** Base URL for API calls */
|
|
40
|
+
apiBaseURL: string;
|
|
41
|
+
/** Path where the API is mounted */
|
|
42
|
+
apiBasePath: string;
|
|
43
|
+
/** Optional headers for the request */
|
|
44
|
+
headers?: Headers;
|
|
45
|
+
/** Additional context properties */
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hooks for Form Builder client plugin
|
|
51
|
+
* All hooks are optional and allow consumers to customize behavior
|
|
52
|
+
*/
|
|
53
|
+
export interface FormBuilderClientHooks {
|
|
54
|
+
/**
|
|
55
|
+
* Called before loading the form list page. Return false to cancel loading.
|
|
56
|
+
* @param context - Loader context with path, params, etc.
|
|
57
|
+
*/
|
|
58
|
+
beforeLoadFormList?: (context: LoaderContext) => Promise<boolean> | boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Called after the form list is loaded.
|
|
61
|
+
* @param context - Loader context
|
|
62
|
+
*/
|
|
63
|
+
afterLoadFormList?: (context: LoaderContext) => Promise<void> | void;
|
|
64
|
+
/**
|
|
65
|
+
* Called before loading the form builder page. Return false to cancel loading.
|
|
66
|
+
* @param id - The form ID (undefined for new forms)
|
|
67
|
+
* @param context - Loader context
|
|
68
|
+
*/
|
|
69
|
+
beforeLoadFormBuilder?: (
|
|
70
|
+
id: string | undefined,
|
|
71
|
+
context: LoaderContext,
|
|
72
|
+
) => Promise<boolean> | boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Called after the form builder is loaded.
|
|
75
|
+
* @param id - The form ID (undefined for new forms)
|
|
76
|
+
* @param context - Loader context
|
|
77
|
+
*/
|
|
78
|
+
afterLoadFormBuilder?: (
|
|
79
|
+
id: string | undefined,
|
|
80
|
+
context: LoaderContext,
|
|
81
|
+
) => Promise<void> | void;
|
|
82
|
+
/**
|
|
83
|
+
* Called before loading the submissions page. Return false to cancel loading.
|
|
84
|
+
* @param formId - The form ID
|
|
85
|
+
* @param context - Loader context
|
|
86
|
+
*/
|
|
87
|
+
beforeLoadSubmissions?: (
|
|
88
|
+
formId: string,
|
|
89
|
+
context: LoaderContext,
|
|
90
|
+
) => Promise<boolean> | boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Called after the submissions page is loaded.
|
|
93
|
+
* @param formId - The form ID
|
|
94
|
+
* @param context - Loader context
|
|
95
|
+
*/
|
|
96
|
+
afterLoadSubmissions?: (
|
|
97
|
+
formId: string,
|
|
98
|
+
context: LoaderContext,
|
|
99
|
+
) => Promise<void> | void;
|
|
100
|
+
/**
|
|
101
|
+
* Called when a loading error occurs.
|
|
102
|
+
* Use this for redirects on authorization failures.
|
|
103
|
+
* @param error - The error that occurred
|
|
104
|
+
* @param context - Loader context
|
|
105
|
+
*/
|
|
106
|
+
onLoadError?: (error: Error, context: LoaderContext) => Promise<void> | void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Configuration for Form Builder client plugin
|
|
111
|
+
*/
|
|
112
|
+
export interface FormBuilderClientConfig {
|
|
113
|
+
/** Base URL for API calls (e.g., "http://localhost:3000") */
|
|
114
|
+
apiBaseURL: string;
|
|
115
|
+
/** Path where the API is mounted (e.g., "/api/data") */
|
|
116
|
+
apiBasePath: string;
|
|
117
|
+
/** Base URL of your site */
|
|
118
|
+
siteBaseURL: string;
|
|
119
|
+
/** Path where pages are mounted (e.g., "/pages") */
|
|
120
|
+
siteBasePath: string;
|
|
121
|
+
/** React Query client instance for caching */
|
|
122
|
+
queryClient: QueryClient;
|
|
123
|
+
/** Optional headers for SSR (e.g., forwarding cookies) */
|
|
124
|
+
headers?: Headers;
|
|
125
|
+
/** Optional hooks for customizing behavior (authorization, redirects, etc.) */
|
|
126
|
+
hooks?: FormBuilderClientHooks;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create form list loader for SSR
|
|
131
|
+
*/
|
|
132
|
+
function createFormListLoader(config: FormBuilderClientConfig) {
|
|
133
|
+
return async () => {
|
|
134
|
+
if (typeof window === "undefined") {
|
|
135
|
+
const { queryClient, apiBasePath, apiBaseURL, headers, hooks } = config;
|
|
136
|
+
|
|
137
|
+
const context: LoaderContext = {
|
|
138
|
+
path: "/forms",
|
|
139
|
+
isSSR: true,
|
|
140
|
+
apiBaseURL,
|
|
141
|
+
apiBasePath,
|
|
142
|
+
headers,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Before hook - authorization check
|
|
147
|
+
if (hooks?.beforeLoadFormList) {
|
|
148
|
+
const canLoad = await hooks.beforeLoadFormList(context);
|
|
149
|
+
if (!canLoad) {
|
|
150
|
+
throw new Error("Load prevented by beforeLoadFormList hook");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
155
|
+
baseURL: apiBaseURL,
|
|
156
|
+
basePath: apiBasePath,
|
|
157
|
+
});
|
|
158
|
+
const queries = createFormBuilderQueryKeys(client, headers);
|
|
159
|
+
const limit = 20;
|
|
160
|
+
|
|
161
|
+
// Prefetch forms using infinite query
|
|
162
|
+
const listQuery = queries.forms.list({ limit, offset: 0 });
|
|
163
|
+
await queryClient.prefetchInfiniteQuery({
|
|
164
|
+
queryKey: listQuery.queryKey,
|
|
165
|
+
queryFn: async ({ pageParam = 0 }) => {
|
|
166
|
+
const response: unknown = await client("/forms", {
|
|
167
|
+
method: "GET",
|
|
168
|
+
query: { limit, offset: pageParam },
|
|
169
|
+
headers,
|
|
170
|
+
});
|
|
171
|
+
if (
|
|
172
|
+
typeof response === "object" &&
|
|
173
|
+
response !== null &&
|
|
174
|
+
"error" in response &&
|
|
175
|
+
response.error
|
|
176
|
+
) {
|
|
177
|
+
throw new Error(String(response.error));
|
|
178
|
+
}
|
|
179
|
+
return (response as { data?: unknown }).data;
|
|
180
|
+
},
|
|
181
|
+
initialPageParam: 0,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// After hook
|
|
185
|
+
if (hooks?.afterLoadFormList) {
|
|
186
|
+
await hooks.afterLoadFormList(context);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check if there was an error
|
|
190
|
+
const queryState = queryClient.getQueryState(listQuery.queryKey);
|
|
191
|
+
if (queryState?.error && hooks?.onLoadError) {
|
|
192
|
+
const error =
|
|
193
|
+
queryState.error instanceof Error
|
|
194
|
+
? queryState.error
|
|
195
|
+
: new Error(String(queryState.error));
|
|
196
|
+
await hooks.onLoadError(error, context);
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Error hook - log the error but don't throw during SSR
|
|
200
|
+
if (hooks?.onLoadError) {
|
|
201
|
+
await hooks.onLoadError(error as Error, context);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Create form builder loader for SSR
|
|
210
|
+
*/
|
|
211
|
+
function createFormBuilderLoader(
|
|
212
|
+
id: string | undefined,
|
|
213
|
+
config: FormBuilderClientConfig,
|
|
214
|
+
) {
|
|
215
|
+
return async () => {
|
|
216
|
+
if (typeof window === "undefined") {
|
|
217
|
+
const { queryClient, apiBasePath, apiBaseURL, headers, hooks } = config;
|
|
218
|
+
|
|
219
|
+
const context: LoaderContext = {
|
|
220
|
+
path: id ? `/forms/${id}/edit` : "/forms/new",
|
|
221
|
+
params: id ? { id } : {},
|
|
222
|
+
isSSR: true,
|
|
223
|
+
apiBaseURL,
|
|
224
|
+
apiBasePath,
|
|
225
|
+
headers,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
// Before hook - authorization check
|
|
230
|
+
if (hooks?.beforeLoadFormBuilder) {
|
|
231
|
+
const canLoad = await hooks.beforeLoadFormBuilder(id, context);
|
|
232
|
+
if (!canLoad) {
|
|
233
|
+
throw new Error("Load prevented by beforeLoadFormBuilder hook");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
238
|
+
baseURL: apiBaseURL,
|
|
239
|
+
basePath: apiBasePath,
|
|
240
|
+
});
|
|
241
|
+
const queries = createFormBuilderQueryKeys(client, headers);
|
|
242
|
+
|
|
243
|
+
// Prefetch form if editing
|
|
244
|
+
if (id) {
|
|
245
|
+
await queryClient.prefetchQuery(queries.forms.byId(id));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// After hook
|
|
249
|
+
if (hooks?.afterLoadFormBuilder) {
|
|
250
|
+
await hooks.afterLoadFormBuilder(id, context);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check if there was an error
|
|
254
|
+
if (id) {
|
|
255
|
+
const queryState = queryClient.getQueryState(
|
|
256
|
+
queries.forms.byId(id).queryKey,
|
|
257
|
+
);
|
|
258
|
+
if (queryState?.error && hooks?.onLoadError) {
|
|
259
|
+
const error =
|
|
260
|
+
queryState.error instanceof Error
|
|
261
|
+
? queryState.error
|
|
262
|
+
: new Error(String(queryState.error));
|
|
263
|
+
await hooks.onLoadError(error, context);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
// Error hook - log the error but don't throw during SSR
|
|
268
|
+
if (hooks?.onLoadError) {
|
|
269
|
+
await hooks.onLoadError(error as Error, context);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Create submissions loader for SSR
|
|
278
|
+
*/
|
|
279
|
+
function createSubmissionsLoader(
|
|
280
|
+
formId: string,
|
|
281
|
+
config: FormBuilderClientConfig,
|
|
282
|
+
) {
|
|
283
|
+
return async () => {
|
|
284
|
+
if (typeof window === "undefined") {
|
|
285
|
+
const { queryClient, apiBasePath, apiBaseURL, headers, hooks } = config;
|
|
286
|
+
|
|
287
|
+
const context: LoaderContext = {
|
|
288
|
+
path: `/forms/${formId}/submissions`,
|
|
289
|
+
params: { formId },
|
|
290
|
+
isSSR: true,
|
|
291
|
+
apiBaseURL,
|
|
292
|
+
apiBasePath,
|
|
293
|
+
headers,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
// Before hook - authorization check
|
|
298
|
+
if (hooks?.beforeLoadSubmissions) {
|
|
299
|
+
const canLoad = await hooks.beforeLoadSubmissions(formId, context);
|
|
300
|
+
if (!canLoad) {
|
|
301
|
+
throw new Error("Load prevented by beforeLoadSubmissions hook");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
306
|
+
baseURL: apiBaseURL,
|
|
307
|
+
basePath: apiBasePath,
|
|
308
|
+
});
|
|
309
|
+
const queries = createFormBuilderQueryKeys(client, headers);
|
|
310
|
+
const limit = 20;
|
|
311
|
+
|
|
312
|
+
// Prefetch form and submissions
|
|
313
|
+
await queryClient.prefetchQuery(queries.forms.byId(formId));
|
|
314
|
+
|
|
315
|
+
const submissionsQuery = queries.formSubmissions.list({
|
|
316
|
+
formId,
|
|
317
|
+
limit,
|
|
318
|
+
offset: 0,
|
|
319
|
+
});
|
|
320
|
+
await queryClient.prefetchInfiniteQuery({
|
|
321
|
+
queryKey: submissionsQuery.queryKey,
|
|
322
|
+
queryFn: async ({ pageParam = 0 }) => {
|
|
323
|
+
const response: unknown = await client(
|
|
324
|
+
"/forms/:formId/submissions",
|
|
325
|
+
{
|
|
326
|
+
method: "GET",
|
|
327
|
+
params: { formId },
|
|
328
|
+
query: { limit, offset: pageParam },
|
|
329
|
+
headers,
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
if (
|
|
333
|
+
typeof response === "object" &&
|
|
334
|
+
response !== null &&
|
|
335
|
+
"error" in response &&
|
|
336
|
+
response.error
|
|
337
|
+
) {
|
|
338
|
+
throw new Error(String(response.error));
|
|
339
|
+
}
|
|
340
|
+
return (response as { data?: unknown }).data;
|
|
341
|
+
},
|
|
342
|
+
initialPageParam: 0,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// After hook
|
|
346
|
+
if (hooks?.afterLoadSubmissions) {
|
|
347
|
+
await hooks.afterLoadSubmissions(formId, context);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Check if there was an error
|
|
351
|
+
const formState = queryClient.getQueryState(
|
|
352
|
+
queries.forms.byId(formId).queryKey,
|
|
353
|
+
);
|
|
354
|
+
const submissionsState = queryClient.getQueryState(
|
|
355
|
+
submissionsQuery.queryKey,
|
|
356
|
+
);
|
|
357
|
+
const queryError = formState?.error || submissionsState?.error;
|
|
358
|
+
if (queryError && hooks?.onLoadError) {
|
|
359
|
+
const error =
|
|
360
|
+
queryError instanceof Error
|
|
361
|
+
? queryError
|
|
362
|
+
: new Error(String(queryError));
|
|
363
|
+
await hooks.onLoadError(error, context);
|
|
364
|
+
}
|
|
365
|
+
} catch (error) {
|
|
366
|
+
// Error hook - log the error but don't throw during SSR
|
|
367
|
+
if (hooks?.onLoadError) {
|
|
368
|
+
await hooks.onLoadError(error as Error, context);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Create form list meta generator
|
|
377
|
+
*/
|
|
378
|
+
function createFormListMeta() {
|
|
379
|
+
return () => {
|
|
380
|
+
const title = "Forms";
|
|
381
|
+
return [
|
|
382
|
+
{ title },
|
|
383
|
+
{ name: "title", content: title },
|
|
384
|
+
{ name: "robots", content: "noindex" },
|
|
385
|
+
];
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Create form builder meta generator
|
|
391
|
+
*/
|
|
392
|
+
function createFormBuilderMeta(
|
|
393
|
+
id: string | undefined,
|
|
394
|
+
config: FormBuilderClientConfig,
|
|
395
|
+
) {
|
|
396
|
+
return () => {
|
|
397
|
+
const { queryClient, apiBasePath, apiBaseURL } = config;
|
|
398
|
+
|
|
399
|
+
let formName = "";
|
|
400
|
+
if (id) {
|
|
401
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
402
|
+
baseURL: apiBaseURL,
|
|
403
|
+
basePath: apiBasePath,
|
|
404
|
+
});
|
|
405
|
+
const queries = createFormBuilderQueryKeys(client);
|
|
406
|
+
const form = queryClient.getQueryData(queries.forms.byId(id).queryKey) as
|
|
407
|
+
| { name: string }
|
|
408
|
+
| undefined;
|
|
409
|
+
formName = form?.name || "";
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const title = id ? `Edit ${formName || "Form"}` : "New Form";
|
|
413
|
+
|
|
414
|
+
return [
|
|
415
|
+
{ title },
|
|
416
|
+
{ name: "title", content: title },
|
|
417
|
+
{ name: "robots", content: "noindex" },
|
|
418
|
+
];
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Create submissions meta generator
|
|
424
|
+
*/
|
|
425
|
+
function createSubmissionsMeta(
|
|
426
|
+
formId: string,
|
|
427
|
+
config: FormBuilderClientConfig,
|
|
428
|
+
) {
|
|
429
|
+
return () => {
|
|
430
|
+
const { queryClient, apiBasePath, apiBaseURL } = config;
|
|
431
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
432
|
+
baseURL: apiBaseURL,
|
|
433
|
+
basePath: apiBasePath,
|
|
434
|
+
});
|
|
435
|
+
const queries = createFormBuilderQueryKeys(client);
|
|
436
|
+
const form = queryClient.getQueryData(
|
|
437
|
+
queries.forms.byId(formId).queryKey,
|
|
438
|
+
) as { name: string } | undefined;
|
|
439
|
+
|
|
440
|
+
const title = form?.name ? `${form.name} Submissions` : "Submissions";
|
|
441
|
+
|
|
442
|
+
return [
|
|
443
|
+
{ title },
|
|
444
|
+
{ name: "title", content: title },
|
|
445
|
+
{ name: "robots", content: "noindex" },
|
|
446
|
+
];
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Form Builder client plugin
|
|
452
|
+
* Provides routes and components for the Form Builder admin interface
|
|
453
|
+
*/
|
|
454
|
+
export const formBuilderClientPlugin = (config: FormBuilderClientConfig) =>
|
|
455
|
+
defineClientPlugin({
|
|
456
|
+
name: "form-builder",
|
|
457
|
+
|
|
458
|
+
routes: () => ({
|
|
459
|
+
formList: createRoute("/forms", () => ({
|
|
460
|
+
PageComponent: () => <FormListPageComponent />,
|
|
461
|
+
loader: createFormListLoader(config),
|
|
462
|
+
meta: createFormListMeta(),
|
|
463
|
+
})),
|
|
464
|
+
|
|
465
|
+
newForm: createRoute("/forms/new", () => ({
|
|
466
|
+
PageComponent: () => <FormBuilderPageComponent />,
|
|
467
|
+
loader: createFormBuilderLoader(undefined, config),
|
|
468
|
+
meta: createFormBuilderMeta(undefined, config),
|
|
469
|
+
})),
|
|
470
|
+
|
|
471
|
+
editForm: createRoute("/forms/:id/edit", ({ params }) => ({
|
|
472
|
+
PageComponent: () => <FormBuilderPageComponent id={params.id} />,
|
|
473
|
+
loader: createFormBuilderLoader(params.id, config),
|
|
474
|
+
meta: createFormBuilderMeta(params.id, config),
|
|
475
|
+
})),
|
|
476
|
+
|
|
477
|
+
submissions: createRoute("/forms/:id/submissions", ({ params }) => ({
|
|
478
|
+
PageComponent: () => <SubmissionsPageComponent formId={params.id} />,
|
|
479
|
+
loader: createSubmissionsLoader(params.id, config),
|
|
480
|
+
meta: createSubmissionsMeta(params.id, config),
|
|
481
|
+
})),
|
|
482
|
+
}),
|
|
483
|
+
|
|
484
|
+
sitemap: async () => {
|
|
485
|
+
// Form Builder admin pages should NOT be in sitemap
|
|
486
|
+
return [];
|
|
487
|
+
},
|
|
488
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createDbPlugin } from "@btst/db";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Form Builder plugin schema
|
|
5
|
+
* Defines the database tables for forms and form submissions
|
|
6
|
+
*/
|
|
7
|
+
export const formBuilderSchema = createDbPlugin("form-builder", {
|
|
8
|
+
form: {
|
|
9
|
+
modelName: "form",
|
|
10
|
+
fields: {
|
|
11
|
+
name: {
|
|
12
|
+
type: "string",
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
slug: {
|
|
16
|
+
type: "string",
|
|
17
|
+
required: true,
|
|
18
|
+
unique: true,
|
|
19
|
+
},
|
|
20
|
+
description: {
|
|
21
|
+
type: "string",
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
24
|
+
// JSON Schema stored as string (includes steps, fieldType, stepGroup, etc.)
|
|
25
|
+
schema: {
|
|
26
|
+
type: "string",
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
// Optional custom success message after submission
|
|
30
|
+
successMessage: {
|
|
31
|
+
type: "string",
|
|
32
|
+
required: false,
|
|
33
|
+
},
|
|
34
|
+
// Optional redirect URL after submission
|
|
35
|
+
redirectUrl: {
|
|
36
|
+
type: "string",
|
|
37
|
+
required: false,
|
|
38
|
+
},
|
|
39
|
+
// Form status: active, inactive, archived
|
|
40
|
+
status: {
|
|
41
|
+
type: "string",
|
|
42
|
+
defaultValue: "active",
|
|
43
|
+
},
|
|
44
|
+
// User who created the form
|
|
45
|
+
createdBy: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: false,
|
|
48
|
+
},
|
|
49
|
+
createdAt: {
|
|
50
|
+
type: "date",
|
|
51
|
+
defaultValue: () => new Date(),
|
|
52
|
+
},
|
|
53
|
+
updatedAt: {
|
|
54
|
+
type: "date",
|
|
55
|
+
defaultValue: () => new Date(),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
formSubmission: {
|
|
60
|
+
modelName: "formSubmission",
|
|
61
|
+
fields: {
|
|
62
|
+
formId: {
|
|
63
|
+
type: "string",
|
|
64
|
+
required: true,
|
|
65
|
+
// Database reference for efficient joins
|
|
66
|
+
references: {
|
|
67
|
+
model: "form",
|
|
68
|
+
field: "id",
|
|
69
|
+
onDelete: "cascade",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
// Submitted data as JSON string
|
|
73
|
+
data: {
|
|
74
|
+
type: "string",
|
|
75
|
+
required: true,
|
|
76
|
+
},
|
|
77
|
+
// Submission timestamp
|
|
78
|
+
submittedAt: {
|
|
79
|
+
type: "date",
|
|
80
|
+
defaultValue: () => new Date(),
|
|
81
|
+
},
|
|
82
|
+
// Optional user ID if authenticated
|
|
83
|
+
submittedBy: {
|
|
84
|
+
type: "string",
|
|
85
|
+
required: false,
|
|
86
|
+
},
|
|
87
|
+
// IP address for rate limiting and spam protection
|
|
88
|
+
ipAddress: {
|
|
89
|
+
type: "string",
|
|
90
|
+
required: false,
|
|
91
|
+
},
|
|
92
|
+
// User agent for analytics
|
|
93
|
+
userAgent: {
|
|
94
|
+
type: "string",
|
|
95
|
+
required: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|