From e79ff09b0f0bdfc50a85784c51ee699743647351 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Tue, 7 Jun 2022 08:49:12 +0200 Subject: [PATCH] =?UTF-8?q?refactor(models):=20=F0=9F=8E=A8=20Build=20type?= =?UTF-8?q?s=20from=20validation=20schemas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/builder/contexts/WorkspaceContext.tsx | 9 +- .../layouts/results/ResultsContent.tsx | 4 +- .../layouts/results/SubmissionContent.tsx | 5 +- .../pages/api/typebots/[typebotId]/results.ts | 8 +- apps/builder/playwright/tests/editor.spec.ts | 3 +- .../builder/playwright/tests/settings.spec.ts | 1 + apps/builder/services/typebots/results.tsx | 23 +- packages/bot-engine/package.json | 5 +- .../migration.sql | 20 ++ packages/db/prisma/schema.prisma | 16 +- packages/models/package.json | 25 ++- packages/models/rollup.config.js | 38 ++++ packages/models/src/result.ts | 3 +- packages/models/src/typebot/settings.ts | 54 +++-- packages/models/src/typebot/steps/bubble.ts | 80 ------- .../models/src/typebot/steps/bubble/bubble.ts | 18 ++ .../models/src/typebot/steps/bubble/embed.ts | 19 ++ .../models/src/typebot/steps/bubble/image.ts | 18 ++ .../models/src/typebot/steps/bubble/index.ts | 5 + .../models/src/typebot/steps/bubble/text.ts | 25 +++ .../models/src/typebot/steps/bubble/video.ts | 26 +++ packages/models/src/typebot/steps/index.ts | 3 +- .../models/src/typebot/steps/input/choice.ts | 40 ++++ .../models/src/typebot/steps/input/date.ts | 35 +++ .../models/src/typebot/steps/input/email.ts | 35 +++ .../models/src/typebot/steps/input/index.ts | 9 + .../models/src/typebot/steps/input/input.ts | 36 ++++ .../models/src/typebot/steps/input/number.ts | 32 +++ .../models/src/typebot/steps/input/payment.ts | 49 +++++ .../models/src/typebot/steps/input/phone.ts | 38 ++++ .../models/src/typebot/steps/input/text.ts | 37 ++++ .../models/src/typebot/steps/input/url.ts | 35 +++ packages/models/src/typebot/steps/inputs.ts | 203 ------------------ .../models/src/typebot/steps/integration.ts | 156 -------------- .../steps/integration/googleAnalytics.ts | 24 +++ .../typebot/steps/integration/googleSheets.ts | 80 +++++++ .../src/typebot/steps/integration/index.ts | 8 + .../typebot/steps/integration/integration.ts | 32 +++ .../src/typebot/steps/integration/makeCom.ts | 13 ++ .../steps/integration/pabblyConnect.ts | 13 ++ .../typebot/steps/integration/sendEmail.ts | 27 +++ .../src/typebot/steps/integration/webhook.ts | 43 ++++ .../src/typebot/steps/integration/zapier.ts | 13 ++ packages/models/src/typebot/steps/item.ts | 22 +- packages/models/src/typebot/steps/logic.ts | 113 ---------- .../models/src/typebot/steps/logic/code.ts | 19 ++ .../src/typebot/steps/logic/condition.ts | 58 +++++ .../models/src/typebot/steps/logic/index.ts | 6 + .../models/src/typebot/steps/logic/logic.ts | 20 ++ .../src/typebot/steps/logic/redirect.ts | 19 ++ .../src/typebot/steps/logic/setVariable.ts | 20 ++ .../src/typebot/steps/logic/typebotLink.ts | 19 ++ packages/models/src/typebot/steps/shared.ts | 60 ++++++ packages/models/src/typebot/steps/steps.ts | 46 ++-- packages/models/src/typebot/theme.ts | 78 ++++--- packages/models/src/typebot/typebot.ts | 96 +++++---- packages/models/src/typebot/variable.ts | 13 +- packages/models/tsconfig.json | 12 +- packages/utils/package.json | 4 + packages/utils/rollup.config.js | 2 +- packages/utils/src/api/utils.ts | 24 ++- yarn.lock | 5 + 62 files changed, 1268 insertions(+), 734 deletions(-) create mode 100644 packages/db/prisma/migrations/20220607063609_required_workspace/migration.sql create mode 100644 packages/models/rollup.config.js delete mode 100644 packages/models/src/typebot/steps/bubble.ts create mode 100644 packages/models/src/typebot/steps/bubble/bubble.ts create mode 100644 packages/models/src/typebot/steps/bubble/embed.ts create mode 100644 packages/models/src/typebot/steps/bubble/image.ts create mode 100644 packages/models/src/typebot/steps/bubble/index.ts create mode 100644 packages/models/src/typebot/steps/bubble/text.ts create mode 100644 packages/models/src/typebot/steps/bubble/video.ts create mode 100644 packages/models/src/typebot/steps/input/choice.ts create mode 100644 packages/models/src/typebot/steps/input/date.ts create mode 100644 packages/models/src/typebot/steps/input/email.ts create mode 100644 packages/models/src/typebot/steps/input/index.ts create mode 100644 packages/models/src/typebot/steps/input/input.ts create mode 100644 packages/models/src/typebot/steps/input/number.ts create mode 100644 packages/models/src/typebot/steps/input/payment.ts create mode 100644 packages/models/src/typebot/steps/input/phone.ts create mode 100644 packages/models/src/typebot/steps/input/text.ts create mode 100644 packages/models/src/typebot/steps/input/url.ts delete mode 100644 packages/models/src/typebot/steps/inputs.ts delete mode 100644 packages/models/src/typebot/steps/integration.ts create mode 100644 packages/models/src/typebot/steps/integration/googleAnalytics.ts create mode 100644 packages/models/src/typebot/steps/integration/googleSheets.ts create mode 100644 packages/models/src/typebot/steps/integration/index.ts create mode 100644 packages/models/src/typebot/steps/integration/integration.ts create mode 100644 packages/models/src/typebot/steps/integration/makeCom.ts create mode 100644 packages/models/src/typebot/steps/integration/pabblyConnect.ts create mode 100644 packages/models/src/typebot/steps/integration/sendEmail.ts create mode 100644 packages/models/src/typebot/steps/integration/webhook.ts create mode 100644 packages/models/src/typebot/steps/integration/zapier.ts delete mode 100644 packages/models/src/typebot/steps/logic.ts create mode 100644 packages/models/src/typebot/steps/logic/code.ts create mode 100644 packages/models/src/typebot/steps/logic/condition.ts create mode 100644 packages/models/src/typebot/steps/logic/index.ts create mode 100644 packages/models/src/typebot/steps/logic/logic.ts create mode 100644 packages/models/src/typebot/steps/logic/redirect.ts create mode 100644 packages/models/src/typebot/steps/logic/setVariable.ts create mode 100644 packages/models/src/typebot/steps/logic/typebotLink.ts create mode 100644 packages/models/src/typebot/steps/shared.ts diff --git a/apps/builder/contexts/WorkspaceContext.tsx b/apps/builder/contexts/WorkspaceContext.tsx index 03976f517c5..192997fa333 100644 --- a/apps/builder/contexts/WorkspaceContext.tsx +++ b/apps/builder/contexts/WorkspaceContext.tsx @@ -5,7 +5,7 @@ import { useEffect, useState, } from 'react' -import { byId, isNotEmpty } from 'utils' +import { byId } from 'utils' import { MemberInWorkspace, Plan, Workspace, WorkspaceRole } from 'db' import { createNewWorkspace, @@ -87,8 +87,11 @@ export const WorkspaceContext = ({ children }: { children: ReactNode }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [typebot?.workspaceId]) - const switchWorkspace = (workspaceId: string) => - setCurrentWorkspace(workspaces?.find(byId(workspaceId))) + const switchWorkspace = (workspaceId: string) => { + const newWorkspace = workspaces?.find(byId(workspaceId)) + if (!newWorkspace) return + setCurrentWorkspace(newWorkspace) + } const createWorkspace = async (name?: string) => { if (!workspaces) return diff --git a/apps/builder/layouts/results/ResultsContent.tsx b/apps/builder/layouts/results/ResultsContent.tsx index a4058807d42..15ef7518df4 100644 --- a/apps/builder/layouts/results/ResultsContent.tsx +++ b/apps/builder/layouts/results/ResultsContent.tsx @@ -70,11 +70,13 @@ export const ResultsContent = () => { - {publishedTypebot && + {workspace && + publishedTypebot && (isAnalytics ? ( ) : ( void } export const SubmissionsContent = ({ + workspaceId, typebotId, totalResults, totalHiddenResults, @@ -51,6 +53,7 @@ export const SubmissionsContent = ({ const resultHeader = parseResultHeader(blocksAndVariables) const { data, mutate, setSize, hasMore } = useResults({ + workspaceId, typebotId, onError: (err) => showToast({ title: err.name, description: err.message }), }) @@ -125,7 +128,7 @@ export const SubmissionsContent = ({ const getAllTableData = async () => { if (!publishedTypebot) return [] - const results = await getAllResults(typebotId) + const results = await getAllResults(workspaceId, typebotId) return convertResultsToTableData(results, resultHeader) } diff --git a/apps/builder/pages/api/typebots/[typebotId]/results.ts b/apps/builder/pages/api/typebots/[typebotId]/results.ts index 0d41b5e7491..8e703c67be8 100644 --- a/apps/builder/pages/api/typebots/[typebotId]/results.ts +++ b/apps/builder/pages/api/typebots/[typebotId]/results.ts @@ -4,13 +4,19 @@ import { NextApiRequest, NextApiResponse } from 'next' import { canReadTypebot, canWriteTypebot } from 'services/api/dbRules' import { getAuthenticatedUser } from 'services/api/utils' import { isFreePlan } from 'services/workspace' -import { forbidden, methodNotAllowed, notAuthenticated } from 'utils' +import { + badRequest, + forbidden, + methodNotAllowed, + notAuthenticated, +} from 'utils' const handler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getAuthenticatedUser(req) if (!user) return notAuthenticated(res) const workspaceId = req.query.workspaceId as string | undefined if (req.method === 'GET') { + if (!workspaceId) return badRequest(res, 'workspaceId is required') const workspace = await prisma.workspace.findFirst({ where: { id: workspaceId, members: { some: { userId: user.id } } }, select: { plan: true }, diff --git a/apps/builder/playwright/tests/editor.spec.ts b/apps/builder/playwright/tests/editor.spec.ts index aa696d32cda..997644595e6 100644 --- a/apps/builder/playwright/tests/editor.spec.ts +++ b/apps/builder/playwright/tests/editor.spec.ts @@ -143,12 +143,13 @@ test.describe.parallel('Editor', () => { await page.goto(`/typebots/${typebotId}/edit`) await page.click('[data-testid="editable-icon"]') + await expect(page.locator('text="My awesome typebot"')).toBeVisible() await page.fill('input[placeholder="Search..."]', 'love') await page.click('text="😍"') await page.click('text="My awesome typebot"') await page.fill('input[value="My awesome typebot"]', 'My superb typebot') await page.press('input[value="My superb typebot"]', 'Enter') - await page.goto(`/typebots`) + await page.click('[aria-label="Navigate back"]') await expect(page.locator('text="😍"')).toBeVisible() await expect(page.locator('text="My superb typebot"')).toBeVisible() }) diff --git a/apps/builder/playwright/tests/settings.spec.ts b/apps/builder/playwright/tests/settings.spec.ts index 202873d1eb4..835fc216fbc 100644 --- a/apps/builder/playwright/tests/settings.spec.ts +++ b/apps/builder/playwright/tests/settings.spec.ts @@ -78,6 +78,7 @@ test.describe.parallel('Settings page', () => { ) await page.goto(`/typebots/${typebotId}/settings`) await page.click('button:has-text("Metadata")') + await page.waitForTimeout(1000) // Fav icon const favIconImg = page.locator('img >> nth=0') diff --git a/apps/builder/services/typebots/results.tsx b/apps/builder/services/typebots/results.tsx index f679e26fd34..3261a0e86ba 100644 --- a/apps/builder/services/typebots/results.tsx +++ b/apps/builder/services/typebots/results.tsx @@ -2,7 +2,7 @@ import { ResultWithAnswers, VariableWithValue, ResultHeaderCell } from 'models' import useSWRInfinite from 'swr/infinite' import { stringify } from 'qs' import { Answer } from 'db' -import { isDefined, sendRequest } from 'utils' +import { isDefined, isEmpty, sendRequest } from 'utils' import { fetcher } from 'services/utils' import { HStack, Text } from '@chakra-ui/react' import { CodeIcon, CalendarIcon } from 'assets/icons' @@ -11,6 +11,7 @@ import { StepIcon } from 'components/editor/StepsSideBar/StepIcon' const paginationLimit = 50 const getKey = ( + workspaceId: string, typebotId: string, pageIndex: number, previousPageData: { @@ -18,16 +19,19 @@ const getKey = ( } ) => { if (previousPageData && previousPageData.results.length === 0) return null - if (pageIndex === 0) return `/api/typebots/${typebotId}/results?limit=50` + if (pageIndex === 0) + return `/api/typebots/${typebotId}/results?limit=50&workspaceId=${workspaceId}` return `/api/typebots/${typebotId}/results?lastResultId=${ previousPageData.results[previousPageData.results.length - 1].id - }&limit=${paginationLimit}` + }&limit=${paginationLimit}&workspaceId=${workspaceId}` } export const useResults = ({ + workspaceId, typebotId, onError, }: { + workspaceId: string typebotId: string onError: (error: Error) => void }) => { @@ -40,9 +44,14 @@ export const useResults = ({ previousPageData: { results: ResultWithAnswers[] } - ) => getKey(typebotId, pageIndex, previousPageData), + ) => getKey(workspaceId, typebotId, pageIndex, previousPageData), fetcher, - { revalidateAll: true } + { + revalidateAll: true, + dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) + ? undefined + : 0, + } ) if (error) onError(error) @@ -80,12 +89,12 @@ export const deleteAllResults = async (typebotId: string) => method: 'DELETE', }) -export const getAllResults = async (typebotId: string) => { +export const getAllResults = async (workspaceId: string, typebotId: string) => { const results = [] let hasMore = true let lastResultId: string | undefined = undefined do { - const query = stringify({ limit: 200, lastResultId }) + const query = stringify({ limit: 200, lastResultId, workspaceId }) const { data } = await sendRequest<{ results: ResultWithAnswers[] }>({ url: `/api/typebots/${typebotId}/results?${query}`, method: 'GET', diff --git a/packages/bot-engine/package.json b/packages/bot-engine/package.json index 6c9b3c64993..67fb6abe85d 100644 --- a/packages/bot-engine/package.json +++ b/packages/bot-engine/package.json @@ -45,7 +45,10 @@ "typescript": "^4.6.4" }, "peerDependencies": { - "react": "^18.1.0" + "react": "^18.1.0", + "utils": "*", + "db": "*", + "models": "*" }, "scripts": { "build": "yarn rollup -c", diff --git a/packages/db/prisma/migrations/20220607063609_required_workspace/migration.sql b/packages/db/prisma/migrations/20220607063609_required_workspace/migration.sql new file mode 100644 index 00000000000..eade505c352 --- /dev/null +++ b/packages/db/prisma/migrations/20220607063609_required_workspace/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - Made the column `workspaceId` on table `Credentials` required. This step will fail if there are existing NULL values in that column. + - Made the column `workspaceId` on table `CustomDomain` required. This step will fail if there are existing NULL values in that column. + - Made the column `workspaceId` on table `DashboardFolder` required. This step will fail if there are existing NULL values in that column. + - Made the column `workspaceId` on table `Typebot` required. This step will fail if there are existing NULL values in that column. + +*/ +-- AlterTable +ALTER TABLE "Credentials" ALTER COLUMN "workspaceId" SET NOT NULL; + +-- AlterTable +ALTER TABLE "CustomDomain" ALTER COLUMN "workspaceId" SET NOT NULL; + +-- AlterTable +ALTER TABLE "DashboardFolder" ALTER COLUMN "workspaceId" SET NOT NULL; + +-- AlterTable +ALTER TABLE "Typebot" ALTER COLUMN "workspaceId" SET NOT NULL; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index ff0392d2f00..30e7fe221b4 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -114,15 +114,15 @@ enum GraphNavigation { model CustomDomain { name String @id createdAt DateTime @default(now()) - workspaceId String? - workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + workspaceId String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) } model Credentials { id String @id @default(cuid()) createdAt DateTime @default(now()) - workspaceId String? - workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + workspaceId String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) data String // Encrypted data name String type String @@ -154,8 +154,8 @@ model DashboardFolder { parentFolder DashboardFolder? @relation("ParentChild", fields: [parentFolderId], references: [id]) childrenFolder DashboardFolder[] @relation("ParentChild") typebots Typebot[] - workspaceId String? - workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + workspaceId String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) } model Typebot { @@ -179,8 +179,8 @@ model Typebot { collaborators CollaboratorsOnTypebots[] invitations Invitation[] webhooks Webhook[] - workspaceId String? - workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + workspaceId String + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) } model Invitation { diff --git a/packages/models/package.json b/packages/models/package.json index 48191567155..c451fe0b473 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -1,20 +1,31 @@ { "name": "models", "version": "1.0.0", - "main": "dist/index.js", - "types": "dist/types/index.d.ts", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/index.d.ts", "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { - "typescript": "^4.6.4" + "typescript": "^4.6.4", + "@rollup/plugin-commonjs": "^22.0.0", + "@rollup/plugin-node-resolve": "^13.3.0", + "@rollup/plugin-typescript": "^8.3.2", + "rollup": "^2.72.1", + "rollup-plugin-dts": "^4.2.1", + "rollup-plugin-peer-deps-external": "^2.2.4" }, "dependencies": { - "next": "^12.1.6", "db": "*", - "@udecode/plate-core": "^11.0.0" + "next": "^12.1.6", + "zod": "^3.17.3" + }, + "peerDependencies": { + "next": "^12.1.6", + "db": "*" }, "scripts": { - "build": "tsc", - "dx": "tsc --watch --preserveWatchOutput" + "build": "yarn rollup -c", + "dx": "yarn rollup -c --watch" } } diff --git a/packages/models/rollup.config.js b/packages/models/rollup.config.js new file mode 100644 index 00000000000..96c951fd367 --- /dev/null +++ b/packages/models/rollup.config.js @@ -0,0 +1,38 @@ +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import typescript from '@rollup/plugin-typescript' +import dts from 'rollup-plugin-dts' +import peerDepsExternal from 'rollup-plugin-peer-deps-external' + +const packageJson = require('./package.json') + +export default [ + { + input: 'src/index.ts', + output: [ + { + file: packageJson.main, + format: 'cjs', + sourcemap: true, + inlineDynamicImports: true, + }, + { + file: packageJson.module, + format: 'esm', + sourcemap: true, + inlineDynamicImports: true, + }, + ], + plugins: [ + peerDepsExternal(), + resolve(), + commonjs(), + typescript({ tsconfig: './tsconfig.json' }), + ], + }, + { + input: 'dist/esm/types/index.d.ts', + output: [{ file: 'dist/index.d.ts', format: 'esm' }], + plugins: [dts()], + }, +] diff --git a/packages/models/src/result.ts b/packages/models/src/result.ts index 1e4829f6212..226d256770c 100644 --- a/packages/models/src/result.ts +++ b/packages/models/src/result.ts @@ -1,5 +1,6 @@ import { Result as ResultFromPrisma } from 'db' -import { Answer, InputStepType, VariableWithValue } from '.' +import { Answer, VariableWithValue } from '.' +import { InputStepType } from './typebot/steps/shared' export type Result = Omit & { createdAt: string diff --git a/packages/models/src/typebot/settings.ts b/packages/models/src/typebot/settings.ts index ebb3ec8e860..a1f141d48f3 100644 --- a/packages/models/src/typebot/settings.ts +++ b/packages/models/src/typebot/settings.ts @@ -1,29 +1,32 @@ -export type Settings = { - general: GeneralSettings - typingEmulation: TypingEmulation - metadata: Metadata -} +import { z } from 'zod' -export type GeneralSettings = { - isBrandingEnabled: boolean - isNewResultOnRefreshEnabled?: boolean - isInputPrefillEnabled?: boolean - isHideQueryParamsEnabled?: boolean -} +const generalSettings = z.object({ + isBrandingEnabled: z.boolean(), + isTypingEmulationEnabled: z.boolean().optional(), + isInputPrefillEnabled: z.boolean().optional(), + isHideQueryParamsEnabled: z.boolean().optional(), + isNewResultOnRefreshEnabled: z.boolean().optional(), +}) -export type TypingEmulation = { - enabled: boolean - speed: number - maxDelay: number -} +const typingEmulation = z.object({ + enabled: z.boolean(), + speed: z.number(), + maxDelay: z.number(), +}) -export type Metadata = { - title?: string - description: string - imageUrl?: string - favIconUrl?: string - customHeadCode?: string -} +const metadataSchema = z.object({ + title: z.string().optional(), + description: z.string().optional(), + imageUrl: z.string().optional(), + favIconUrl: z.string().optional(), + customHeadCode: z.string().optional(), +}) + +export const settingsSchema = z.object({ + general: generalSettings, + typingEmulation: typingEmulation, + metadata: metadataSchema, +}) export const defaultSettings: Settings = { general: { @@ -38,3 +41,8 @@ export const defaultSettings: Settings = { 'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.', }, } + +export type Settings = z.infer +export type GeneralSettings = z.infer +export type TypingEmulation = z.infer +export type Metadata = z.infer diff --git a/packages/models/src/typebot/steps/bubble.ts b/packages/models/src/typebot/steps/bubble.ts deleted file mode 100644 index 6094e086c06..00000000000 --- a/packages/models/src/typebot/steps/bubble.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { StepBase } from '.' -import { TElement } from '@udecode/plate-core' - -export type BubbleStep = - | TextBubbleStep - | ImageBubbleStep - | VideoBubbleStep - | EmbedBubbleStep - -export enum BubbleStepType { - TEXT = 'text', - IMAGE = 'image', - VIDEO = 'video', - EMBED = 'embed', -} - -export type BubbleStepContent = - | TextBubbleContent - | ImageBubbleContent - | VideoBubbleContent - | EmbedBubbleContent - -export type TextBubbleStep = StepBase & { - type: BubbleStepType.TEXT - content: TextBubbleContent -} - -export type ImageBubbleStep = StepBase & { - type: BubbleStepType.IMAGE - content: ImageBubbleContent -} - -export type VideoBubbleStep = StepBase & { - type: BubbleStepType.VIDEO - content: VideoBubbleContent -} - -export type EmbedBubbleStep = StepBase & { - type: BubbleStepType.EMBED - content: EmbedBubbleContent -} - -export type TextBubbleContent = { - html: string - richText: TElement[] - plainText: string -} - -export type ImageBubbleContent = { - url?: string -} - -export type EmbedBubbleContent = { - url?: string - height: number -} - -export enum VideoBubbleContentType { - URL = 'url', - YOUTUBE = 'youtube', - VIMEO = 'vimeo', -} - -export type VideoBubbleContent = { - type?: VideoBubbleContentType - url?: string - id?: string -} - -export const defaultTextBubbleContent: TextBubbleContent = { - html: '', - richText: [], - plainText: '', -} - -export const defaultImageBubbleContent: ImageBubbleContent = {} - -export const defaultVideoBubbleContent: VideoBubbleContent = {} - -export const defaultEmbedBubbleContent: EmbedBubbleContent = { height: 400 } diff --git a/packages/models/src/typebot/steps/bubble/bubble.ts b/packages/models/src/typebot/steps/bubble/bubble.ts new file mode 100644 index 00000000000..a5a12cdd117 --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/bubble.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { embedBubbleContentSchema, embedBubbleStepSchema } from './embed' +import { imageBubbleContentSchema, imageBubbleStepSchema } from './image' +import { textBubbleContentSchema, textBubbleStepSchema } from './text' +import { videoBubbleContentSchema, videoBubbleStepSchema } from './video' + +export const bubbleStepContentSchema = textBubbleContentSchema + .or(imageBubbleContentSchema) + .or(videoBubbleContentSchema) + .or(embedBubbleContentSchema) + +export const bubbleStepSchema = textBubbleStepSchema + .or(imageBubbleStepSchema) + .or(videoBubbleStepSchema) + .or(embedBubbleStepSchema) + +export type BubbleStep = z.infer +export type BubbleStepContent = z.infer diff --git a/packages/models/src/typebot/steps/bubble/embed.ts b/packages/models/src/typebot/steps/bubble/embed.ts new file mode 100644 index 00000000000..5d806136c19 --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/embed.ts @@ -0,0 +1,19 @@ +import { stepBaseSchema, BubbleStepType } from '../shared' +import { z } from 'zod' + +export const embedBubbleContentSchema = z.object({ + url: z.string().optional(), + height: z.number(), +}) + +export const embedBubbleStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([BubbleStepType.EMBED]), + content: embedBubbleContentSchema, + }) +) + +export const defaultEmbedBubbleContent: EmbedBubbleContent = { height: 400 } + +export type EmbedBubbleStep = z.infer +export type EmbedBubbleContent = z.infer diff --git a/packages/models/src/typebot/steps/bubble/image.ts b/packages/models/src/typebot/steps/bubble/image.ts new file mode 100644 index 00000000000..d34651cdced --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/image.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { stepBaseSchema, BubbleStepType } from '../shared' + +export const imageBubbleContentSchema = z.object({ + url: z.string().optional(), +}) + +export const imageBubbleStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([BubbleStepType.IMAGE]), + content: imageBubbleContentSchema, + }) +) + +export const defaultImageBubbleContent: ImageBubbleContent = {} + +export type ImageBubbleStep = z.infer +export type ImageBubbleContent = z.infer diff --git a/packages/models/src/typebot/steps/bubble/index.ts b/packages/models/src/typebot/steps/bubble/index.ts new file mode 100644 index 00000000000..9b236682d54 --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/index.ts @@ -0,0 +1,5 @@ +export * from './bubble' +export * from './text' +export * from './image' +export * from './video' +export * from './embed' diff --git a/packages/models/src/typebot/steps/bubble/text.ts b/packages/models/src/typebot/steps/bubble/text.ts new file mode 100644 index 00000000000..c7ada487167 --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/text.ts @@ -0,0 +1,25 @@ +import { stepBaseSchema, BubbleStepType } from '../shared' +import { z } from 'zod' + +export const defaultTextBubbleContent: TextBubbleContent = { + html: '', + richText: [], + plainText: '', +} + +export const textBubbleContentSchema = z.object({ + html: z.string(), + richText: z.array(z.any()), + plainText: z.string(), +}) + +export type TextBubbleContent = z.infer + +export const textBubbleStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([BubbleStepType.TEXT]), + content: textBubbleContentSchema, + }) +) + +export type TextBubbleStep = z.infer diff --git a/packages/models/src/typebot/steps/bubble/video.ts b/packages/models/src/typebot/steps/bubble/video.ts new file mode 100644 index 00000000000..2715e1ebe7b --- /dev/null +++ b/packages/models/src/typebot/steps/bubble/video.ts @@ -0,0 +1,26 @@ +import { stepBaseSchema, BubbleStepType } from '../shared' +import { z } from 'zod' + +export enum VideoBubbleContentType { + URL = 'url', + YOUTUBE = 'youtube', + VIMEO = 'vimeo', +} + +export const videoBubbleContentSchema = z.object({ + url: z.string().optional(), + id: z.string().optional(), + type: z.nativeEnum(VideoBubbleContentType).optional(), +}) + +export const videoBubbleStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([BubbleStepType.VIDEO]), + content: videoBubbleContentSchema, + }) +) + +export const defaultVideoBubbleContent: VideoBubbleContent = {} + +export type VideoBubbleStep = z.infer +export type VideoBubbleContent = z.infer diff --git a/packages/models/src/typebot/steps/index.ts b/packages/models/src/typebot/steps/index.ts index e03aa24f17f..dae6d2ae7c7 100644 --- a/packages/models/src/typebot/steps/index.ts +++ b/packages/models/src/typebot/steps/index.ts @@ -1,6 +1,7 @@ export * from './steps' export * from './bubble' -export * from './inputs' +export * from './input' export * from './logic' export * from './integration' export * from './item' +export * from './shared' diff --git a/packages/models/src/typebot/steps/input/choice.ts b/packages/models/src/typebot/steps/input/choice.ts new file mode 100644 index 00000000000..36e1c07c9b7 --- /dev/null +++ b/packages/models/src/typebot/steps/input/choice.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' +import { + stepBaseSchema, + InputStepType, + defaultButtonLabel, + optionBaseSchema, + itemBaseSchema, + ItemType, +} from '../shared' + +export const choiceInputOptionsSchema = optionBaseSchema.and( + z.object({ + isMultipleChoice: z.boolean(), + buttonLabel: z.string(), + }) +) + +export const defaultChoiceInputOptions: ChoiceInputOptions = { + buttonLabel: defaultButtonLabel, + isMultipleChoice: false, +} + +export const choiceInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.CHOICE]), + items: z.array(z.any()), + options: choiceInputOptionsSchema, + }) +) + +export const buttonItemSchema = itemBaseSchema.and( + z.object({ + type: z.literal(ItemType.BUTTON), + content: z.string().optional(), + }) +) + +export type ButtonItem = z.infer +export type ChoiceInputStep = z.infer +export type ChoiceInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/date.ts b/packages/models/src/typebot/steps/input/date.ts new file mode 100644 index 00000000000..e8419d84428 --- /dev/null +++ b/packages/models/src/typebot/steps/input/date.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' +import { + stepBaseSchema, + InputStepType, + defaultButtonLabel, + optionBaseSchema, +} from '../shared' + +export const dateInputOptionsSchema = optionBaseSchema.and( + z.object({ + labels: z.object({ + button: z.string(), + from: z.string(), + to: z.string(), + }), + hasTime: z.boolean(), + isRange: z.boolean(), + }) +) + +export const dateInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.DATE]), + options: dateInputOptionsSchema, + }) +) + +export const defaultDateInputOptions: DateInputOptions = { + hasTime: false, + isRange: false, + labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' }, +} + +export type DateInputStep = z.infer +export type DateInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/email.ts b/packages/models/src/typebot/steps/input/email.ts new file mode 100644 index 00000000000..4e7354d033f --- /dev/null +++ b/packages/models/src/typebot/steps/input/email.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' +import { + defaultButtonLabel, + InputStepType, + optionBaseSchema, + stepBaseSchema, +} from '../shared' +import { textInputOptionsBaseSchema } from './text' + +export const emailInputOptionsSchema = optionBaseSchema + .and(textInputOptionsBaseSchema) + .and( + z.object({ + retryMessageContent: z.string(), + }) + ) + +export const emailInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.EMAIL]), + options: emailInputOptionsSchema, + }) +) + +export const defaultEmailInputOptions: EmailInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type your email...', + }, + retryMessageContent: + "This email doesn't seem to be valid. Can you type it again?", +} + +export type EmailInputStep = z.infer +export type EmailInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/index.ts b/packages/models/src/typebot/steps/input/index.ts new file mode 100644 index 00000000000..c3fa22def4b --- /dev/null +++ b/packages/models/src/typebot/steps/input/index.ts @@ -0,0 +1,9 @@ +export * from './input' +export * from './text' +export * from './email' +export * from './number' +export * from './url' +export * from './date' +export * from './choice' +export * from './payment' +export * from './phone' diff --git a/packages/models/src/typebot/steps/input/input.ts b/packages/models/src/typebot/steps/input/input.ts new file mode 100644 index 00000000000..c91fa6e61e5 --- /dev/null +++ b/packages/models/src/typebot/steps/input/input.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import { optionBaseSchema } from '../shared' +import { choiceInputOptionsSchema, choiceInputSchema } from './choice' +import { dateInputOptionsSchema, dateInputSchema } from './date' +import { emailInputOptionsSchema, emailInputSchema } from './email' +import { numberInputOptionsSchema, numberInputSchema } from './number' +import { paymentInputOptionsSchema, paymentInputSchema } from './payment' +import { + phoneNumberInputOptionsSchema, + phoneNumberInputStepSchema, +} from './phone' +import { textInputOptionsSchema, textInputSchema } from './text' +import { urlInputOptionsSchema, urlInputSchema } from './url' + +export type OptionBase = z.infer + +export const inputStepOptionsSchema = textInputOptionsSchema + .or(choiceInputOptionsSchema) + .or(emailInputOptionsSchema) + .or(numberInputOptionsSchema) + .or(urlInputOptionsSchema) + .or(phoneNumberInputOptionsSchema) + .or(dateInputOptionsSchema) + .or(paymentInputOptionsSchema) + +export const inputStepSchema = textInputSchema + .or(numberInputSchema) + .or(emailInputSchema) + .or(urlInputSchema) + .or(dateInputSchema) + .or(phoneNumberInputStepSchema) + .or(choiceInputSchema) + .or(paymentInputSchema) + +export type InputStep = z.infer +export type InputStepOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/number.ts b/packages/models/src/typebot/steps/input/number.ts new file mode 100644 index 00000000000..d93eeda199e --- /dev/null +++ b/packages/models/src/typebot/steps/input/number.ts @@ -0,0 +1,32 @@ +import { z } from 'zod' +import { + defaultButtonLabel, + InputStepType, + optionBaseSchema, + stepBaseSchema, +} from '../shared' +import { textInputOptionsBaseSchema } from './text' + +export const numberInputOptionsSchema = optionBaseSchema + .and(textInputOptionsBaseSchema) + .and( + z.object({ + min: z.number().optional(), + max: z.number().optional(), + step: z.number().optional(), + }) + ) + +export const numberInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.NUMBER]), + options: numberInputOptionsSchema, + }) +) + +export const defaultNumberInputOptions: NumberInputOptions = { + labels: { button: defaultButtonLabel, placeholder: 'Type a number...' }, +} + +export type NumberInputStep = z.infer +export type NumberInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/payment.ts b/packages/models/src/typebot/steps/input/payment.ts new file mode 100644 index 00000000000..5807853ac2f --- /dev/null +++ b/packages/models/src/typebot/steps/input/payment.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' +import { InputStepType, optionBaseSchema, stepBaseSchema } from '../shared' + +export type CreditCardDetails = { + number: string + exp_month: string + exp_year: string + cvc: string +} + +export enum PaymentProvider { + STRIPE = 'Stripe', +} + +export const paymentInputOptionsSchema = optionBaseSchema.and( + z.object({ + provider: z.nativeEnum(PaymentProvider), + labels: z.object({ + button: z.string(), + success: z.string().optional(), + }), + additionalInformation: z + .object({ + name: z.string().optional(), + email: z.string().optional(), + phoneNumber: z.string().optional(), + }) + .optional(), + credentialsId: z.string().optional(), + currency: z.string(), + amount: z.string().optional(), + }) +) + +export const paymentInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.PAYMENT]), + options: paymentInputOptionsSchema, + }) +) + +export const defaultPaymentInputOptions: PaymentInputOptions = { + provider: PaymentProvider.STRIPE, + labels: { button: 'Pay', success: 'Success' }, + currency: 'USD', +} + +export type PaymentInputStep = z.infer +export type PaymentInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/phone.ts b/packages/models/src/typebot/steps/input/phone.ts new file mode 100644 index 00000000000..b67c0e76ed4 --- /dev/null +++ b/packages/models/src/typebot/steps/input/phone.ts @@ -0,0 +1,38 @@ +import { z } from 'zod' +import { + defaultButtonLabel, + InputStepType, + optionBaseSchema, + stepBaseSchema, +} from '../shared' +import { textInputOptionsBaseSchema } from './text' + +export const phoneNumberInputOptionsSchema = optionBaseSchema + .and(textInputOptionsBaseSchema) + .and( + z.object({ + retryMessageContent: z.string(), + defaultCountryCode: z.string().optional(), + }) + ) + +export const phoneNumberInputStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.PHONE]), + options: phoneNumberInputOptionsSchema, + }) +) + +export const defaultPhoneInputOptions: PhoneNumberInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type your phone number...', + }, + retryMessageContent: + "This phone number doesn't seem to be valid. Can you type it again?", +} + +export type PhoneNumberInputStep = z.infer +export type PhoneNumberInputOptions = z.infer< + typeof phoneNumberInputOptionsSchema +> diff --git a/packages/models/src/typebot/steps/input/text.ts b/packages/models/src/typebot/steps/input/text.ts new file mode 100644 index 00000000000..e1b5a3bffaa --- /dev/null +++ b/packages/models/src/typebot/steps/input/text.ts @@ -0,0 +1,37 @@ +import { z } from 'zod' +import { + defaultButtonLabel, + InputStepType, + optionBaseSchema, + stepBaseSchema, +} from '../shared' + +export const textInputOptionsBaseSchema = z.object({ + labels: z.object({ + placeholder: z.string(), + button: z.string(), + }), +}) + +export const textInputOptionsSchema = textInputOptionsBaseSchema + .and(optionBaseSchema) + .and( + z.object({ + isLong: z.boolean(), + }) + ) + +export const defaultTextInputOptions: TextInputOptions = { + isLong: false, + labels: { button: defaultButtonLabel, placeholder: 'Type your answer...' }, +} + +export const textInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.TEXT]), + options: textInputOptionsSchema, + }) +) + +export type TextInputStep = z.infer +export type TextInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/input/url.ts b/packages/models/src/typebot/steps/input/url.ts new file mode 100644 index 00000000000..1a3267d13bb --- /dev/null +++ b/packages/models/src/typebot/steps/input/url.ts @@ -0,0 +1,35 @@ +import { z } from 'zod' +import { + defaultButtonLabel, + InputStepType, + optionBaseSchema, + stepBaseSchema, +} from '../shared' +import { textInputOptionsBaseSchema } from './text' + +export const urlInputOptionsSchema = optionBaseSchema + .and(textInputOptionsBaseSchema) + .and( + z.object({ + retryMessageContent: z.string(), + }) + ) + +export const urlInputSchema = stepBaseSchema.and( + z.object({ + type: z.enum([InputStepType.URL]), + options: urlInputOptionsSchema, + }) +) + +export const defaultUrlInputOptions: UrlInputOptions = { + labels: { + button: defaultButtonLabel, + placeholder: 'Type a URL...', + }, + retryMessageContent: + "This URL doesn't seem to be valid. Can you type it again?", +} + +export type UrlInputStep = z.infer +export type UrlInputOptions = z.infer diff --git a/packages/models/src/typebot/steps/inputs.ts b/packages/models/src/typebot/steps/inputs.ts deleted file mode 100644 index 28731a8138b..00000000000 --- a/packages/models/src/typebot/steps/inputs.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { ItemBase, ItemType } from '.' -import { StepBase } from './steps' - -export type InputStep = - | TextInputStep - | NumberInputStep - | EmailInputStep - | UrlInputStep - | DateInputStep - | PhoneNumberInputStep - | ChoiceInputStep - | PaymentInputStep - -export enum InputStepType { - TEXT = 'text input', - NUMBER = 'number input', - EMAIL = 'email input', - URL = 'url input', - DATE = 'date input', - PHONE = 'phone number input', - CHOICE = 'choice input', - PAYMENT = 'payment input', -} - -export type InputStepOptions = - | TextInputOptions - | NumberInputOptions - | EmailInputOptions - | DateInputOptions - | UrlInputOptions - | PhoneNumberInputOptions - | ChoiceInputOptions - | PaymentInputOptions - -export type TextInputStep = StepBase & { - type: InputStepType.TEXT - options: TextInputOptions -} - -export type NumberInputStep = StepBase & { - type: InputStepType.NUMBER - options: NumberInputOptions -} - -export type EmailInputStep = StepBase & { - type: InputStepType.EMAIL - options: EmailInputOptions -} - -export type UrlInputStep = StepBase & { - type: InputStepType.URL - options: UrlInputOptions -} - -export type DateInputStep = StepBase & { - type: InputStepType.DATE - options: DateInputOptions -} - -export type PhoneNumberInputStep = StepBase & { - type: InputStepType.PHONE - options: PhoneNumberInputOptions -} - -export type ChoiceInputStep = StepBase & { - type: InputStepType.CHOICE - items: ButtonItem[] - options: ChoiceInputOptions -} - -export type ButtonItem = ItemBase & { - type: ItemType.BUTTON - content?: string -} - -export type PaymentInputStep = StepBase & { - type: InputStepType.PAYMENT - options: PaymentInputOptions -} - -export type CreditCardDetails = { - number: string - exp_month: string - exp_year: string - cvc: string -} - -type OptionBase = { variableId?: string } - -type InputTextOptionsBase = { - labels: { placeholder: string; button: string } -} - -export type ChoiceInputOptions = OptionBase & { - isMultipleChoice: boolean - buttonLabel: string -} - -export type DateInputOptions = OptionBase & { - labels: { button: string; from: string; to: string } - hasTime: boolean - isRange: boolean -} - -export type EmailInputOptions = OptionBase & { - labels: { placeholder: string; button: string } - retryMessageContent: string -} - -export type UrlInputOptions = OptionBase & { - labels: { placeholder: string; button: string } - retryMessageContent: string -} - -export type PhoneNumberInputOptions = OptionBase & { - labels: { placeholder: string; button: string } - retryMessageContent: string - defaultCountryCode?: string -} - -export type TextInputOptions = OptionBase & - InputTextOptionsBase & { - isLong: boolean - } - -export type NumberInputOptions = OptionBase & - InputTextOptionsBase & { - min?: number - max?: number - step?: number - } - -export enum PaymentProvider { - STRIPE = 'Stripe', -} - -export type PaymentInputOptions = OptionBase & { - provider: PaymentProvider - amount?: string - currency: string - credentialsId?: string - additionalInformation?: { - name?: string - email?: string - phoneNumber?: string - } - labels: { button: string; success?: string } -} - -const defaultButtonLabel = 'Send' - -export const defaultTextInputOptions: TextInputOptions = { - isLong: false, - labels: { button: defaultButtonLabel, placeholder: 'Type your answer...' }, -} - -export const defaultNumberInputOptions: NumberInputOptions = { - labels: { button: defaultButtonLabel, placeholder: 'Type a number...' }, -} - -export const defaultEmailInputOptions: EmailInputOptions = { - labels: { - button: defaultButtonLabel, - placeholder: 'Type your email...', - }, - retryMessageContent: - "This email doesn't seem to be valid. Can you type it again?", -} - -export const defaultUrlInputOptions: UrlInputOptions = { - labels: { - button: defaultButtonLabel, - placeholder: 'Type a URL...', - }, - retryMessageContent: - "This URL doesn't seem to be valid. Can you type it again?", -} - -export const defaultDateInputOptions: DateInputOptions = { - hasTime: false, - isRange: false, - labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' }, -} - -export const defaultPhoneInputOptions: PhoneNumberInputOptions = { - labels: { - button: defaultButtonLabel, - placeholder: 'Type your phone number...', - }, - retryMessageContent: - "This phone number doesn't seem to be valid. Can you type it again?", -} - -export const defaultChoiceInputOptions: ChoiceInputOptions = { - buttonLabel: defaultButtonLabel, - isMultipleChoice: false, -} - -export const defaultPaymentInputOptions: PaymentInputOptions = { - provider: PaymentProvider.STRIPE, - labels: { button: 'Pay', success: 'Success' }, - currency: 'USD', -} diff --git a/packages/models/src/typebot/steps/integration.ts b/packages/models/src/typebot/steps/integration.ts deleted file mode 100644 index 6fcf51a6e7f..00000000000 --- a/packages/models/src/typebot/steps/integration.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { StepBase } from '.' - -export type IntegrationStep = - | GoogleSheetsStep - | GoogleAnalyticsStep - | WebhookStep - | SendEmailStep - | ZapierStep - | MakeComStep - | PabblyConnectStep - -export type IntegrationStepOptions = - | GoogleSheetsOptions - | GoogleAnalyticsOptions - | WebhookOptions - | SendEmailOptions - -export enum IntegrationStepType { - GOOGLE_SHEETS = 'Google Sheets', - GOOGLE_ANALYTICS = 'Google Analytics', - WEBHOOK = 'Webhook', - EMAIL = 'Email', - ZAPIER = 'Zapier', - MAKE_COM = 'Make.com', - PABBLY_CONNECT = 'Pabbly', -} - -export type GoogleSheetsStep = StepBase & { - type: IntegrationStepType.GOOGLE_SHEETS - options: GoogleSheetsOptions -} - -export type GoogleAnalyticsStep = StepBase & { - type: IntegrationStepType.GOOGLE_ANALYTICS - options: GoogleAnalyticsOptions -} - -export type WebhookStep = StepBase & { - type: IntegrationStepType.WEBHOOK - options: WebhookOptions - webhookId: string -} - -export type ZapierStep = Omit & { - type: IntegrationStepType.ZAPIER -} - -export type MakeComStep = Omit & { - type: IntegrationStepType.MAKE_COM -} - -export type PabblyConnectStep = Omit & { - type: IntegrationStepType.PABBLY_CONNECT -} - -export type SendEmailStep = StepBase & { - type: IntegrationStepType.EMAIL - options: SendEmailOptions -} - -export type SendEmailOptions = { - credentialsId: string | 'default' - recipients: string[] - replyTo?: string - cc?: string[] - bcc?: string[] - subject?: string - body?: string -} - -export type GoogleAnalyticsOptions = { - trackingId?: string - category?: string - action?: string - label?: string - value?: number -} - -export enum GoogleSheetsAction { - GET = 'Get data from sheet', - INSERT_ROW = 'Insert a row', - UPDATE_ROW = 'Update a row', -} - -export type GoogleSheetsOptions = - | GoogleSheetsOptionsBase - | GoogleSheetsGetOptions - | GoogleSheetsInsertRowOptions - | GoogleSheetsUpdateRowOptions - -export type GoogleSheetsOptionsBase = { - credentialsId?: string - spreadsheetId?: string - sheetId?: string -} - -export type Cell = { id: string; column?: string; value?: string } -export type ExtractingCell = { - id: string - column?: string - variableId?: string -} - -export type GoogleSheetsGetOptions = NonNullable & { - action: GoogleSheetsAction.GET - referenceCell?: Cell - cellsToExtract: ExtractingCell[] -} - -export type GoogleSheetsInsertRowOptions = - NonNullable & { - action: GoogleSheetsAction.INSERT_ROW - cellsToInsert: Cell[] - } - -export type GoogleSheetsUpdateRowOptions = - NonNullable & { - action: GoogleSheetsAction.UPDATE_ROW - referenceCell?: Cell - cellsToUpsert: Cell[] - } - -export type ResponseVariableMapping = { - id: string - bodyPath?: string - variableId?: string -} - -export type WebhookOptions = { - variablesForTest: VariableForTest[] - responseVariableMapping: ResponseVariableMapping[] - isAdvancedConfig?: boolean - isCustomBody?: boolean -} - -export type VariableForTest = { - id: string - variableId?: string - value?: string -} - -export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {} - -export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {} - -export const defaultWebhookOptions: Omit = { - responseVariableMapping: [], - variablesForTest: [], - isAdvancedConfig: false, - isCustomBody: false, -} - -export const defaultSendEmailOptions: SendEmailOptions = { - credentialsId: 'default', - recipients: [], -} diff --git a/packages/models/src/typebot/steps/integration/googleAnalytics.ts b/packages/models/src/typebot/steps/integration/googleAnalytics.ts new file mode 100644 index 00000000000..30677177746 --- /dev/null +++ b/packages/models/src/typebot/steps/integration/googleAnalytics.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' + +export const googleAnalyticsOptionsSchema = z.object({ + trackingId: z.string().optional(), + category: z.string().optional(), + action: z.string().optional(), + label: z.string().optional(), + value: z.number().optional(), +}) + +export const googleAnalyticsStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.GOOGLE_ANALYTICS]), + options: googleAnalyticsOptionsSchema, + }) +) + +export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {} + +export type GoogleAnalyticsStep = z.infer +export type GoogleAnalyticsOptions = z.infer< + typeof googleAnalyticsOptionsSchema +> diff --git a/packages/models/src/typebot/steps/integration/googleSheets.ts b/packages/models/src/typebot/steps/integration/googleSheets.ts new file mode 100644 index 00000000000..43002fe09bb --- /dev/null +++ b/packages/models/src/typebot/steps/integration/googleSheets.ts @@ -0,0 +1,80 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' + +export enum GoogleSheetsAction { + GET = 'Get data from sheet', + INSERT_ROW = 'Insert a row', + UPDATE_ROW = 'Update a row', +} + +const cellSchema = z.object({ + column: z.string().optional(), + value: z.string().optional(), + id: z.string(), +}) + +const extractingCellSchema = z.object({ + column: z.string().optional(), + id: z.string(), + variableId: z.string().optional(), +}) + +const googleSheetsOptionsBaseSchema = z.object({ + credentialsId: z.string().optional(), + sheetId: z.string().optional(), + spreadsheetId: z.string().optional(), +}) + +const googleSheetsGetOptionsSchema = googleSheetsOptionsBaseSchema.and( + z.object({ + action: z.enum([GoogleSheetsAction.GET]), + referenceCell: cellSchema.optional(), + cellsToExtract: z.array(extractingCellSchema), + }) +) + +const googleSheetsInsertRowOptionsSchema = googleSheetsOptionsBaseSchema.and( + z.object({ + action: z.enum([GoogleSheetsAction.INSERT_ROW]), + cellsToInsert: z.array(cellSchema), + }) +) + +const googleSheetsUpdateRowOptionsSchema = googleSheetsOptionsBaseSchema.and( + z.object({ + action: z.enum([GoogleSheetsAction.UPDATE_ROW]), + cellsToUpsert: z.array(cellSchema), + referenceCell: cellSchema.optional(), + }) +) + +export const googleSheetsOptionsSchema = googleSheetsOptionsBaseSchema + .or(googleSheetsGetOptionsSchema) + .or(googleSheetsInsertRowOptionsSchema) + .or(googleSheetsUpdateRowOptionsSchema) + +export const googleSheetsStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.GOOGLE_SHEETS]), + options: googleSheetsOptionsSchema, + }) +) + +export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {} + +export type GoogleSheetsStep = z.infer +export type GoogleSheetsOptions = z.infer +export type GoogleSheetsOptionsBase = z.infer< + typeof googleSheetsOptionsBaseSchema +> +export type GoogleSheetsGetOptions = z.infer< + typeof googleSheetsGetOptionsSchema +> +export type GoogleSheetsInsertRowOptions = z.infer< + typeof googleSheetsInsertRowOptionsSchema +> +export type GoogleSheetsUpdateRowOptions = z.infer< + typeof googleSheetsUpdateRowOptionsSchema +> +export type Cell = z.infer +export type ExtractingCell = z.infer diff --git a/packages/models/src/typebot/steps/integration/index.ts b/packages/models/src/typebot/steps/integration/index.ts new file mode 100644 index 00000000000..bffe978d6a6 --- /dev/null +++ b/packages/models/src/typebot/steps/integration/index.ts @@ -0,0 +1,8 @@ +export * from './integration' +export * from './webhook' +export * from './googleAnalytics' +export * from './pabblyConnect' +export * from './makeCom' +export * from './zapier' +export * from './sendEmail' +export * from './googleSheets' diff --git a/packages/models/src/typebot/steps/integration/integration.ts b/packages/models/src/typebot/steps/integration/integration.ts new file mode 100644 index 00000000000..344b19a0e71 --- /dev/null +++ b/packages/models/src/typebot/steps/integration/integration.ts @@ -0,0 +1,32 @@ +import { z } from 'zod' +import { + googleAnalyticsOptionsSchema, + googleAnalyticsStepSchema, +} from './googleAnalytics' +import { + googleSheetsOptionsSchema, + googleSheetsStepSchema, +} from './googleSheets' +import { makeComStepSchema } from './makeCom' +import { pabblyConnectStepSchema } from './pabblyConnect' +import { sendEmailOptionsSchema, sendEmailStepSchema } from './sendEmail' +import { webhookOptionsSchema, webhookStepSchema } from './webhook' +import { zapierStepSchema } from './zapier' + +const integrationStepOptionsSchema = googleSheetsOptionsSchema + .or(googleAnalyticsOptionsSchema) + .or(webhookOptionsSchema) + .or(sendEmailOptionsSchema) + +export const integrationStepSchema = googleSheetsStepSchema + .or(googleAnalyticsStepSchema) + .or(webhookStepSchema) + .or(sendEmailStepSchema) + .or(zapierStepSchema) + .or(makeComStepSchema) + .or(pabblyConnectStepSchema) + +export type IntegrationStep = z.infer +export type IntegrationStepOptions = z.infer< + typeof integrationStepOptionsSchema +> diff --git a/packages/models/src/typebot/steps/integration/makeCom.ts b/packages/models/src/typebot/steps/integration/makeCom.ts new file mode 100644 index 00000000000..e3809f9295b --- /dev/null +++ b/packages/models/src/typebot/steps/integration/makeCom.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' +import { webhookOptionsSchema } from './webhook' + +export const makeComStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.MAKE_COM]), + options: webhookOptionsSchema, + webhookId: z.string(), + }) +) + +export type MakeComStep = z.infer diff --git a/packages/models/src/typebot/steps/integration/pabblyConnect.ts b/packages/models/src/typebot/steps/integration/pabblyConnect.ts new file mode 100644 index 00000000000..66909b6185b --- /dev/null +++ b/packages/models/src/typebot/steps/integration/pabblyConnect.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' +import { webhookOptionsSchema } from './webhook' + +export const pabblyConnectStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.PABBLY_CONNECT]), + options: webhookOptionsSchema, + webhookId: z.string(), + }) +) + +export type PabblyConnectStep = z.infer diff --git a/packages/models/src/typebot/steps/integration/sendEmail.ts b/packages/models/src/typebot/steps/integration/sendEmail.ts new file mode 100644 index 00000000000..ca7df2c4ccb --- /dev/null +++ b/packages/models/src/typebot/steps/integration/sendEmail.ts @@ -0,0 +1,27 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' + +export const sendEmailOptionsSchema = z.object({ + credentialsId: z.string(), + recipients: z.array(z.string()), + subject: z.string().optional(), + body: z.string().optional(), + replyTo: z.string().optional(), + cc: z.array(z.string()).optional(), + bcc: z.array(z.string()).optional(), +}) + +export const sendEmailStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.EMAIL]), + options: sendEmailOptionsSchema, + }) +) + +export const defaultSendEmailOptions: SendEmailOptions = { + credentialsId: 'default', + recipients: [], +} + +export type SendEmailStep = z.infer +export type SendEmailOptions = z.infer diff --git a/packages/models/src/typebot/steps/integration/webhook.ts b/packages/models/src/typebot/steps/integration/webhook.ts new file mode 100644 index 00000000000..c57cc4107cc --- /dev/null +++ b/packages/models/src/typebot/steps/integration/webhook.ts @@ -0,0 +1,43 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' + +const variableForTestSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + value: z.string().optional(), +}) + +const responseVariableMappingSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + bodyPath: z.string().optional(), +}) + +export const webhookOptionsSchema = z.object({ + variablesForTest: z.array(variableForTestSchema), + responseVariableMapping: z.array(responseVariableMappingSchema), + isAdvancedConfig: z.boolean().optional(), + isCustomBody: z.boolean().optional(), +}) + +export const webhookStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.WEBHOOK]), + options: webhookOptionsSchema, + webhookId: z.string(), + }) +) + +export const defaultWebhookOptions: Omit = { + responseVariableMapping: [], + variablesForTest: [], + isAdvancedConfig: false, + isCustomBody: false, +} + +export type WebhookStep = z.infer +export type WebhookOptions = z.infer +export type ResponseVariableMapping = z.infer< + typeof responseVariableMappingSchema +> +export type VariableForTest = z.infer diff --git a/packages/models/src/typebot/steps/integration/zapier.ts b/packages/models/src/typebot/steps/integration/zapier.ts new file mode 100644 index 00000000000..e2ddf6b05b3 --- /dev/null +++ b/packages/models/src/typebot/steps/integration/zapier.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' +import { IntegrationStepType, stepBaseSchema } from '../shared' +import { webhookOptionsSchema } from './webhook' + +export const zapierStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([IntegrationStepType.ZAPIER]), + options: webhookOptionsSchema, + webhookId: z.string(), + }) +) + +export type ZapierStep = z.infer diff --git a/packages/models/src/typebot/steps/item.ts b/packages/models/src/typebot/steps/item.ts index 1b4f498a063..ed61dfb345d 100644 --- a/packages/models/src/typebot/steps/item.ts +++ b/packages/models/src/typebot/steps/item.ts @@ -1,20 +1,14 @@ -import { ButtonItem, ConditionItem } from '.' - -export type Item = ButtonItem | ConditionItem - -export enum ItemType { - BUTTON, - CONDITION, -} - -export type ItemBase = { - id: string - stepId: string - outgoingEdgeId?: string -} +import { z } from 'zod' +import { itemBaseSchema } from './shared' +import { buttonItemSchema } from './input' +import { conditionItemSchema } from './logic' export type ItemIndices = { blockIndex: number stepIndex: number itemIndex: number } +const itemScema = buttonItemSchema.or(conditionItemSchema) + +export type ItemBase = z.infer +export type Item = z.infer diff --git a/packages/models/src/typebot/steps/logic.ts b/packages/models/src/typebot/steps/logic.ts deleted file mode 100644 index 3caa9eee962..00000000000 --- a/packages/models/src/typebot/steps/logic.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { ItemType, StepBase } from '.' -import { ItemBase } from './item' - -export type LogicStep = - | SetVariableStep - | ConditionStep - | RedirectStep - | CodeStep - | TypebotLinkStep - -export type LogicStepOptions = - | SetVariableOptions - | RedirectOptions - | CodeOptions - | TypebotLinkOptions - -export enum LogicStepType { - SET_VARIABLE = 'Set variable', - CONDITION = 'Condition', - REDIRECT = 'Redirect', - CODE = 'Code', - TYPEBOT_LINK = 'Typebot link', -} - -export type SetVariableStep = StepBase & { - type: LogicStepType.SET_VARIABLE - options: SetVariableOptions -} - -export type ConditionStep = StepBase & { - type: LogicStepType.CONDITION - items: [ConditionItem] -} - -export type ConditionItem = ItemBase & { - type: ItemType.CONDITION - content: ConditionContent -} - -export type RedirectStep = StepBase & { - type: LogicStepType.REDIRECT - options: RedirectOptions -} - -export type CodeStep = StepBase & { - type: LogicStepType.CODE - options: CodeOptions -} - -export type TypebotLinkStep = StepBase & { - type: LogicStepType.TYPEBOT_LINK - options: TypebotLinkOptions -} - -export enum LogicalOperator { - OR = 'OR', - AND = 'AND', -} - -export enum ComparisonOperators { - EQUAL = 'Equal to', - NOT_EQUAL = 'Not equal', - CONTAINS = 'Contains', - GREATER = 'Greater than', - LESS = 'Less than', - IS_SET = 'Is set', -} - -export type ConditionContent = { - comparisons: Comparison[] - logicalOperator: LogicalOperator -} - -export type Comparison = { - id: string - variableId?: string - comparisonOperator?: ComparisonOperators - value?: string -} - -export type SetVariableOptions = { - variableId?: string - expressionToEvaluate?: string - isCode?: boolean -} - -export type RedirectOptions = { - url?: string - isNewTab: boolean -} - -export type CodeOptions = { - name: string - content?: string -} - -export type TypebotLinkOptions = { - typebotId?: string | 'current' - blockId?: string -} - -export const defaultSetVariablesOptions: SetVariableOptions = {} - -export const defaultConditionContent: ConditionContent = { - comparisons: [], - logicalOperator: LogicalOperator.AND, -} - -export const defaultRedirectOptions: RedirectOptions = { isNewTab: false } - -export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' } - -export const defaultTypebotLinkOptions: TypebotLinkOptions = {} diff --git a/packages/models/src/typebot/steps/logic/code.ts b/packages/models/src/typebot/steps/logic/code.ts new file mode 100644 index 00000000000..96160920a75 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/code.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { LogicStepType, stepBaseSchema } from '../shared' + +export const codeOptionsSchema = z.object({ + name: z.string(), + content: z.string().optional(), +}) + +export const codeStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([LogicStepType.CODE]), + options: codeOptionsSchema, + }) +) + +export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' } + +export type CodeStep = z.infer +export type CodeOptions = z.infer diff --git a/packages/models/src/typebot/steps/logic/condition.ts b/packages/models/src/typebot/steps/logic/condition.ts new file mode 100644 index 00000000000..3a06dbbdc64 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/condition.ts @@ -0,0 +1,58 @@ +import { z } from 'zod' +import { + itemBaseSchema, + ItemType, + LogicStepType, + stepBaseSchema, +} from '../shared' + +export enum LogicalOperator { + OR = 'OR', + AND = 'AND', +} + +export enum ComparisonOperators { + EQUAL = 'Equal to', + NOT_EQUAL = 'Not equal', + CONTAINS = 'Contains', + GREATER = 'Greater than', + LESS = 'Less than', + IS_SET = 'Is set', +} + +const comparisonSchema = z.object({ + id: z.string(), + variableId: z.string().optional(), + comparisonOperator: z.nativeEnum(ComparisonOperators).optional(), + value: z.string().optional(), +}) + +const conditionContentSchema = z.object({ + logicalOperator: z.nativeEnum(LogicalOperator), + comparisons: z.array(comparisonSchema), +}) + +export const conditionItemSchema = itemBaseSchema.and( + z.object({ + type: z.literal(ItemType.CONDITION), + content: conditionContentSchema, + }) +) + +export const conditionStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([LogicStepType.CONDITION]), + items: z.array(conditionItemSchema), + options: z.object({}), + }) +) + +export const defaultConditionContent: ConditionContent = { + comparisons: [], + logicalOperator: LogicalOperator.AND, +} + +export type ConditionItem = z.infer +export type Comparison = z.infer +export type ConditionStep = z.infer +export type ConditionContent = z.infer diff --git a/packages/models/src/typebot/steps/logic/index.ts b/packages/models/src/typebot/steps/logic/index.ts new file mode 100644 index 00000000000..49c06ebdc97 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/index.ts @@ -0,0 +1,6 @@ +export * from './logic' +export * from './code' +export * from './condition' +export * from './redirect' +export * from './setVariable' +export * from './typebotLink' diff --git a/packages/models/src/typebot/steps/logic/logic.ts b/packages/models/src/typebot/steps/logic/logic.ts new file mode 100644 index 00000000000..1861a378c15 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/logic.ts @@ -0,0 +1,20 @@ +import { z } from 'zod' +import { codeOptionsSchema, codeStepSchema } from './code' +import { conditionStepSchema } from './condition' +import { redirectOptionsSchema, redirectStepSchema } from './redirect' +import { setVariableOptionsSchema, setVariableStepSchema } from './setVariable' +import { typebotLinkOptionsSchema, typebotLinkStepSchema } from './typebotLink' + +const logicStepOptionsSchema = codeOptionsSchema + .or(redirectOptionsSchema) + .or(setVariableOptionsSchema) + .or(typebotLinkOptionsSchema) + +export const logicStepSchema = codeStepSchema + .or(conditionStepSchema) + .or(redirectStepSchema) + .or(typebotLinkStepSchema) + .or(setVariableStepSchema) + +export type LogicStep = z.infer +export type LogicStepOptions = z.infer diff --git a/packages/models/src/typebot/steps/logic/redirect.ts b/packages/models/src/typebot/steps/logic/redirect.ts new file mode 100644 index 00000000000..66f8522cb56 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/redirect.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { LogicStepType, stepBaseSchema } from '../shared' + +export const redirectOptionsSchema = z.object({ + url: z.string().optional(), + isNewTab: z.boolean(), +}) + +export const redirectStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([LogicStepType.REDIRECT]), + options: redirectOptionsSchema, + }) +) + +export const defaultRedirectOptions: RedirectOptions = { isNewTab: false } + +export type RedirectStep = z.infer +export type RedirectOptions = z.infer diff --git a/packages/models/src/typebot/steps/logic/setVariable.ts b/packages/models/src/typebot/steps/logic/setVariable.ts new file mode 100644 index 00000000000..842a0fdc853 --- /dev/null +++ b/packages/models/src/typebot/steps/logic/setVariable.ts @@ -0,0 +1,20 @@ +import { z } from 'zod' +import { LogicStepType, stepBaseSchema } from '../shared' + +export const setVariableOptionsSchema = z.object({ + variableId: z.string().optional(), + expressionToEvaluate: z.string().optional(), + isCode: z.boolean().optional(), +}) + +export const setVariableStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([LogicStepType.SET_VARIABLE]), + options: setVariableOptionsSchema, + }) +) + +export const defaultSetVariablesOptions: SetVariableOptions = {} + +export type SetVariableStep = z.infer +export type SetVariableOptions = z.infer diff --git a/packages/models/src/typebot/steps/logic/typebotLink.ts b/packages/models/src/typebot/steps/logic/typebotLink.ts new file mode 100644 index 00000000000..59fa9e40f5f --- /dev/null +++ b/packages/models/src/typebot/steps/logic/typebotLink.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { LogicStepType, stepBaseSchema } from '../shared' + +export const typebotLinkOptionsSchema = z.object({ + typebotId: z.string().optional(), + blockId: z.string().optional(), +}) + +export const typebotLinkStepSchema = stepBaseSchema.and( + z.object({ + type: z.enum([LogicStepType.TYPEBOT_LINK]), + options: typebotLinkOptionsSchema, + }) +) + +export const defaultTypebotLinkOptions: TypebotLinkOptions = {} + +export type TypebotLinkStep = z.infer +export type TypebotLinkOptions = z.infer diff --git a/packages/models/src/typebot/steps/shared.ts b/packages/models/src/typebot/steps/shared.ts new file mode 100644 index 00000000000..6335e6b68fa --- /dev/null +++ b/packages/models/src/typebot/steps/shared.ts @@ -0,0 +1,60 @@ +import { z } from 'zod' + +export const stepBaseSchema = z.object({ + id: z.string(), + blockId: z.string(), + outgoingEdgeId: z.string().optional(), +}) + +export const defaultButtonLabel = 'Send' + +export const optionBaseSchema = z.object({ + variableId: z.string().optional(), +}) + +export const itemBaseSchema = z.object({ + id: z.string(), + stepId: z.string(), + outgoingEdgeId: z.string().optional(), +}) + +export enum ItemType { + BUTTON, + CONDITION, +} + +export enum BubbleStepType { + TEXT = 'text', + IMAGE = 'image', + VIDEO = 'video', + EMBED = 'embed', +} + +export enum InputStepType { + TEXT = 'text input', + NUMBER = 'number input', + EMAIL = 'email input', + URL = 'url input', + DATE = 'date input', + PHONE = 'phone number input', + CHOICE = 'choice input', + PAYMENT = 'payment input', +} + +export enum LogicStepType { + SET_VARIABLE = 'Set variable', + CONDITION = 'Condition', + REDIRECT = 'Redirect', + CODE = 'Code', + TYPEBOT_LINK = 'Typebot link', +} + +export enum IntegrationStepType { + GOOGLE_SHEETS = 'Google Sheets', + GOOGLE_ANALYTICS = 'Google Analytics', + WEBHOOK = 'Webhook', + EMAIL = 'Email', + ZAPIER = 'Zapier', + MAKE_COM = 'Make.com', + PABBLY_CONNECT = 'Pabbly', +} diff --git a/packages/models/src/typebot/steps/steps.ts b/packages/models/src/typebot/steps/steps.ts index 8d3fb42adb7..9dceaa91018 100644 --- a/packages/models/src/typebot/steps/steps.ts +++ b/packages/models/src/typebot/steps/steps.ts @@ -1,21 +1,21 @@ import { InputStepOptions, IntegrationStepOptions, - IntegrationStepType, Item, LogicStepOptions, } from '.' -import { BubbleStep, BubbleStepType } from './bubble' -import { InputStep, InputStepType } from './inputs' -import { IntegrationStep } from './integration' -import { ConditionStep, LogicStep, LogicStepType } from './logic' - -export type Step = - | StartStep - | BubbleStep - | InputStep - | LogicStep - | IntegrationStep +import { BubbleStep, bubbleStepSchema } from './bubble' +import { InputStep, inputStepSchema } from './input' +import { IntegrationStep, integrationStepSchema } from './integration' +import { ConditionStep, LogicStep, logicStepSchema } from './logic' +import { z } from 'zod' +import { + BubbleStepType, + InputStepType, + IntegrationStepType, + LogicStepType, + stepBaseSchema, +} from './shared' export type DraggableStep = BubbleStep | InputStep | LogicStep | IntegrationStep @@ -49,14 +49,26 @@ export type StepOptions = export type StepWithItems = Omit & { items: Item[] } -export type StepBase = { id: string; blockId: string; outgoingEdgeId?: string } +export type StepBase = z.infer -export type StartStep = StepBase & { - type: 'start' - label: string -} +const startStepSchema = stepBaseSchema.and( + z.object({ + type: z.literal('start'), + label: z.string(), + }) +) + +export type StartStep = z.infer export type StepIndices = { blockIndex: number stepIndex: number } + +export const stepSchema = startStepSchema + .or(bubbleStepSchema) + .or(inputStepSchema) + .or(logicStepSchema) + .or(integrationStepSchema) + +export type Step = z.infer diff --git a/packages/models/src/typebot/theme.ts b/packages/models/src/typebot/theme.ts index b32489da8cd..7faf66650dd 100644 --- a/packages/models/src/typebot/theme.ts +++ b/packages/models/src/typebot/theme.ts @@ -1,36 +1,29 @@ -export type Theme = { - general: GeneralTheme - chat: ChatTheme - customCss?: string -} +import { z } from 'zod' -export type GeneralTheme = { - font: string - background: Background -} +const avatarPropsSchema = z.object({ + isEnabled: z.boolean(), + url: z.string().optional(), +}) -export type AvatarProps = { - isEnabled: boolean - url?: string -} +const containerColorsSchema = z.object({ + backgroundColor: z.string(), + color: z.string(), +}) -export type ChatTheme = { - hostAvatar?: AvatarProps - guestAvatar?: AvatarProps - hostBubbles: ContainerColors - guestBubbles: ContainerColors - buttons: ContainerColors - inputs: InputColors -} +const inputColorsSchema = containerColorsSchema.and( + z.object({ + placeholderColor: z.string(), + }) +) -export type ContainerColors = { - backgroundColor: string - color: string -} - -export type InputColors = ContainerColors & { - placeholderColor: string -} +const chatThemeSchema = z.object({ + hostAvatar: avatarPropsSchema.optional(), + guestAvatar: avatarPropsSchema.optional(), + hostBubbles: containerColorsSchema, + guestBubbles: containerColorsSchema, + buttons: containerColorsSchema, + inputs: inputColorsSchema, +}) export enum BackgroundType { COLOR = 'Color', @@ -38,10 +31,21 @@ export enum BackgroundType { NONE = 'None', } -export type Background = { - type: BackgroundType - content?: string -} +const backgroundSchema = z.object({ + type: z.nativeEnum(BackgroundType), + content: z.string().optional(), +}) + +const generalThemeSchema = z.object({ + font: z.string(), + background: backgroundSchema, +}) + +export const themeSchema = z.object({ + general: generalThemeSchema, + chat: chatThemeSchema, + customCss: z.string().optional(), +}) export const defaultTheme: Theme = { chat: { @@ -56,3 +60,11 @@ export const defaultTheme: Theme = { }, general: { font: 'Open Sans', background: { type: BackgroundType.NONE } }, } + +export type Theme = z.infer +export type ChatTheme = z.infer +export type AvatarProps = z.infer +export type GeneralTheme = z.infer +export type Background = z.infer +export type ContainerColors = z.infer +export type InputColors = z.infer diff --git a/packages/models/src/typebot/typebot.ts b/packages/models/src/typebot/typebot.ts index f27e6d941b0..03b2ccf73b9 100644 --- a/packages/models/src/typebot/typebot.ts +++ b/packages/models/src/typebot/typebot.ts @@ -1,46 +1,56 @@ -import { Typebot as TypebotFromPrisma } from 'db' -import { Settings } from './settings' -import { Step } from './steps/steps' -import { Theme } from './theme' -import { Variable } from './variable' +import { z } from 'zod' +import { settingsSchema } from './settings' +import { stepSchema } from './steps' +import { themeSchema } from './theme' +import { variableSchema } from './variable' -export type Typebot = Omit< - TypebotFromPrisma, - | 'blocks' - | 'theme' - | 'settings' - | 'variables' - | 'edges' - | 'createdAt' - | 'updatedAt' -> & { - blocks: Block[] - variables: Variable[] - edges: Edge[] - theme: Theme - settings: Settings - createdAt: string - updatedAt: string -} +const blockSchema = z.object({ + id: z.string(), + title: z.string(), + graphCoordinates: z.object({ + x: z.number(), + y: z.number(), + }), + steps: z.array(stepSchema), +}) -export type Block = { - id: string - title: string - graphCoordinates: { - x: number - y: number - } - steps: Step[] -} +const sourceSchema = z.object({ + blockId: z.string(), + stepId: z.string(), + itemId: z.string().optional(), +}) -export type Source = { - blockId: string - stepId: string - itemId?: string -} -export type Target = { blockId: string; stepId?: string } -export type Edge = { - id: string - from: Source - to: Target -} +const targetSchema = z.object({ + blockId: z.string(), + stepId: z.string().optional(), +}) + +const edgeSchema = z.object({ + id: z.string(), + from: sourceSchema, + to: targetSchema, +}) + +const typebotSchema = z.object({ + id: z.string(), + name: z.string(), + blocks: z.array(blockSchema), + edges: z.array(edgeSchema), + variables: z.array(variableSchema), + theme: themeSchema, + settings: settingsSchema, + createdAt: z.string(), + updatedAt: z.string(), + icon: z.string().nullable(), + publishedTypebotId: z.string().nullable(), + folderId: z.string().nullable(), + publicId: z.string().nullable(), + customDomain: z.string().nullable(), + workspaceId: z.string(), +}) + +export type Typebot = z.infer +export type Target = z.infer +export type Source = z.infer +export type Edge = z.infer +export type Block = z.infer diff --git a/packages/models/src/typebot/variable.ts b/packages/models/src/typebot/variable.ts index 2097cfc1a6e..89985e168ba 100644 --- a/packages/models/src/typebot/variable.ts +++ b/packages/models/src/typebot/variable.ts @@ -1,9 +1,12 @@ -export type Variable = { - id: string - name: string - value?: string | number -} +import { z } from 'zod' + +export const variableSchema = z.object({ + id: z.string(), + name: z.string(), + value: z.string().or(z.number()).optional(), +}) export type VariableWithValue = Omit & { value: string } +export type Variable = z.infer diff --git a/packages/models/tsconfig.json b/packages/models/tsconfig.json index e43969c2d0b..ce02a5c63d3 100644 --- a/packages/models/tsconfig.json +++ b/packages/models/tsconfig.json @@ -1,13 +1,17 @@ { "compilerOptions": { - "target": "es2016", - "module": "commonjs", + "target": "es5", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, + "module": "ESNext", "declaration": true, - "declarationDir": "./dist/types", - "outDir": "./dist" + "declarationDir": "types", + "sourceMap": true, + "outDir": "dist", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "emitDeclarationOnly": true } } diff --git a/packages/utils/package.json b/packages/utils/package.json index ffb3494dd5a..107fae809b6 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -20,6 +20,10 @@ "models": "*", "next": "^12.1.6" }, + "peerDependencies": { + "next": "^12.1.6", + "models": "*" + }, "scripts": { "build": "yarn rollup -c", "dx": "yarn rollup -c --watch" diff --git a/packages/utils/rollup.config.js b/packages/utils/rollup.config.js index 2e792339843..96c951fd367 100644 --- a/packages/utils/rollup.config.js +++ b/packages/utils/rollup.config.js @@ -25,7 +25,7 @@ export default [ ], plugins: [ peerDepsExternal(), - resolve({ preferBuiltins: true }), + resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json' }), ], diff --git a/packages/utils/src/api/utils.ts b/packages/utils/src/api/utils.ts index 90e2cddd53b..6c4be2ec849 100644 --- a/packages/utils/src/api/utils.ts +++ b/packages/utils/src/api/utils.ts @@ -1,19 +1,23 @@ import { NextApiRequest, NextApiResponse } from 'next' -export const methodNotAllowed = (res: NextApiResponse) => - res.status(405).json({ message: 'Method Not Allowed' }) +export const methodNotAllowed = ( + res: NextApiResponse, + customMessage?: string +) => res.status(405).json({ message: customMessage ?? 'Method Not Allowed' }) -export const notAuthenticated = (res: NextApiResponse) => - res.status(401).json({ message: 'Not authenticated' }) +export const notAuthenticated = ( + res: NextApiResponse, + customMessage?: string +) => res.status(401).json({ message: customMessage ?? 'Not authenticated' }) -export const notFound = (res: NextApiResponse) => - res.status(404).json({ message: 'Not found' }) +export const notFound = (res: NextApiResponse, customMessage?: string) => + res.status(404).json({ message: customMessage ?? 'Not found' }) -export const badRequest = (res: NextApiResponse) => - res.status(400).json({ message: 'Bad Request' }) +export const badRequest = (res: NextApiResponse, customMessage?: any) => + res.status(400).json({ message: customMessage ?? 'Bad Request' }) -export const forbidden = (res: NextApiResponse) => - res.status(403).json({ message: 'Bad Request' }) +export const forbidden = (res: NextApiResponse, customMessage?: string) => + res.status(403).json({ message: customMessage ?? 'Bad Request' }) export const initMiddleware = ( diff --git a/yarn.lock b/yarn.lock index b35f84e2370..b87c7a873ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15406,6 +15406,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zod@^3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.17.3.tgz#86abbc670ff0063a4588d85a4dcc917d6e4af2ba" + integrity sha512-4oKP5zvG6GGbMlqBkI5FESOAweldEhSOZ6LI6cG+JzUT7ofj1ZOC0PJudpQOpT1iqOFpYYtX5Pw0+o403y4bcg== + zustand@^3.4.2, zustand@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d"