@sanity/form-toolkit 1.1.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/index.mjs ADDED
@@ -0,0 +1,151 @@
1
+ import { asyncList } from "@sanity/sanity-plugin-async-list";
2
+ import { definePlugin } from "sanity";
3
+ import { jsxs, jsx } from "react/jsx-runtime";
4
+ import { Card, Text } from "@sanity/ui";
5
+ import { defineEventHandler } from "h3";
6
+ import mailchimp from "@mailchimp/mailchimp_marketing";
7
+ const Option$1 = (option) => /* @__PURE__ */ jsxs(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: [
8
+ /* @__PURE__ */ jsx(Text, { size: 2, textOverflow: "ellipsis", children: option.name }),
9
+ /* @__PURE__ */ jsx(Card, { paddingTop: 2, tone: "inherit", style: { background: "inherit" }, children: /* @__PURE__ */ jsx(Text, { size: 1, textOverflow: "ellipsis", children: `ID: ${option.value}` }) })
10
+ ] }), hubSpotInput = definePlugin((options) => ({
11
+ name: "sanity-plugin-form-toolkit_hubspot-input",
12
+ plugins: [
13
+ asyncList({
14
+ schemaType: "hubSpotForm",
15
+ loader: async () => await (await fetch(options.url)).json(),
16
+ autocompleteProps: {
17
+ renderOption: (option) => Option$1(option),
18
+ renderValue(value, option) {
19
+ return option?.name ?? value;
20
+ }
21
+ }
22
+ })
23
+ ]
24
+ })), detectFramework = () => {
25
+ if (process.env.NEXT_RUNTIME)
26
+ return "nextjs";
27
+ if (process.env.NUXT_ENV)
28
+ return "nuxt";
29
+ if (process.env.SVELTEKIT_ENV)
30
+ return "sveltekit";
31
+ if (process.env.REMIX_ENV)
32
+ return "remix";
33
+ if (process.env.ASTRO_ENV)
34
+ return "astro";
35
+ throw new Error("Unable to detect framework.");
36
+ }, createHandler = (handlerFunc) => {
37
+ const framework = detectFramework(), handlerLogic = async ({
38
+ // eslint-disable-next-line
39
+ req,
40
+ res
41
+ }) => {
42
+ try {
43
+ const data = await handlerFunc();
44
+ return res && (res.writeHead(200, { "Content-Type": "application/json" }), res.end(JSON.stringify(data))), data;
45
+ } catch (error) {
46
+ return error instanceof Error ? (res && (res.writeHead(500, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: error.message }))), { error: error.message }) : (res && (res.writeHead(500, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: "An unexpected error occurred" }))), { error: "An unexpected error occurred" });
47
+ }
48
+ };
49
+ if (framework === "nextjs")
50
+ return async (req, res) => {
51
+ res.setHeader("Access-Control-Allow-Origin", "*"), res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"), res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"), await handlerLogic({ req, res });
52
+ };
53
+ if (framework === "nuxt")
54
+ return defineEventHandler(async (event) => {
55
+ const req = event.node.req, res = event.node.res;
56
+ return handlerLogic({ req, res });
57
+ });
58
+ if (framework === "sveltekit")
59
+ return {
60
+ GET: async ({ request }) => {
61
+ const result = await handlerLogic({ req: request });
62
+ return new Response(JSON.stringify(result), {
63
+ headers: { "Content-Type": "application/json" }
64
+ });
65
+ }
66
+ };
67
+ if (framework === "remix")
68
+ return {
69
+ loader: async ({ request }) => {
70
+ const result = await handlerLogic({ req: request });
71
+ return new Response(JSON.stringify(result), {
72
+ headers: { "Content-Type": "application/json" }
73
+ });
74
+ }
75
+ };
76
+ if (framework === "astro")
77
+ return {
78
+ get: async ({ request }) => {
79
+ const result = await handlerLogic({ req: request });
80
+ return {
81
+ body: JSON.stringify(result),
82
+ headers: { "Content-Type": "application/json" }
83
+ };
84
+ }
85
+ };
86
+ throw new Error(`Unsupported framework: ${framework}`);
87
+ };
88
+ async function fetchHubSpotData({ token }) {
89
+ try {
90
+ const apiResponse = await fetch("https://api.hubapi.com/marketing/v3/forms/?limit=9999", {
91
+ headers: {
92
+ Authorization: `Bearer ${token}`
93
+ }
94
+ });
95
+ if (!apiResponse.ok)
96
+ return console.error(`Failed to fetch data: ${apiResponse.statusText}`), null;
97
+ const { results } = await apiResponse.json();
98
+ return results.map((result) => ({
99
+ ...result,
100
+ value: result.id
101
+ }));
102
+ } catch (e) {
103
+ return console.error(e), null;
104
+ }
105
+ }
106
+ const hubSpotHandler = ({ token }) => createHandler(() => fetchHubSpotData({ token })), Option = (option) => /* @__PURE__ */ jsxs(Card, { "data-as": "button", padding: 3, radius: 2, tone: "inherit", children: [
107
+ /* @__PURE__ */ jsx(Text, { size: 2, textOverflow: "ellipsis", children: `${option.form.header.text ? `${option.form.header.text} - ` : ""}${option.value}` }),
108
+ /* @__PURE__ */ jsx(Card, { paddingTop: 2, tone: "inherit", style: { background: "inherit" }, children: /* @__PURE__ */ jsx(Text, { size: 1, textOverflow: "ellipsis", children: `${option.list.name} - ${option.list.stats.member_count} member${option.list.stats.member_count == 1 ? "" : "s"}` }) })
109
+ ] }), mailchimpInput = definePlugin((options) => ({
110
+ name: "sanity-plugin-form-toolkit_mailchimp-input",
111
+ plugins: [
112
+ asyncList({
113
+ schemaType: "mailchimpForm",
114
+ loader: async () => await (await fetch(options.url)).json(),
115
+ autocompleteProps: {
116
+ //@ts-expect-error incorrect typing on props?
117
+ renderOption: (option) => Option(option)
118
+ }
119
+ })
120
+ ]
121
+ }));
122
+ async function fetchMailchimpData({
123
+ key,
124
+ server
125
+ }) {
126
+ mailchimp.setConfig({
127
+ apiKey: key,
128
+ server
129
+ });
130
+ const signupForms = [], { lists } = await mailchimp.lists.getAllLists();
131
+ for (const list of lists) {
132
+ const { signup_forms } = await mailchimp.lists.getListSignupForms(list.id);
133
+ for (const form of signup_forms)
134
+ signupForms.push({
135
+ list,
136
+ form,
137
+ value: form.signup_form_url
138
+ });
139
+ }
140
+ return signupForms;
141
+ }
142
+ const mailchimpHandler = (keys) => createHandler(() => fetchMailchimpData(keys));
143
+ export {
144
+ fetchHubSpotData,
145
+ fetchMailchimpData,
146
+ hubSpotHandler,
147
+ hubSpotInput,
148
+ mailchimpHandler,
149
+ mailchimpInput
150
+ };
151
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/hubspot/components/option.tsx","../src/hubspot/index.ts","../src/shared/create-handler.ts","../src/hubspot/fetch-hubspot-data.ts","../src/hubspot/create-handler.ts","../src/mailchimp/components/option.tsx","../src/mailchimp/index.ts","../src/mailchimp/create-handler.ts"],"sourcesContent":["import {Card, Text} from '@sanity/ui'\nimport type {ReactElement} from 'react'\n\nexport const Option = (option: {value: string; name: string}): ReactElement => {\n return (\n <Card data-as=\"button\" padding={3} radius={2} tone=\"inherit\">\n <Text size={2} textOverflow=\"ellipsis\">\n {option.name}\n </Text>\n <Card paddingTop={2} tone=\"inherit\" style={{background: 'inherit'}}>\n <Text size={1} textOverflow=\"ellipsis\">\n {`ID: ${option.value}`}\n </Text>\n </Card>\n </Card>\n )\n}\n","import {asyncList} from '@sanity/sanity-plugin-async-list'\nimport {definePlugin} from 'sanity'\n\nimport {Option} from './components/option'\n\ninterface HubSpotInputConfig {\n url: string | URL\n}\n\n/**\n * Usage in `sanity.config.ts` (or .js)\n *\n * ```ts\n * import {defineConfig} from 'sanity'\n * import {hubSpotInput} from '@sanity/sanity-plugin-form-toolkit'\n *\n * export default defineConfig({\n * // ...\n * plugins: [\n * hubSpotInput({\n * url: 'http://localhost:3000/api/hubspot'\n * })\n * ],\n * })\n * ```\n */\ntype ExtendedOption = {value: string; name: string}\n\nexport const hubSpotInput = definePlugin<HubSpotInputConfig>((options) => {\n return {\n name: 'sanity-plugin-form-toolkit_hubspot-input',\n plugins: [\n asyncList({\n schemaType: 'hubSpotForm',\n loader: async () => {\n const data = await fetch(options.url)\n const body = await data.json()\n return body\n },\n autocompleteProps: {\n renderOption: (option) => Option(option as ExtendedOption),\n renderValue(value, option) {\n // @ts-expect-error can't extend default type?\n return option?.name ?? value\n },\n },\n }),\n ],\n }\n})\n","import {defineEventHandler, H3Event} from 'h3' // For Nuxt.js/Nitro\nimport type {IncomingMessage, ServerResponse} from 'http'\n\n// Utility function to detect the framework at runtime\nconst detectFramework = (): string => {\n if (process.env.NEXT_RUNTIME) {\n return 'nextjs'\n } else if (process.env.NUXT_ENV) {\n return 'nuxt'\n } else if (process.env.SVELTEKIT_ENV) {\n return 'sveltekit'\n } else if (process.env.REMIX_ENV) {\n return 'remix'\n } else if (process.env.ASTRO_ENV) {\n return 'astro'\n }\n throw new Error('Unable to detect framework.')\n}\n\n// Create a generic handler with predefined logic\nconst createHandler = (handlerFunc: () => Promise<unknown>) => {\n const framework = detectFramework()\n\n // Handler logic to fetch Mailchimp data\n const handlerLogic = async ({\n // eslint-disable-next-line\n req,\n res,\n }: {\n req: IncomingMessage | Request\n res?: ServerResponse\n }): Promise<unknown> => {\n try {\n const data = await handlerFunc()\n\n if (res) {\n // Send response directly for frameworks like Next.js and Nuxt.js\n res.writeHead(200, {'Content-Type': 'application/json'})\n res.end(JSON.stringify(data))\n }\n\n // Return the response for frameworks like SvelteKit, Remix, and Astro\n return data\n } catch (error) {\n if (error instanceof Error) {\n if (res) {\n res.writeHead(500, {'Content-Type': 'application/json'})\n res.end(JSON.stringify({error: error.message}))\n }\n return {error: error.message}\n }\n // Handle non-Error types (e.g., strings, objects)\n if (res) {\n res.writeHead(500, {'Content-Type': 'application/json'})\n res.end(JSON.stringify({error: 'An unexpected error occurred'}))\n }\n return {error: 'An unexpected error occurred'}\n }\n }\n\n // Framework-specific implementations\n if (framework === 'nextjs') {\n return async (req: IncomingMessage, res: ServerResponse) => {\n // Set CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*') // Allow all origins\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') // Allowed HTTP methods\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') // Allowed headers\n\n await handlerLogic({req, res})\n }\n } else if (framework === 'nuxt') {\n return defineEventHandler(async (event: H3Event) => {\n const req = event.node.req\n const res = event.node.res\n return handlerLogic({req, res})\n })\n } else if (framework === 'sveltekit') {\n return {\n GET: async ({request}: {request: Request}) => {\n const result = await handlerLogic({req: request})\n return new Response(JSON.stringify(result), {\n headers: {'Content-Type': 'application/json'},\n })\n },\n }\n } else if (framework === 'remix') {\n return {\n loader: async ({request}: {request: Request}) => {\n const result = await handlerLogic({req: request})\n return new Response(JSON.stringify(result), {\n headers: {'Content-Type': 'application/json'},\n })\n },\n }\n } else if (framework === 'astro') {\n return {\n get: async ({request}: {request: Request}) => {\n const result = await handlerLogic({req: request})\n return {\n body: JSON.stringify(result),\n headers: {'Content-Type': 'application/json'},\n }\n },\n }\n }\n throw new Error(`Unsupported framework: ${framework}`)\n}\n\nexport default createHandler\n","type HubSpotForm = {\n id: string\n name: string\n [key: string]: unknown // Additional properties from the API response\n}\n\ntype MappedResult = HubSpotForm & {\n value: string\n}\nexport async function fetchHubSpotData({token}: {token: string}): Promise<MappedResult[] | null> {\n try {\n const apiResponse = await fetch('https://api.hubapi.com/marketing/v3/forms/?limit=9999', {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n })\n\n if (!apiResponse.ok) {\n console.error(`Failed to fetch data: ${apiResponse.statusText}`)\n return null\n }\n\n const {results}: {results: HubSpotForm[]} = await apiResponse.json()\n\n return results.map((result) => ({\n ...result,\n value: result.id,\n }))\n } catch (e: unknown) {\n console.error(e)\n return null // Explicitly return null on error\n }\n}\n","import createHandler from '../shared/create-handler'\nimport {fetchHubSpotData} from './fetch-hubspot-data'\n\nexport const hubSpotHandler = ({token}: {token: string}) => {\n return createHandler(() => fetchHubSpotData({token}))\n}\n","import {Card, Text} from '@sanity/ui'\nimport type {ReactElement} from 'react'\n\nexport const Option = (option: {\n value: string\n form: {\n header: {\n text?: string\n }\n }\n list: {\n name: string\n stats: {\n member_count: number\n }\n }\n}): ReactElement => {\n return (\n <Card data-as=\"button\" padding={3} radius={2} tone=\"inherit\">\n <Text size={2} textOverflow=\"ellipsis\">\n {`${option.form.header.text ? `${option.form.header.text} - ` : ``}${option.value}`}\n </Text>\n <Card paddingTop={2} tone=\"inherit\" style={{background: 'inherit'}}>\n <Text size={1} textOverflow=\"ellipsis\">\n {`${option.list.name} - ${option.list.stats.member_count} member${option.list.stats.member_count == 1 ? '' : 's'}`}\n </Text>\n </Card>\n </Card>\n )\n}\n","import {asyncList} from '@sanity/sanity-plugin-async-list'\nimport {definePlugin} from 'sanity'\n\nimport {Option} from './components/option'\n\ninterface MailchimpInputConfig {\n url: string | URL\n}\n\n/**\n * Usage in `sanity.config.ts` (or .js)\n *\n * ```ts\n * import {defineConfig} from 'sanity'\n * import {formiumInput} from 'sanity-plugin-form-toolkit'\n *\n * export default defineConfig({\n * // ...\n * plugins: [formiumInput()],\n * })\n * ```\n */\n\nexport const mailchimpInput = definePlugin<MailchimpInputConfig>((options) => {\n return {\n name: 'sanity-plugin-form-toolkit_mailchimp-input',\n plugins: [\n asyncList({\n schemaType: 'mailchimpForm',\n loader: async () => {\n const data = await fetch(options.url)\n const body = await data.json()\n return body\n },\n autocompleteProps: {\n //@ts-expect-error incorrect typing on props?\n renderOption: (option) => Option(option),\n },\n }),\n ],\n }\n})\n","import mailchimp from '@mailchimp/mailchimp_marketing'\n\nimport createHandler from '../shared/create-handler'\n\n// Fetch from Mailchimp's API\nexport async function fetchMailchimpData({\n key,\n server,\n}: {\n key: string\n server: string\n}): Promise<unknown> {\n mailchimp.setConfig({\n apiKey: key,\n server: server,\n })\n const signupForms = []\n // @ts-expect-error bad typing for mailchimp\n const {lists} = await mailchimp.lists.getAllLists()\n for (const list of lists) {\n // @ts-expect-error bad typing for mailchimp\n // eslint-disable-next-line camelcase\n const {signup_forms} = await mailchimp.lists.getListSignupForms(list.id)\n // eslint-disable-next-line camelcase\n for (const form of signup_forms) {\n signupForms.push({\n list,\n form,\n value: form.signup_form_url,\n })\n }\n }\n return signupForms\n}\n\n// Create the Mailchimp handler for a specific key and server\nexport const mailchimpHandler = (keys: {key: string; server: string}) => {\n return createHandler(() => fetchMailchimpData(keys))\n}\n"],"names":["Option"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@sanity/form-toolkit",
3
+ "version": "1.1.0",
4
+ "description": "Tool kit for integrating forms with a Sanity Studio",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin"
8
+ ],
9
+ "homepage": "https://github.com/sanity-io/form-toolkit#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/sanity-io/form-toolkit/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+ssh://git@github.com/sanity-io/form-toolkit.git"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Chris LaRocque <chris.larocque@sanity.io>",
19
+ "sideEffects": false,
20
+ "type": "commonjs",
21
+ "exports": {
22
+ ".": {
23
+ "source": "./src/index.ts",
24
+ "import": "./dist/index.mjs",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./package.json": "./package.json"
28
+ },
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "files": [
32
+ "dist",
33
+ "sanity.json",
34
+ "src",
35
+ "v2-incompatible.js"
36
+ ],
37
+ "scripts": {
38
+ "build": "plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
39
+ "format": "prettier --write --cache --ignore-unknown .",
40
+ "link-watch": "plugin-kit link-watch",
41
+ "lint": "eslint .",
42
+ "prepublishOnly": "npm run build",
43
+ "watch": "pkg-utils watch --strict",
44
+ "prepare": "husky"
45
+ },
46
+ "dependencies": {
47
+ "@formium/client": "^0.1.4",
48
+ "@mailchimp/mailchimp_marketing": "^3.0.80",
49
+ "@sanity/icons": "^3.5.0",
50
+ "@sanity/incompatible-plugin": "^1.0.4",
51
+ "@sanity/sanity-plugin-async-list": "^1.3.0",
52
+ "@sanity/ui": "^2.10.12",
53
+ "h3": "^1.13.0"
54
+ },
55
+ "devDependencies": {
56
+ "@commitlint/cli": "^19.6.1",
57
+ "@commitlint/config-conventional": "^19.6.0",
58
+ "@sanity/pkg-utils": "^6.11.14",
59
+ "@sanity/plugin-kit": "^4.0.18",
60
+ "@sanity/semantic-release-preset": "^5.0.0",
61
+ "@types/mailchimp__mailchimp_marketing": "^3.0.20",
62
+ "@types/react": "^18",
63
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
64
+ "@typescript-eslint/parser": "^7.18.0",
65
+ "eslint": "^8.57.1",
66
+ "eslint-config-prettier": "^9.1.0",
67
+ "eslint-config-sanity": "^7.1.3",
68
+ "eslint-plugin-prettier": "^5.2.1",
69
+ "eslint-plugin-react": "^7.37.2",
70
+ "eslint-plugin-react-hooks": "^5.1.0",
71
+ "husky": "^9.1.7",
72
+ "lint-staged": "^15.4.1",
73
+ "prettier": "^3.4.2",
74
+ "prettier-plugin-packagejson": "^2.5.6",
75
+ "react": "^18",
76
+ "react-dom": "^18",
77
+ "sanity": "^3.66.1",
78
+ "styled-components": "^6.1.13",
79
+ "typescript": "^5.7.2"
80
+ },
81
+ "peerDependencies": {
82
+ "react": "^18",
83
+ "sanity": "^3"
84
+ },
85
+ "engines": {
86
+ "node": ">=18"
87
+ },
88
+ "overrides": {
89
+ "conventional-changelog-conventionalcommits": ">= 8.0.0"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }
package/sanity.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,27 @@
1
+ import {definePlugin} from 'sanity'
2
+
3
+ import {schema} from './schema-types'
4
+ interface FormSchemaConfig {
5
+ /* nothing here yet */
6
+ }
7
+
8
+ /**
9
+ * Usage in `sanity.config.ts` (or .js)
10
+ *
11
+ * ```ts
12
+ * import {defineConfig} from 'sanity'
13
+ * import {formiumInput} from 'sanity-plugin-form-toolkit'
14
+ *
15
+ * export default defineConfig({
16
+ * // ...
17
+ * plugins: [formiumInput()],
18
+ * })
19
+ * ```
20
+ */
21
+ // Is Formium dead? All attempts to use the API come back with an expired cert https://github.com/formium/formium/issues/77
22
+ export const formSchema = definePlugin<FormSchemaConfig | void>(() => {
23
+ return {
24
+ name: 'sanity-plugin-form-toolkit_form-schema',
25
+ schema,
26
+ }
27
+ })
@@ -0,0 +1,146 @@
1
+ import {
2
+ CalendarIcon,
3
+ ClockIcon,
4
+ ColorWheelIcon,
5
+ DocumentIcon,
6
+ EarthGlobeIcon,
7
+ EnvelopeIcon,
8
+ HashIcon,
9
+ NumberIcon,
10
+ TextIcon,
11
+ } from '@sanity/icons'
12
+ import {defineField, defineType} from 'sanity'
13
+
14
+ export const formFieldType = defineType({
15
+ name: 'formField',
16
+ type: 'object',
17
+ fields: [
18
+ defineField({
19
+ name: 'label',
20
+ type: 'string',
21
+ group: 'info',
22
+ }),
23
+ defineField({
24
+ name: 'name',
25
+ type: 'slug',
26
+ group: 'info',
27
+ validation: (rule) => rule.required(),
28
+ options: {
29
+ source: 'label',
30
+ },
31
+ }),
32
+ defineField({
33
+ name: 'type',
34
+ type: 'string',
35
+ group: 'info',
36
+ validation: (rule) => rule.required(),
37
+ options: {
38
+ list: [
39
+ {value: 'color', title: 'Color'},
40
+ {value: 'date', title: 'Date'},
41
+ {value: 'datetime-local', title: 'Date-time'},
42
+ 'email',
43
+ 'file',
44
+ {value: 'month', title: 'Month & year'},
45
+ 'number',
46
+ {value: 'tel', title: 'Telephone'},
47
+ 'text',
48
+ 'time',
49
+ 'range',
50
+ {value: 'url', title: 'URL'},
51
+ 'week',
52
+ ],
53
+ },
54
+ }),
55
+ defineField({
56
+ group: 'validation',
57
+ name: 'required',
58
+ type: 'boolean',
59
+ hidden: ({parent}) => {
60
+ const unallowedTypes = ['hidden', 'range', 'color']
61
+
62
+ return unallowedTypes.includes(parent.type)
63
+ },
64
+ }),
65
+ defineField({
66
+ group: 'validation',
67
+ name: 'max',
68
+ type: 'number',
69
+ hidden: ({parent}) => {
70
+ const allowedTypes = ['date', 'month', 'week', 'time', 'datetime-local', 'number', 'range']
71
+
72
+ return !allowedTypes.includes(parent.type)
73
+ },
74
+ }),
75
+ defineField({
76
+ group: 'validation',
77
+ name: 'min',
78
+ type: 'number',
79
+ hidden: ({parent}) => {
80
+ const allowedTypes = ['date', 'month', 'week', 'time', 'datetime-local', 'number', 'range']
81
+
82
+ return !allowedTypes.includes(parent.type)
83
+ },
84
+ }),
85
+ defineField({
86
+ group: 'validation',
87
+ name: 'step',
88
+ type: 'number',
89
+ hidden: ({parent}) => {
90
+ const allowedTypes = ['date', 'month', 'week', 'time', 'datetime-local', 'number', 'range']
91
+
92
+ return !allowedTypes.includes(parent.type)
93
+ },
94
+ }),
95
+ defineField({
96
+ group: 'validation',
97
+ name: 'maxlength',
98
+ title: 'Max length',
99
+ type: 'number',
100
+ hidden: ({parent}) => {
101
+ const allowedTypes = ['text', 'search', 'url', 'tel', 'email', 'password']
102
+
103
+ return !allowedTypes.includes(parent.type)
104
+ },
105
+ }),
106
+ defineField({
107
+ group: 'validation',
108
+ name: 'minlength',
109
+ title: 'Min length',
110
+ type: 'number',
111
+ hidden: ({parent}) => {
112
+ const allowedTypes = ['text', 'search', 'url', 'tel', 'email', 'password']
113
+
114
+ return !allowedTypes.includes(parent.type)
115
+ },
116
+ }),
117
+ ],
118
+ groups: [{name: 'info'}, {name: 'validation'}],
119
+ preview: {
120
+ select: {
121
+ title: 'label',
122
+ subtitle: 'type',
123
+ },
124
+ prepare: ({title, subtitle}) => {
125
+ const icon: Record<
126
+ string,
127
+ typeof TextIcon | typeof NumberIcon | typeof CalendarIcon | typeof ClockIcon
128
+ > = {
129
+ text: TextIcon,
130
+ number: NumberIcon,
131
+ date: CalendarIcon,
132
+ 'datetime-local': CalendarIcon,
133
+ month: CalendarIcon,
134
+ time: ClockIcon,
135
+ color: ColorWheelIcon,
136
+ email: EnvelopeIcon,
137
+ url: EarthGlobeIcon,
138
+ file: DocumentIcon,
139
+ tel: HashIcon,
140
+ week: CalendarIcon,
141
+ range: NumberIcon,
142
+ }
143
+ return {title, subtitle, media: icon[subtitle]}
144
+ },
145
+ },
146
+ })
@@ -0,0 +1,13 @@
1
+ import {defineField, defineType} from 'sanity'
2
+
3
+ export const formType = defineType({
4
+ name: 'form',
5
+ type: 'object',
6
+ fields: [
7
+ defineField({
8
+ name: 'fields',
9
+ type: 'array',
10
+ of: [{type: 'formField'}],
11
+ }),
12
+ ],
13
+ })
@@ -0,0 +1,8 @@
1
+ import type {SchemaTypeDefinition} from 'sanity'
2
+
3
+ import {formType} from './form'
4
+ import {formFieldType} from './form-field'
5
+
6
+ export const schema: {types: SchemaTypeDefinition[]} = {
7
+ types: [formFieldType, formType],
8
+ }
@@ -0,0 +1,52 @@
1
+ import {createClient, type Form} from '@formium/client'
2
+ import {asyncList} from '@sanity/sanity-plugin-async-list'
3
+ import {definePlugin} from 'sanity'
4
+
5
+ interface FormiumInputConfig {
6
+ /* nothing here yet */
7
+ }
8
+
9
+ /**
10
+ * Usage in `sanity.config.ts` (or .js)
11
+ *
12
+ * ```ts
13
+ * import {defineConfig} from 'sanity'
14
+ * import {formiumInput} from 'sanity-plugin-form-toolkit'
15
+ *
16
+ * export default defineConfig({
17
+ * // ...
18
+ * plugins: [formiumInput()],
19
+ * })
20
+ * ```
21
+ */
22
+ // Is Formium dead? All attempts to use the API come back with an expired cert https://github.com/formium/formium/issues/77
23
+ export const formiumInput = definePlugin<FormiumInputConfig | void>(() => {
24
+ return {
25
+ name: 'sanity-plugin-form-toolkit_formium-input',
26
+ plugins: [
27
+ asyncList({
28
+ schemaType: 'formiumInput',
29
+ secrets: {
30
+ keys: [
31
+ {key: 'projectId', title: 'Project ID'},
32
+ {key: 'token', title: 'Token'},
33
+ ],
34
+ },
35
+ loader: async ({secrets}) => {
36
+ const formium = createClient(secrets?.projectId || '', {
37
+ apiToken: secrets?.token,
38
+ })
39
+ const {data}: {data: Form[]} = await formium.findForms()
40
+ return data && data.length
41
+ ? data.map(({name, id}) => {
42
+ return {
43
+ title: name,
44
+ value: id,
45
+ }
46
+ })
47
+ : []
48
+ },
49
+ }),
50
+ ],
51
+ }
52
+ })
@@ -0,0 +1,17 @@
1
+ import {Card, Text} from '@sanity/ui'
2
+ import type {ReactElement} from 'react'
3
+
4
+ export const Option = (option: {value: string; name: string}): ReactElement => {
5
+ return (
6
+ <Card data-as="button" padding={3} radius={2} tone="inherit">
7
+ <Text size={2} textOverflow="ellipsis">
8
+ {option.name}
9
+ </Text>
10
+ <Card paddingTop={2} tone="inherit" style={{background: 'inherit'}}>
11
+ <Text size={1} textOverflow="ellipsis">
12
+ {`ID: ${option.value}`}
13
+ </Text>
14
+ </Card>
15
+ </Card>
16
+ )
17
+ }
@@ -0,0 +1,6 @@
1
+ import createHandler from '../shared/create-handler'
2
+ import {fetchHubSpotData} from './fetch-hubspot-data'
3
+
4
+ export const hubSpotHandler = ({token}: {token: string}) => {
5
+ return createHandler(() => fetchHubSpotData({token}))
6
+ }
@@ -0,0 +1,33 @@
1
+ type HubSpotForm = {
2
+ id: string
3
+ name: string
4
+ [key: string]: unknown // Additional properties from the API response
5
+ }
6
+
7
+ type MappedResult = HubSpotForm & {
8
+ value: string
9
+ }
10
+ export async function fetchHubSpotData({token}: {token: string}): Promise<MappedResult[] | null> {
11
+ try {
12
+ const apiResponse = await fetch('https://api.hubapi.com/marketing/v3/forms/?limit=9999', {
13
+ headers: {
14
+ Authorization: `Bearer ${token}`,
15
+ },
16
+ })
17
+
18
+ if (!apiResponse.ok) {
19
+ console.error(`Failed to fetch data: ${apiResponse.statusText}`)
20
+ return null
21
+ }
22
+
23
+ const {results}: {results: HubSpotForm[]} = await apiResponse.json()
24
+
25
+ return results.map((result) => ({
26
+ ...result,
27
+ value: result.id,
28
+ }))
29
+ } catch (e: unknown) {
30
+ console.error(e)
31
+ return null // Explicitly return null on error
32
+ }
33
+ }
@@ -0,0 +1,50 @@
1
+ import {asyncList} from '@sanity/sanity-plugin-async-list'
2
+ import {definePlugin} from 'sanity'
3
+
4
+ import {Option} from './components/option'
5
+
6
+ interface HubSpotInputConfig {
7
+ url: string | URL
8
+ }
9
+
10
+ /**
11
+ * Usage in `sanity.config.ts` (or .js)
12
+ *
13
+ * ```ts
14
+ * import {defineConfig} from 'sanity'
15
+ * import {hubSpotInput} from '@sanity/sanity-plugin-form-toolkit'
16
+ *
17
+ * export default defineConfig({
18
+ * // ...
19
+ * plugins: [
20
+ * hubSpotInput({
21
+ * url: 'http://localhost:3000/api/hubspot'
22
+ * })
23
+ * ],
24
+ * })
25
+ * ```
26
+ */
27
+ type ExtendedOption = {value: string; name: string}
28
+
29
+ export const hubSpotInput = definePlugin<HubSpotInputConfig>((options) => {
30
+ return {
31
+ name: 'sanity-plugin-form-toolkit_hubspot-input',
32
+ plugins: [
33
+ asyncList({
34
+ schemaType: 'hubSpotForm',
35
+ loader: async () => {
36
+ const data = await fetch(options.url)
37
+ const body = await data.json()
38
+ return body
39
+ },
40
+ autocompleteProps: {
41
+ renderOption: (option) => Option(option as ExtendedOption),
42
+ renderValue(value, option) {
43
+ // @ts-expect-error can't extend default type?
44
+ return option?.name ?? value
45
+ },
46
+ },
47
+ }),
48
+ ],
49
+ }
50
+ })
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ // import {formSchema} from './form-schema'
2
+ // import {formiumInput} from './formium'
3
+ import {hubSpotInput} from './hubspot'
4
+ import {hubSpotHandler} from './hubspot/create-handler'
5
+ import {fetchHubSpotData} from './hubspot/fetch-hubspot-data'
6
+ import {mailchimpInput} from './mailchimp'
7
+ import {fetchMailchimpData, mailchimpHandler} from './mailchimp/create-handler'
8
+
9
+ export {
10
+ fetchHubSpotData,
11
+ fetchMailchimpData,
12
+ hubSpotHandler,
13
+ hubSpotInput,
14
+ mailchimpHandler,
15
+ mailchimpInput,
16
+ }