diff --git a/apps/web/.source/browser.ts b/apps/web/.source/browser.ts index c11137b..b4b97c4 100644 --- a/apps/web/.source/browser.ts +++ b/apps/web/.source/browser.ts @@ -7,6 +7,6 @@ const create = browser(); const browserCollections = { - docs: create.doc("docs", {"conditional-logic.mdx": () => import("../content/docs/conditional-logic.mdx?collection=docs"), "configuration.mdx": () => import("../content/docs/configuration.mdx?collection=docs"), "custom-rendering.mdx": () => import("../content/docs/custom-rendering.mdx?collection=docs"), "form-component.mdx": () => import("../content/docs/form-component.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "installation.mdx": () => import("../content/docs/installation.mdx?collection=docs"), "quick-start.mdx": () => import("../content/docs/quick-start.mdx?collection=docs"), "schema.mdx": () => import("../content/docs/schema.mdx?collection=docs"), "validation.mdx": () => import("../content/docs/validation.mdx?collection=docs"), "your-first-form.mdx": () => import("../content/docs/your-first-form.mdx?collection=docs"), "fields/types.mdx": () => import("../content/docs/fields/types.mdx?collection=docs"), "fields/data/checkbox-group.mdx": () => import("../content/docs/fields/data/checkbox-group.mdx?collection=docs"), "fields/data/checkbox.mdx": () => import("../content/docs/fields/data/checkbox.mdx?collection=docs"), "fields/data/date.mdx": () => import("../content/docs/fields/data/date.mdx?collection=docs"), "fields/data/number.mdx": () => import("../content/docs/fields/data/number.mdx?collection=docs"), "fields/data/password.mdx": () => import("../content/docs/fields/data/password.mdx?collection=docs"), "fields/data/radio.mdx": () => import("../content/docs/fields/data/radio.mdx?collection=docs"), "fields/data/select.mdx": () => import("../content/docs/fields/data/select.mdx?collection=docs"), "fields/data/switch.mdx": () => import("../content/docs/fields/data/switch.mdx?collection=docs"), "fields/data/tags.mdx": () => import("../content/docs/fields/data/tags.mdx?collection=docs"), "fields/data/text.mdx": () => import("../content/docs/fields/data/text.mdx?collection=docs"), "fields/data/textarea.mdx": () => import("../content/docs/fields/data/textarea.mdx?collection=docs"), "fields/data/upload.mdx": () => import("../content/docs/fields/data/upload.mdx?collection=docs"), "fields/layout/array.mdx": () => import("../content/docs/fields/layout/array.mdx?collection=docs"), "fields/layout/collapsible.mdx": () => import("../content/docs/fields/layout/collapsible.mdx?collection=docs"), "fields/layout/group.mdx": () => import("../content/docs/fields/layout/group.mdx?collection=docs"), "fields/layout/row.mdx": () => import("../content/docs/fields/layout/row.mdx?collection=docs"), "fields/layout/tabs.mdx": () => import("../content/docs/fields/layout/tabs.mdx?collection=docs"), }), + docs: create.doc("docs", {"conditional-logic.mdx": () => import("../content/docs/conditional-logic.mdx?collection=docs"), "configuration.mdx": () => import("../content/docs/configuration.mdx?collection=docs"), "custom-rendering.mdx": () => import("../content/docs/custom-rendering.mdx?collection=docs"), "form-component.mdx": () => import("../content/docs/form-component.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "installation.mdx": () => import("../content/docs/installation.mdx?collection=docs"), "quick-start.mdx": () => import("../content/docs/quick-start.mdx?collection=docs"), "schema.mdx": () => import("../content/docs/schema.mdx?collection=docs"), "validation.mdx": () => import("../content/docs/validation.mdx?collection=docs"), "your-first-form.mdx": () => import("../content/docs/your-first-form.mdx?collection=docs"), "fields/types.mdx": () => import("../content/docs/fields/types.mdx?collection=docs"), "fields/layout/array.mdx": () => import("../content/docs/fields/layout/array.mdx?collection=docs"), "fields/layout/collapsible.mdx": () => import("../content/docs/fields/layout/collapsible.mdx?collection=docs"), "fields/layout/group.mdx": () => import("../content/docs/fields/layout/group.mdx?collection=docs"), "fields/layout/row.mdx": () => import("../content/docs/fields/layout/row.mdx?collection=docs"), "fields/layout/tabs.mdx": () => import("../content/docs/fields/layout/tabs.mdx?collection=docs"), "fields/data/checkbox-group.mdx": () => import("../content/docs/fields/data/checkbox-group.mdx?collection=docs"), "fields/data/checkbox.mdx": () => import("../content/docs/fields/data/checkbox.mdx?collection=docs"), "fields/data/date.mdx": () => import("../content/docs/fields/data/date.mdx?collection=docs"), "fields/data/number.mdx": () => import("../content/docs/fields/data/number.mdx?collection=docs"), "fields/data/password.mdx": () => import("../content/docs/fields/data/password.mdx?collection=docs"), "fields/data/radio.mdx": () => import("../content/docs/fields/data/radio.mdx?collection=docs"), "fields/data/select.mdx": () => import("../content/docs/fields/data/select.mdx?collection=docs"), "fields/data/switch.mdx": () => import("../content/docs/fields/data/switch.mdx?collection=docs"), "fields/data/tags.mdx": () => import("../content/docs/fields/data/tags.mdx?collection=docs"), "fields/data/text.mdx": () => import("../content/docs/fields/data/text.mdx?collection=docs"), "fields/data/textarea.mdx": () => import("../content/docs/fields/data/textarea.mdx?collection=docs"), "fields/data/upload.mdx": () => import("../content/docs/fields/data/upload.mdx?collection=docs"), }), }; export default browserCollections; \ No newline at end of file diff --git a/apps/web/.source/server.ts b/apps/web/.source/server.ts index a158859..2fd4dd8 100644 --- a/apps/web/.source/server.ts +++ b/apps/web/.source/server.ts @@ -1,21 +1,21 @@ // @ts-nocheck -import * as __fd_glob_31 from "../content/docs/fields/layout/tabs.mdx?collection=docs" -import * as __fd_glob_30 from "../content/docs/fields/layout/row.mdx?collection=docs" -import * as __fd_glob_29 from "../content/docs/fields/layout/group.mdx?collection=docs" -import * as __fd_glob_28 from "../content/docs/fields/layout/collapsible.mdx?collection=docs" -import * as __fd_glob_27 from "../content/docs/fields/layout/array.mdx?collection=docs" -import * as __fd_glob_26 from "../content/docs/fields/data/upload.mdx?collection=docs" -import * as __fd_glob_25 from "../content/docs/fields/data/textarea.mdx?collection=docs" -import * as __fd_glob_24 from "../content/docs/fields/data/text.mdx?collection=docs" -import * as __fd_glob_23 from "../content/docs/fields/data/tags.mdx?collection=docs" -import * as __fd_glob_22 from "../content/docs/fields/data/switch.mdx?collection=docs" -import * as __fd_glob_21 from "../content/docs/fields/data/select.mdx?collection=docs" -import * as __fd_glob_20 from "../content/docs/fields/data/radio.mdx?collection=docs" -import * as __fd_glob_19 from "../content/docs/fields/data/password.mdx?collection=docs" -import * as __fd_glob_18 from "../content/docs/fields/data/number.mdx?collection=docs" -import * as __fd_glob_17 from "../content/docs/fields/data/date.mdx?collection=docs" -import * as __fd_glob_16 from "../content/docs/fields/data/checkbox.mdx?collection=docs" -import * as __fd_glob_15 from "../content/docs/fields/data/checkbox-group.mdx?collection=docs" +import * as __fd_glob_31 from "../content/docs/fields/data/upload.mdx?collection=docs" +import * as __fd_glob_30 from "../content/docs/fields/data/textarea.mdx?collection=docs" +import * as __fd_glob_29 from "../content/docs/fields/data/text.mdx?collection=docs" +import * as __fd_glob_28 from "../content/docs/fields/data/tags.mdx?collection=docs" +import * as __fd_glob_27 from "../content/docs/fields/data/switch.mdx?collection=docs" +import * as __fd_glob_26 from "../content/docs/fields/data/select.mdx?collection=docs" +import * as __fd_glob_25 from "../content/docs/fields/data/radio.mdx?collection=docs" +import * as __fd_glob_24 from "../content/docs/fields/data/password.mdx?collection=docs" +import * as __fd_glob_23 from "../content/docs/fields/data/number.mdx?collection=docs" +import * as __fd_glob_22 from "../content/docs/fields/data/date.mdx?collection=docs" +import * as __fd_glob_21 from "../content/docs/fields/data/checkbox.mdx?collection=docs" +import * as __fd_glob_20 from "../content/docs/fields/data/checkbox-group.mdx?collection=docs" +import * as __fd_glob_19 from "../content/docs/fields/layout/tabs.mdx?collection=docs" +import * as __fd_glob_18 from "../content/docs/fields/layout/row.mdx?collection=docs" +import * as __fd_glob_17 from "../content/docs/fields/layout/group.mdx?collection=docs" +import * as __fd_glob_16 from "../content/docs/fields/layout/collapsible.mdx?collection=docs" +import * as __fd_glob_15 from "../content/docs/fields/layout/array.mdx?collection=docs" import * as __fd_glob_14 from "../content/docs/fields/types.mdx?collection=docs" import * as __fd_glob_13 from "../content/docs/your-first-form.mdx?collection=docs" import * as __fd_glob_12 from "../content/docs/validation.mdx?collection=docs" @@ -27,8 +27,8 @@ import * as __fd_glob_7 from "../content/docs/form-component.mdx?collection=docs import * as __fd_glob_6 from "../content/docs/custom-rendering.mdx?collection=docs" import * as __fd_glob_5 from "../content/docs/configuration.mdx?collection=docs" import * as __fd_glob_4 from "../content/docs/conditional-logic.mdx?collection=docs" -import { default as __fd_glob_3 } from "../content/docs/fields/data/meta.json?collection=docs" -import { default as __fd_glob_2 } from "../content/docs/fields/layout/meta.json?collection=docs" +import { default as __fd_glob_3 } from "../content/docs/fields/layout/meta.json?collection=docs" +import { default as __fd_glob_2 } from "../content/docs/fields/data/meta.json?collection=docs" import { default as __fd_glob_1 } from "../content/docs/fields/meta.json?collection=docs" import { default as __fd_glob_0 } from "../content/docs/meta.json?collection=docs" import { server } from 'fumadocs-mdx/runtime/server'; @@ -39,4 +39,4 @@ const create = server({"doc":{"passthroughs":["extractedReferences"]}}); -export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/layout/meta.json": __fd_glob_2, "fields/data/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/data/checkbox-group.mdx": __fd_glob_15, "fields/data/checkbox.mdx": __fd_glob_16, "fields/data/date.mdx": __fd_glob_17, "fields/data/number.mdx": __fd_glob_18, "fields/data/password.mdx": __fd_glob_19, "fields/data/radio.mdx": __fd_glob_20, "fields/data/select.mdx": __fd_glob_21, "fields/data/switch.mdx": __fd_glob_22, "fields/data/tags.mdx": __fd_glob_23, "fields/data/text.mdx": __fd_glob_24, "fields/data/textarea.mdx": __fd_glob_25, "fields/data/upload.mdx": __fd_glob_26, "fields/layout/array.mdx": __fd_glob_27, "fields/layout/collapsible.mdx": __fd_glob_28, "fields/layout/group.mdx": __fd_glob_29, "fields/layout/row.mdx": __fd_glob_30, "fields/layout/tabs.mdx": __fd_glob_31, }); \ No newline at end of file +export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/data/meta.json": __fd_glob_2, "fields/layout/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/layout/array.mdx": __fd_glob_15, "fields/layout/collapsible.mdx": __fd_glob_16, "fields/layout/group.mdx": __fd_glob_17, "fields/layout/row.mdx": __fd_glob_18, "fields/layout/tabs.mdx": __fd_glob_19, "fields/data/checkbox-group.mdx": __fd_glob_20, "fields/data/checkbox.mdx": __fd_glob_21, "fields/data/date.mdx": __fd_glob_22, "fields/data/number.mdx": __fd_glob_23, "fields/data/password.mdx": __fd_glob_24, "fields/data/radio.mdx": __fd_glob_25, "fields/data/select.mdx": __fd_glob_26, "fields/data/switch.mdx": __fd_glob_27, "fields/data/tags.mdx": __fd_glob_28, "fields/data/text.mdx": __fd_glob_29, "fields/data/textarea.mdx": __fd_glob_30, "fields/data/upload.mdx": __fd_glob_31, }); \ No newline at end of file diff --git a/apps/web/app/(builder)/components/export-sheet.tsx b/apps/web/app/(builder)/components/export-sheet.tsx index ae8df32..f357dee 100644 --- a/apps/web/app/(builder)/components/export-sheet.tsx +++ b/apps/web/app/(builder)/components/export-sheet.tsx @@ -24,6 +24,7 @@ import { import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { generateComponentCode } from "../lib/code-generator"; import { toBuilderDocument } from "../lib/persistence"; +import { nodesToFields } from "../lib/schema-builder"; import { downloadTextFile, toSafeFileName } from "../lib/utils"; import { useBuilderStore } from "../lib/store"; @@ -67,13 +68,26 @@ export function ExportSheet() { return generateComponentCode(nodes, rootIds, formName); }, [open, nodes, rootIds, formName]); + const schemaJson = React.useMemo(() => { + if (!open) return ""; + const fields = nodesToFields(nodes, rootIds); + return JSON.stringify(fields, null, 2); + }, [open, nodes, rootIds]); + const downloadJson = React.useCallback(() => { if (!documentJson) return; - const fileName = `${toSafeFileName(formName)}.json`; + const fileName = `${toSafeFileName(formName)}-doc.json`; downloadTextFile(documentJson, fileName, "application/json"); - toast.success("Builder backup exported"); + toast.success("Builder Document exported"); }, [documentJson, formName]); + const downloadSchema = React.useCallback(() => { + if (!schemaJson) return; + const fileName = `${toSafeFileName(formName)}.json`; + downloadTextFile(schemaJson, fileName, "application/json"); + toast.success("BuzzForm schema exported"); + }, [schemaJson, formName]); + const downloadCode = React.useCallback(() => { if (!componentCode) return; const fileName = `${toSafeFileName(formName)}.tsx`; @@ -99,8 +113,8 @@ export function ExportSheet() { Export Form - Export production-ready TSX for your app, or a BuzzForm file for - Builder import. + Export production-ready TSX, portable BuzzForm schema JSON, or a + Builder document file. @@ -116,7 +130,12 @@ export function ExportSheet() { - BuzzForm File + Builder Document + .json + + + + BuzzForm Schema .json @@ -172,11 +191,11 @@ export function ExportSheet() { strokeWidth={1.8} className="text-muted-foreground" /> - BuzzForm file + Builder document file

- Keep this as your editable BuzzForm source file to back up, - share, and continue work later in BuzzForm Builder. + Includes Builder metadata and node ids to fully restore your + workspace exactly as-is.

@@ -201,6 +220,50 @@ export function ExportSheet() { + + + +
+
+
+ + BuzzForm schema +
+

+ Pure schema JSON for copy/paste import and runtime form + generation. +

+
+ +
+
+ +
+ +
+
+
+
diff --git a/apps/web/app/(builder)/components/header/form-manager-dialog.tsx b/apps/web/app/(builder)/components/header/form-manager-dialog.tsx index 7921512..875f912 100644 --- a/apps/web/app/(builder)/components/header/form-manager-dialog.tsx +++ b/apps/web/app/(builder)/components/header/form-manager-dialog.tsx @@ -37,7 +37,7 @@ export function FormManagerDialog() { } /> - + Forms diff --git a/apps/web/app/(builder)/components/header/form-manager-new-panel.tsx b/apps/web/app/(builder)/components/header/form-manager-new-panel.tsx index d4e5174..d1fb48b 100644 --- a/apps/web/app/(builder)/components/header/form-manager-new-panel.tsx +++ b/apps/web/app/(builder)/components/header/form-manager-new-panel.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; import { Card, CardContent, @@ -11,25 +12,33 @@ import { CardTitle, } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; +import { Textarea } from "@/components/ui/textarea"; import { HugeiconsIcon } from "@hugeicons/react"; import { Add01Icon, Alert01Icon, + Copy01Icon, File02Icon, Upload01Icon, } from "@hugeicons/core-free-icons"; -import { - fromBuilderDocument, - parseBuilderDocumentJson, - type BuilderDocumentState, -} from "../../lib/persistence"; import { useBuilderStore } from "../../lib/store"; import { toast } from "sonner"; +import { + parseImportedFormJson, + type ImportPayloadFormat, +} from "../../lib/import-json"; +import type { BuilderDocumentState } from "../../lib/persistence"; const MIN_IMPORT_FEEDBACK_MS = 500; +const IMPORT_FORMAT_LABELS: Record = { + "builder-backup": "Builder Backup", + "buzzform-schema": "BuzzForm Schema", +}; + type PendingImport = { - fileName: string; + sourceName: string; + format: ImportPayloadFormat; formName: string; nodeCount: number; state: BuilderDocumentState; @@ -46,6 +55,17 @@ type FormManagerPendingImportStateProps = { onClearSelection: () => void; }; +type FormManagerPasteImportStateProps = { + value: string; + isImporting: boolean; + onChangeValue: (value: string) => void; + onBack: () => void; + onPasteFromClipboard: () => void; + onImport: () => void; + onDragOver: (event: React.DragEvent) => void; + onDrop: (event: React.DragEvent) => void; +}; + function FormManagerPendingImportState({ pendingImport, isImporting, @@ -59,13 +79,19 @@ function FormManagerPendingImportState({ -
+

{pendingImport.formName}

-

- {pendingImport.fileName} -

+

{pendingImport.sourceName}

+
+ + {IMPORT_FORMAT_LABELS[pendingImport.format]} + + + {pendingImport.nodeCount} nodes + +
@@ -84,7 +110,7 @@ function FormManagerPendingImportState({ -
+