2
0

(engine) Improve engine overall robustness

This commit is contained in:
Baptiste Arnaud
2023-01-25 11:27:47 +01:00
parent ff62b922a0
commit 30baa611e5
210 changed files with 1820 additions and 1919 deletions

View File

@ -0,0 +1,42 @@
import { z } from 'zod'
import { schemaForType } from './utils'
import { Answer as AnswerPrisma, Prisma } from 'db'
export const answerSchema = schemaForType<AnswerPrisma>()(
z.object({
createdAt: z.date(),
resultId: z.string(),
blockId: z.string(),
groupId: z.string(),
variableId: z.string().nullable(),
content: z.string(),
storageUsed: z.number().nullable(),
})
)
export const answerInputSchema =
schemaForType<Prisma.AnswerUncheckedUpdateInput>()(
answerSchema
.omit({
createdAt: true,
resultId: true,
variableId: true,
storageUsed: true,
})
.and(
z.object({
variableId: z.string().nullish(),
storageUsed: z.number().nullish(),
})
)
)
export type Stats = {
totalViews: number
totalStarts: number
totalCompleted: number
}
export type Answer = z.infer<typeof answerSchema>
export type AnswerInput = z.infer<typeof answerInputSchema>

View File

@ -0,0 +1,11 @@
import { z } from 'zod'
export const blockBaseSchema = z.object({
id: z.string(),
groupId: z.string(),
outgoingEdgeId: z.string().optional(),
})
export const optionBaseSchema = z.object({
variableId: z.string().optional(),
})

View File

@ -0,0 +1,19 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { BubbleBlockType } from './enums'
export const audioBubbleContentSchema = z.object({
url: z.string().optional(),
})
export const audioBubbleBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([BubbleBlockType.AUDIO]),
content: audioBubbleContentSchema,
})
)
export const defaultAudioBubbleContent = {}
export type AudioBubbleBlock = z.infer<typeof audioBubbleBlockSchema>
export type AudioBubbleContent = z.infer<typeof audioBubbleContentSchema>

View File

@ -0,0 +1,20 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { BubbleBlockType } from './enums'
export const embedBubbleContentSchema = z.object({
url: z.string().optional(),
height: z.number(),
})
export const embedBubbleBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([BubbleBlockType.EMBED]),
content: embedBubbleContentSchema,
})
)
export const defaultEmbedBubbleContent: EmbedBubbleContent = { height: 400 }
export type EmbedBubbleBlock = z.infer<typeof embedBubbleBlockSchema>
export type EmbedBubbleContent = z.infer<typeof embedBubbleContentSchema>

View File

@ -0,0 +1,7 @@
export enum BubbleBlockType {
TEXT = 'text',
IMAGE = 'image',
VIDEO = 'video',
EMBED = 'embed',
AUDIO = 'audio',
}

View File

@ -0,0 +1,19 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { BubbleBlockType } from './enums'
export const imageBubbleContentSchema = z.object({
url: z.string().optional(),
})
export const imageBubbleBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([BubbleBlockType.IMAGE]),
content: imageBubbleContentSchema,
})
)
export const defaultImageBubbleContent: ImageBubbleContent = {}
export type ImageBubbleBlock = z.infer<typeof imageBubbleBlockSchema>
export type ImageBubbleContent = z.infer<typeof imageBubbleContentSchema>

View File

@ -0,0 +1,7 @@
export * from './audio'
export * from './embed'
export * from './enums'
export * from './image'
export * from './schemas'
export * from './text'
export * from './video'

View File

@ -0,0 +1,21 @@
import { z } from 'zod'
import { audioBubbleBlockSchema, audioBubbleContentSchema } from './audio'
import { embedBubbleContentSchema, embedBubbleBlockSchema } from './embed'
import { imageBubbleContentSchema, imageBubbleBlockSchema } from './image'
import { textBubbleContentSchema, textBubbleBlockSchema } from './text'
import { videoBubbleContentSchema, videoBubbleBlockSchema } from './video'
export const bubbleBlockContentSchema = textBubbleContentSchema
.or(imageBubbleContentSchema)
.or(videoBubbleContentSchema)
.or(embedBubbleContentSchema)
.or(audioBubbleContentSchema)
export const bubbleBlockSchema = textBubbleBlockSchema
.or(imageBubbleBlockSchema)
.or(videoBubbleBlockSchema)
.or(embedBubbleBlockSchema)
.or(audioBubbleBlockSchema)
export type BubbleBlock = z.infer<typeof bubbleBlockSchema>
export type BubbleBlockContent = z.infer<typeof bubbleBlockContentSchema>

View File

@ -0,0 +1,26 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { BubbleBlockType } from './enums'
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<typeof textBubbleContentSchema>
export const textBubbleBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([BubbleBlockType.TEXT]),
content: textBubbleContentSchema,
})
)
export type TextBubbleBlock = z.infer<typeof textBubbleBlockSchema>

View File

@ -0,0 +1,5 @@
export enum VideoBubbleContentType {
URL = 'url',
YOUTUBE = 'youtube',
VIMEO = 'vimeo',
}

View File

@ -0,0 +1,2 @@
export * from './enums'
export * from './schemas'

View File

@ -0,0 +1,22 @@
import { z } from 'zod'
import { blockBaseSchema } from '../../baseSchemas'
import { BubbleBlockType } from '../enums'
import { VideoBubbleContentType } from './enums'
export const videoBubbleContentSchema = z.object({
url: z.string().optional(),
id: z.string().optional(),
type: z.nativeEnum(VideoBubbleContentType).optional(),
})
export const videoBubbleBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([BubbleBlockType.VIDEO]),
content: videoBubbleContentSchema,
})
)
export const defaultVideoBubbleContent: VideoBubbleContent = {}
export type VideoBubbleBlock = z.infer<typeof videoBubbleBlockSchema>
export type VideoBubbleContent = z.infer<typeof videoBubbleContentSchema>

View File

@ -0,0 +1,7 @@
export * from './baseSchemas'
export * from './bubbles'
export * from './inputs'
export * from './integrations'
export * from './logic'
export * from './schemas'
export * from './start'

View File

@ -0,0 +1,37 @@
import { z } from 'zod'
import { ItemType } from '../../items/enums'
import { itemBaseSchema } from '../../items/baseSchemas'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
export const choiceInputOptionsSchema = optionBaseSchema.and(
z.object({
isMultipleChoice: z.boolean(),
buttonLabel: z.string(),
})
)
export const defaultChoiceInputOptions: ChoiceInputOptions = {
buttonLabel: defaultButtonLabel,
isMultipleChoice: false,
}
export const buttonItemSchema = itemBaseSchema.and(
z.object({
type: z.literal(ItemType.BUTTON),
content: z.string().optional(),
})
)
export const choiceInputSchema = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.CHOICE]),
items: z.array(buttonItemSchema),
options: choiceInputOptionsSchema,
})
)
export type ButtonItem = z.infer<typeof buttonItemSchema>
export type ChoiceInputBlock = z.infer<typeof choiceInputSchema>
export type ChoiceInputOptions = z.infer<typeof choiceInputOptionsSchema>

View File

@ -0,0 +1 @@
export const defaultButtonLabel = 'Send'

View File

@ -0,0 +1,32 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
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 = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.DATE]),
options: dateInputOptionsSchema,
})
)
export const defaultDateInputOptions: DateInputOptions = {
hasTime: false,
isRange: false,
labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' },
}
export type DateInputBlock = z.infer<typeof dateInputSchema>
export type DateInputOptions = z.infer<typeof dateInputOptionsSchema>

View File

@ -0,0 +1,32 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
import { textInputOptionsBaseSchema } from './text'
export const emailInputOptionsSchema = optionBaseSchema
.and(textInputOptionsBaseSchema)
.and(
z.object({
retryMessageContent: z.string(),
})
)
export const emailInputSchema = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.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 EmailInputBlock = z.infer<typeof emailInputSchema>
export type EmailInputOptions = z.infer<typeof emailInputOptionsSchema>

View File

@ -0,0 +1,12 @@
export enum InputBlockType {
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',
RATING = 'rating input',
FILE = 'file input',
}

View File

@ -0,0 +1,41 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { InputBlockType } from './enums'
export const fileInputOptionsSchema = optionBaseSchema.and(
z.object({
isRequired: z.boolean().optional(),
isMultipleAllowed: z.boolean(),
labels: z.object({
placeholder: z.string(),
button: z.string(),
clear: z.string().optional(),
skip: z.string().optional(),
}),
sizeLimit: z.number().optional(),
})
)
export const fileInputStepSchema = blockBaseSchema.and(
z.object({
type: z.literal(InputBlockType.FILE),
options: fileInputOptionsSchema,
})
)
export const defaultFileInputOptions: FileInputOptions = {
isRequired: true,
isMultipleAllowed: false,
labels: {
placeholder: `<strong>
Click to upload
</strong> or drag and drop<br>
(size limit: 10MB)`,
button: 'Upload',
clear: 'Clear',
skip: 'Skip',
},
}
export type FileInputBlock = z.infer<typeof fileInputStepSchema>
export type FileInputOptions = z.infer<typeof fileInputOptionsSchema>

View File

@ -0,0 +1,13 @@
export * from './choice'
export * from './constants'
export * from './date'
export * from './email'
export * from './enums'
export * from './file'
export * from './number'
export * from './payment'
export * from './phone'
export * from './rating'
export * from './schemas'
export * from './text'
export * from './url'

View File

@ -0,0 +1,29 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
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 = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.NUMBER]),
options: numberInputOptionsSchema,
})
)
export const defaultNumberInputOptions: NumberInputOptions = {
labels: { button: defaultButtonLabel, placeholder: 'Type a number...' },
}
export type NumberInputBlock = z.infer<typeof numberInputSchema>
export type NumberInputOptions = z.infer<typeof numberInputOptionsSchema>

View File

@ -0,0 +1,3 @@
export enum PaymentProvider {
STRIPE = 'Stripe',
}

View File

@ -0,0 +1,2 @@
export * from './enums'
export * from './schemas'

View File

@ -0,0 +1,56 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../../baseSchemas'
import { InputBlockType } from '../enums'
import { PaymentProvider } from './enums'
export type CreditCardDetails = {
number: string
exp_month: string
exp_year: string
cvc: string
}
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 paymentInputRuntimeOptionsSchema = z.object({
paymentIntentSecret: z.string(),
amountLabel: z.string(),
publicKey: z.string(),
})
export const paymentInputSchema = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.PAYMENT]),
options: paymentInputOptionsSchema,
})
)
export const defaultPaymentInputOptions: PaymentInputOptions = {
provider: PaymentProvider.STRIPE,
labels: { button: 'Pay', success: 'Success' },
currency: 'USD',
}
export type PaymentInputBlock = z.infer<typeof paymentInputSchema>
export type PaymentInputOptions = z.infer<typeof paymentInputOptionsSchema>
export type PaymentInputRuntimeOptions = z.infer<
typeof paymentInputRuntimeOptionsSchema
>

View File

@ -0,0 +1,35 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
import { textInputOptionsBaseSchema } from './text'
export const phoneNumberInputOptionsSchema = optionBaseSchema
.and(textInputOptionsBaseSchema)
.and(
z.object({
retryMessageContent: z.string(),
defaultCountryCode: z.string().optional(),
})
)
export const phoneNumberInputBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.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 PhoneNumberInputBlock = z.infer<typeof phoneNumberInputBlockSchema>
export type PhoneNumberInputOptions = z.infer<
typeof phoneNumberInputOptionsSchema
>

View File

@ -0,0 +1,38 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
export const defaultRatingInputOptions: RatingInputOptions = {
buttonType: 'Numbers',
length: 10,
labels: { button: defaultButtonLabel },
customIcon: { isEnabled: false },
}
export const ratingInputOptionsSchema = optionBaseSchema.and(
z.object({
buttonType: z.literal('Icons').or(z.literal('Numbers')),
length: z.number(),
labels: z.object({
left: z.string().optional(),
right: z.string().optional(),
button: z.string(),
}),
customIcon: z.object({
isEnabled: z.boolean(),
svg: z.string().optional(),
}),
isOneClickSubmitEnabled: z.boolean().optional(),
})
)
export const ratingInputBlockSchema = blockBaseSchema.and(
z.object({
type: z.literal(InputBlockType.RATING),
options: ratingInputOptionsSchema,
})
)
export type RatingInputBlock = z.infer<typeof ratingInputBlockSchema>
export type RatingInputOptions = z.infer<typeof ratingInputOptionsSchema>

View File

@ -0,0 +1,42 @@
import { z } from 'zod'
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,
phoneNumberInputBlockSchema,
} from './phone'
import { ratingInputOptionsSchema, ratingInputBlockSchema } from './rating'
import { textInputOptionsSchema, textInputSchema } from './text'
import { fileInputOptionsSchema, fileInputStepSchema } from './file'
import { urlInputOptionsSchema, urlInputSchema } from './url'
import { optionBaseSchema } from '../baseSchemas'
export type OptionBase = z.infer<typeof optionBaseSchema>
export const inputBlockOptionsSchema = textInputOptionsSchema
.or(choiceInputOptionsSchema)
.or(emailInputOptionsSchema)
.or(numberInputOptionsSchema)
.or(urlInputOptionsSchema)
.or(phoneNumberInputOptionsSchema)
.or(dateInputOptionsSchema)
.or(paymentInputOptionsSchema)
.or(ratingInputOptionsSchema)
.or(fileInputOptionsSchema)
export const inputBlockSchema = textInputSchema
.or(numberInputSchema)
.or(emailInputSchema)
.or(urlInputSchema)
.or(dateInputSchema)
.or(phoneNumberInputBlockSchema)
.or(choiceInputSchema)
.or(paymentInputSchema)
.or(ratingInputBlockSchema)
.or(fileInputStepSchema)
export type InputBlock = z.infer<typeof inputBlockSchema>
export type InputBlockOptions = z.infer<typeof inputBlockOptionsSchema>

View File

@ -0,0 +1,34 @@
import { z } from 'zod'
import { blockBaseSchema, optionBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
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 = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.TEXT]),
options: textInputOptionsSchema,
})
)
export type TextInputBlock = z.infer<typeof textInputSchema>
export type TextInputOptions = z.infer<typeof textInputOptionsSchema>

View File

@ -0,0 +1,32 @@
import { z } from 'zod'
import { optionBaseSchema, blockBaseSchema } from '../baseSchemas'
import { defaultButtonLabel } from './constants'
import { InputBlockType } from './enums'
import { textInputOptionsBaseSchema } from './text'
export const urlInputOptionsSchema = optionBaseSchema
.and(textInputOptionsBaseSchema)
.and(
z.object({
retryMessageContent: z.string(),
})
)
export const urlInputSchema = blockBaseSchema.and(
z.object({
type: z.enum([InputBlockType.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 UrlInputBlock = z.infer<typeof urlInputSchema>
export type UrlInputOptions = z.infer<typeof urlInputOptionsSchema>

View File

@ -0,0 +1,32 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
export const chatwootOptionsSchema = z.object({
baseUrl: z.string(),
websiteToken: z.string(),
user: z
.object({
id: z.string().optional(),
email: z.string().optional(),
name: z.string().optional(),
avatarUrl: z.string().optional(),
phoneNumber: z.string().optional(),
})
.optional(),
})
export const chatwootBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.CHATWOOT]),
options: chatwootOptionsSchema,
})
)
export const defaultChatwootOptions: ChatwootOptions = {
baseUrl: 'https://app.chatwoot.com',
websiteToken: '',
}
export type ChatwootBlock = z.infer<typeof chatwootBlockSchema>
export type ChatwootOptions = z.infer<typeof chatwootOptionsSchema>

View File

@ -0,0 +1,10 @@
export enum IntegrationBlockType {
GOOGLE_SHEETS = 'Google Sheets',
GOOGLE_ANALYTICS = 'Google Analytics',
WEBHOOK = 'Webhook',
EMAIL = 'Email',
ZAPIER = 'Zapier',
MAKE_COM = 'Make.com',
PABBLY_CONNECT = 'Pabbly',
CHATWOOT = 'Chatwoot',
}

View File

@ -0,0 +1,25 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
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 googleAnalyticsBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.GOOGLE_ANALYTICS]),
options: googleAnalyticsOptionsSchema,
})
)
export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {}
export type GoogleAnalyticsBlock = z.infer<typeof googleAnalyticsBlockSchema>
export type GoogleAnalyticsOptions = z.infer<
typeof googleAnalyticsOptionsSchema
>

View File

@ -0,0 +1,5 @@
export enum GoogleSheetsAction {
GET = 'Get data from sheet',
INSERT_ROW = 'Insert a row',
UPDATE_ROW = 'Update a row',
}

View File

@ -0,0 +1,2 @@
export * from './enums'
export * from './schemas'

View File

@ -0,0 +1,126 @@
import { z } from 'zod'
import { ComparisonOperators, LogicalOperator } from '../../logic/condition'
import cuid from 'cuid'
import { IntegrationBlockType } from '../enums'
import { GoogleSheetsAction } from './enums'
import { blockBaseSchema } from '../../baseSchemas'
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 rowsFilterComparisonSchema = z.object({
id: z.string(),
column: z.string().optional(),
comparisonOperator: z.nativeEnum(ComparisonOperators).optional(),
value: z.string().optional(),
})
const googleSheetsGetOptionsSchema = googleSheetsOptionsBaseSchema.and(
z.object({
action: z.enum([GoogleSheetsAction.GET]),
// TODO: remove referenceCell once migrated to filtering
referenceCell: cellSchema.optional(),
filter: z.object({
comparisons: z.array(rowsFilterComparisonSchema),
logicalOperator: z.nativeEnum(LogicalOperator),
}),
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 = googleSheetsGetOptionsSchema
.or(googleSheetsInsertRowOptionsSchema)
.or(googleSheetsUpdateRowOptionsSchema)
.or(googleSheetsOptionsBaseSchema)
export const googleSheetsBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.GOOGLE_SHEETS]),
options: googleSheetsOptionsSchema,
})
)
export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {}
export const defaultGoogleSheetsGetOptions: GoogleSheetsGetOptions = {
action: GoogleSheetsAction.GET,
cellsToExtract: [
{
id: cuid(),
},
],
filter: {
comparisons: [
{
id: cuid(),
},
],
logicalOperator: LogicalOperator.AND,
},
}
export const defaultGoogleSheetsInsertOptions: GoogleSheetsInsertRowOptions = {
action: GoogleSheetsAction.INSERT_ROW,
cellsToInsert: [
{
id: cuid(),
},
],
}
export const defaultGoogleSheetsUpdateOptions: GoogleSheetsUpdateRowOptions = {
action: GoogleSheetsAction.UPDATE_ROW,
cellsToUpsert: [
{
id: cuid(),
},
],
}
export type GoogleSheetsBlock = z.infer<typeof googleSheetsBlockSchema>
export type GoogleSheetsOptions = z.infer<typeof googleSheetsOptionsSchema>
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<typeof cellSchema>
export type ExtractingCell = z.infer<typeof extractingCellSchema>
export type RowsFilterComparison = z.infer<typeof rowsFilterComparisonSchema>

View File

@ -0,0 +1,10 @@
export * from './chatwoot'
export * from './enums'
export * from './googleAnalytics'
export * from './googleSheets'
export * from './makeCom'
export * from './pabblyConnect'
export * from './schemas'
export * from './sendEmail'
export * from './webhook'
export * from './zapier'

View File

@ -0,0 +1,14 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
export const makeComBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.MAKE_COM]),
options: webhookOptionsSchema,
webhookId: z.string(),
})
)
export type MakeComBlock = z.infer<typeof makeComBlockSchema>

View File

@ -0,0 +1,14 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
export const pabblyConnectBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.PABBLY_CONNECT]),
options: webhookOptionsSchema,
webhookId: z.string(),
})
)
export type PabblyConnectBlock = z.infer<typeof pabblyConnectBlockSchema>

View File

@ -0,0 +1,35 @@
import { z } from 'zod'
import { chatwootBlockSchema, chatwootOptionsSchema } from './chatwoot'
import {
googleAnalyticsOptionsSchema,
googleAnalyticsBlockSchema,
} from './googleAnalytics'
import {
googleSheetsOptionsSchema,
googleSheetsBlockSchema,
} from './googleSheets/schemas'
import { makeComBlockSchema } from './makeCom'
import { pabblyConnectBlockSchema } from './pabblyConnect'
import { sendEmailOptionsSchema, sendEmailBlockSchema } from './sendEmail'
import { webhookOptionsSchema, webhookBlockSchema } from './webhook'
import { zapierBlockSchema } from './zapier'
const integrationBlockOptionsSchema = googleSheetsOptionsSchema
.or(googleAnalyticsOptionsSchema)
.or(webhookOptionsSchema)
.or(sendEmailOptionsSchema)
.or(chatwootOptionsSchema)
export const integrationBlockSchema = googleSheetsBlockSchema
.or(googleAnalyticsBlockSchema)
.or(webhookBlockSchema)
.or(sendEmailBlockSchema)
.or(zapierBlockSchema)
.or(makeComBlockSchema)
.or(pabblyConnectBlockSchema)
.or(chatwootBlockSchema)
export type IntegrationBlock = z.infer<typeof integrationBlockSchema>
export type IntegrationBlockOptions = z.infer<
typeof integrationBlockOptionsSchema
>

View File

@ -0,0 +1,32 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
export const sendEmailOptionsSchema = z.object({
credentialsId: z.string(),
isCustomBody: z.boolean().optional(),
isBodyCode: z.boolean().optional(),
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(),
attachmentsVariableId: z.string().optional(),
})
export const sendEmailBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.EMAIL]),
options: sendEmailOptionsSchema,
})
)
export const defaultSendEmailOptions: SendEmailOptions = {
credentialsId: 'default',
isCustomBody: false,
recipients: [],
}
export type SendEmailBlock = z.infer<typeof sendEmailBlockSchema>
export type SendEmailOptions = z.infer<typeof sendEmailOptionsSchema>

View File

@ -0,0 +1,44 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
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 webhookBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.WEBHOOK]),
options: webhookOptionsSchema,
webhookId: z.string(),
})
)
export const defaultWebhookOptions: Omit<WebhookOptions, 'webhookId'> = {
responseVariableMapping: [],
variablesForTest: [],
isAdvancedConfig: false,
isCustomBody: false,
}
export type WebhookBlock = z.infer<typeof webhookBlockSchema>
export type WebhookOptions = z.infer<typeof webhookOptionsSchema>
export type ResponseVariableMapping = z.infer<
typeof responseVariableMappingSchema
>
export type VariableForTest = z.infer<typeof variableForTestSchema>

View File

@ -0,0 +1,14 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { IntegrationBlockType } from './enums'
import { webhookOptionsSchema } from './webhook'
export const zapierBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([IntegrationBlockType.ZAPIER]),
options: webhookOptionsSchema,
webhookId: z.string(),
})
)
export type ZapierBlock = z.infer<typeof zapierBlockSchema>

View File

@ -0,0 +1,21 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { LogicBlockType } from './enums'
export const codeOptionsSchema = z.object({
name: z.string(),
content: z.string().optional(),
shouldExecuteInParentContext: z.boolean().optional(),
})
export const codeBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([LogicBlockType.CODE]),
options: codeOptionsSchema,
})
)
export const defaultCodeOptions: CodeOptions = { name: 'Code snippet' }
export type CodeBlock = z.infer<typeof codeBlockSchema>
export type CodeOptions = z.infer<typeof codeOptionsSchema>

View File

@ -0,0 +1,55 @@
import { z } from 'zod'
import { ItemType } from '../../items/enums'
import { itemBaseSchema } from '../../items/baseSchemas'
import { blockBaseSchema } from '../baseSchemas'
import { LogicBlockType } from './enums'
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 conditionBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([LogicBlockType.CONDITION]),
items: z.array(conditionItemSchema),
})
)
export const defaultConditionContent: ConditionContent = {
comparisons: [],
logicalOperator: LogicalOperator.AND,
}
export type ConditionItem = z.infer<typeof conditionItemSchema>
export type Comparison = z.infer<typeof comparisonSchema>
export type ConditionBlock = z.infer<typeof conditionBlockSchema>
export type ConditionContent = z.infer<typeof conditionContentSchema>

View File

@ -0,0 +1,7 @@
export enum LogicBlockType {
SET_VARIABLE = 'Set variable',
CONDITION = 'Condition',
REDIRECT = 'Redirect',
CODE = 'Code',
TYPEBOT_LINK = 'Typebot link',
}

View File

@ -0,0 +1,7 @@
export * from './code'
export * from './condition'
export * from './enums'
export * from './logicBlock'
export * from './redirect'
export * from './setVariable'
export * from './typebotLink'

View File

@ -0,0 +1,20 @@
import { z } from 'zod'
import { codeOptionsSchema, codeBlockSchema } from './code'
import { conditionBlockSchema } from './condition'
import { redirectOptionsSchema, redirectBlockSchema } from './redirect'
import { setVariableOptionsSchema, setVariableBlockSchema } from './setVariable'
import { typebotLinkOptionsSchema, typebotLinkBlockSchema } from './typebotLink'
const logicBlockOptionsSchema = codeOptionsSchema
.or(redirectOptionsSchema)
.or(setVariableOptionsSchema)
.or(typebotLinkOptionsSchema)
export const logicBlockSchema = codeBlockSchema
.or(conditionBlockSchema)
.or(redirectBlockSchema)
.or(typebotLinkBlockSchema)
.or(setVariableBlockSchema)
export type LogicBlock = z.infer<typeof logicBlockSchema>
export type LogicBlockOptions = z.infer<typeof logicBlockOptionsSchema>

View File

@ -0,0 +1,20 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { LogicBlockType } from './enums'
export const redirectOptionsSchema = z.object({
url: z.string().optional(),
isNewTab: z.boolean(),
})
export const redirectBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([LogicBlockType.REDIRECT]),
options: redirectOptionsSchema,
})
)
export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }
export type RedirectBlock = z.infer<typeof redirectBlockSchema>
export type RedirectOptions = z.infer<typeof redirectOptionsSchema>

View File

@ -0,0 +1,21 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { LogicBlockType } from './enums'
export const setVariableOptionsSchema = z.object({
variableId: z.string().optional(),
expressionToEvaluate: z.string().optional(),
isCode: z.boolean().optional(),
})
export const setVariableBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([LogicBlockType.SET_VARIABLE]),
options: setVariableOptionsSchema,
})
)
export const defaultSetVariablesOptions: SetVariableOptions = {}
export type SetVariableBlock = z.infer<typeof setVariableBlockSchema>
export type SetVariableOptions = z.infer<typeof setVariableOptionsSchema>

View File

@ -0,0 +1,20 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
import { LogicBlockType } from './enums'
export const typebotLinkOptionsSchema = z.object({
typebotId: z.string().optional(),
groupId: z.string().optional(),
})
export const typebotLinkBlockSchema = blockBaseSchema.and(
z.object({
type: z.enum([LogicBlockType.TYPEBOT_LINK]),
options: typebotLinkOptionsSchema,
})
)
export const defaultTypebotLinkOptions: TypebotLinkOptions = {}
export type TypebotLinkBlock = z.infer<typeof typebotLinkBlockSchema>
export type TypebotLinkOptions = z.infer<typeof typebotLinkOptionsSchema>

View File

@ -0,0 +1,76 @@
import { z } from 'zod'
import { BubbleBlockType } from './bubbles/enums'
import { BubbleBlock, bubbleBlockSchema } from './bubbles/schemas'
import { ChoiceInputBlock } from './inputs/choice'
import { InputBlockType } from './inputs/enums'
import {
InputBlock,
InputBlockOptions,
inputBlockSchema,
} from './inputs/schemas'
import { IntegrationBlockType } from './integrations/enums'
import {
IntegrationBlock,
IntegrationBlockOptions,
integrationBlockSchema,
} from './integrations/schemas'
import { ConditionBlock } from './logic/condition'
import { LogicBlockType } from './logic/enums'
import {
LogicBlock,
LogicBlockOptions,
logicBlockSchema,
} from './logic/logicBlock'
import { blockBaseSchema } from './baseSchemas'
import { startBlockSchema } from './start/schemas'
export type DraggableBlock =
| BubbleBlock
| InputBlock
| LogicBlock
| IntegrationBlock
export type BlockType =
| 'start'
| BubbleBlockType
| InputBlockType
| LogicBlockType
| IntegrationBlockType
export type DraggableBlockType =
| BubbleBlockType
| InputBlockType
| LogicBlockType
| IntegrationBlockType
export type BlockWithOptions =
| InputBlock
| Exclude<LogicBlock, ConditionBlock>
| IntegrationBlock
export type BlockWithOptionsType =
| InputBlockType
| Exclude<LogicBlockType, LogicBlockType.CONDITION>
| IntegrationBlockType
export type BlockOptions =
| InputBlockOptions
| LogicBlockOptions
| IntegrationBlockOptions
export type BlockWithItems = ConditionBlock | ChoiceInputBlock
export type BlockBase = z.infer<typeof blockBaseSchema>
export type BlockIndices = {
groupIndex: number
blockIndex: number
}
export const blockSchema = startBlockSchema
.or(bubbleBlockSchema)
.or(inputBlockSchema)
.or(logicBlockSchema)
.or(integrationBlockSchema)
export type Block = z.infer<typeof blockSchema>

View File

@ -0,0 +1 @@
export * from './schemas'

View File

@ -0,0 +1,11 @@
import { z } from 'zod'
import { blockBaseSchema } from '../baseSchemas'
export const startBlockSchema = blockBaseSchema.and(
z.object({
type: z.literal('start'),
label: z.string(),
})
)
export type StartBlock = z.infer<typeof startBlockSchema>

View File

@ -0,0 +1,213 @@
import { z } from 'zod'
import {
googleAnalyticsOptionsSchema,
inputBlockSchema,
paymentInputRuntimeOptionsSchema,
redirectOptionsSchema,
} from './blocks'
import { publicTypebotSchema } from './publicTypebot'
import { ChatSession as ChatSessionPrisma } from 'db'
import { schemaForType } from './utils'
import { logSchema, resultSchema } from './result'
import { typebotSchema } from './typebot'
import {
BubbleBlockType,
textBubbleContentSchema,
imageBubbleContentSchema,
videoBubbleContentSchema,
audioBubbleContentSchema,
embedBubbleContentSchema,
} from './blocks/bubbles'
const typebotInSessionStateSchema = publicTypebotSchema.pick({
id: true,
groups: true,
edges: true,
variables: true,
})
const dynamicThemeSchema = z.object({
hostAvatarUrl: z.string().optional(),
guestAvatarUrl: z.string().optional(),
})
export const sessionStateSchema = z.object({
typebot: typebotInSessionStateSchema,
dynamicTheme: dynamicThemeSchema.optional(),
linkedTypebots: z.object({
typebots: z.array(typebotInSessionStateSchema),
queue: z.array(z.object({ edgeId: z.string(), typebotId: z.string() })),
}),
currentTypebotId: z.string(),
result: resultSchema
.pick({ id: true, variables: true, hasStarted: true })
.optional(),
isPreview: z.boolean(),
currentBlock: z
.object({
blockId: z.string(),
groupId: z.string(),
})
.optional(),
})
const chatSessionSchema = schemaForType<ChatSessionPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
state: sessionStateSchema,
})
)
const textMessageSchema = z.object({
type: z.enum([BubbleBlockType.TEXT]),
content: textBubbleContentSchema.omit({
richText: true,
}),
})
const imageMessageSchema = z.object({
type: z.enum([BubbleBlockType.IMAGE]),
content: imageBubbleContentSchema,
})
const videoMessageSchema = z.object({
type: z.enum([BubbleBlockType.VIDEO]),
content: videoBubbleContentSchema,
})
const audioMessageSchema = z.object({
type: z.enum([BubbleBlockType.AUDIO]),
content: audioBubbleContentSchema,
})
const embedMessageSchema = z.object({
type: z.enum([BubbleBlockType.EMBED]),
content: embedBubbleContentSchema,
})
const chatMessageSchema = textMessageSchema
.or(imageMessageSchema)
.or(videoMessageSchema)
.or(audioMessageSchema)
.or(embedMessageSchema)
const codeToExecuteSchema = z.object({
content: z.string(),
args: z.array(
z.object({
id: z.string(),
value: z.string().or(z.number()).or(z.boolean()).nullish(),
})
),
})
const startTypebotSchema = typebotSchema.pick({
id: true,
groups: true,
edges: true,
variables: true,
settings: true,
theme: true,
})
const startParamsSchema = z.object({
typebot: startTypebotSchema
.or(z.string())
.describe(
'Either a Typebot ID or a Typebot object. If you provide a Typebot object, it will be executed in preview mode. ([How can I find my typebot ID?](https://docs.typebot.io/api#how-to-find-my-typebotid)).'
),
isPreview: z
.boolean()
.optional()
.describe(
"If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab."
),
resultId: z
.string()
.optional()
.describe("Provide it if you'd like to overwrite an existing result."),
startGroupId: z
.string()
.optional()
.describe('Start chat from a specific group.'),
prefilledVariables: z
.record(z.unknown())
.optional()
.describe(
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
),
})
export const sendMessageInputSchema = z.object({
message: z
.string()
.optional()
.describe(
'The answer to the previous chat input. Do not provide it if you are starting a new chat.'
),
sessionId: z
.string()
.optional()
.describe(
'Session ID that you get from the initial chat request to a bot. If not provided, it will create a new session.'
),
startParams: startParamsSchema.optional(),
})
const runtimeOptionsSchema = paymentInputRuntimeOptionsSchema.optional()
const replyLogSchema = logSchema
.pick({
status: true,
description: true,
})
.and(z.object({ details: z.unknown().optional() }))
export const chatReplySchema = z.object({
messages: z.array(chatMessageSchema),
input: inputBlockSchema
.and(
z.object({
prefilledValue: z.string().optional(),
runtimeOptions: runtimeOptionsSchema.optional(),
})
)
.optional(),
logic: z
.object({
redirect: redirectOptionsSchema.optional(),
codeToExecute: codeToExecuteSchema.optional(),
})
.optional(),
integrations: z
.object({
chatwoot: z
.object({
codeToExecute: codeToExecuteSchema,
})
.optional(),
googleAnalytics: googleAnalyticsOptionsSchema.optional(),
})
.optional(),
sessionId: z.string().optional(),
typebot: typebotSchema
.pick({ id: true, theme: true, settings: true })
.optional(),
resultId: z.string().optional(),
dynamicTheme: dynamicThemeSchema.optional(),
logs: z.array(replyLogSchema).optional(),
})
export type ChatSession = z.infer<typeof chatSessionSchema>
export type SessionState = z.infer<typeof sessionStateSchema>
export type TypebotInSession = z.infer<typeof typebotInSessionStateSchema>
export type ChatReply = z.infer<typeof chatReplySchema>
export type ChatMessage = z.infer<typeof chatMessageSchema>
export type SendMessageInput = z.infer<typeof sendMessageInputSchema>
export type CodeToExecute = z.infer<typeof codeToExecuteSchema>
export type StartParams = z.infer<typeof startParamsSchema>
export type RuntimeOptions = z.infer<typeof runtimeOptionsSchema>
export type StartTypebot = z.infer<typeof startTypebotSchema>
export type ReplyLog = z.infer<typeof replyLogSchema>

View File

@ -0,0 +1,58 @@
import { Credentials as CredentialsFromPrisma } from 'db'
export type Credentials =
| SmtpCredentials
| GoogleSheetsCredentials
| StripeCredentials
export type CredentialsBase = Omit<CredentialsFromPrisma, 'data' | 'type'>
export enum CredentialsType {
GOOGLE_SHEETS = 'google sheets',
SMTP = 'smtp',
STRIPE = 'stripe',
}
export type SmtpCredentials = CredentialsBase & {
type: CredentialsType.SMTP
data: SmtpCredentialsData
}
export type GoogleSheetsCredentials = CredentialsBase & {
type: CredentialsType.GOOGLE_SHEETS
data: GoogleSheetsCredentialsData
}
export type StripeCredentials = CredentialsBase & {
type: CredentialsType.STRIPE
data: StripeCredentialsData
}
export type GoogleSheetsCredentialsData = {
refresh_token?: string | null
expiry_date?: number | null
access_token?: string | null
token_type?: string | null
id_token?: string | null
scope?: string
}
export type SmtpCredentialsData = {
host?: string
username?: string
password?: string
isTlsEnabled?: boolean
port: number
from: { email?: string; name?: string }
}
export type StripeCredentialsData = {
live: {
secretKey: string
publicKey: string
}
test?: {
secretKey?: string
publicKey?: string
}
}

View File

@ -0,0 +1,9 @@
import { z } from 'zod'
export const itemBaseSchema = z.object({
id: z.string(),
blockId: z.string(),
outgoingEdgeId: z.string().optional(),
})
export type ItemBase = z.infer<typeof itemBaseSchema>

View File

@ -0,0 +1,4 @@
export enum ItemType {
BUTTON,
CONDITION,
}

View File

@ -0,0 +1,4 @@
export * from './baseSchemas'
export * from './enums'
export * from './schemas'
export * from './types'

View File

@ -0,0 +1,7 @@
import { z } from 'zod'
import { buttonItemSchema } from '../blocks/inputs/choice'
import { conditionItemSchema } from '../blocks/logic/condition'
const itemSchema = buttonItemSchema.or(conditionItemSchema)
export type Item = z.infer<typeof itemSchema>

View File

@ -0,0 +1,5 @@
export type ItemIndices = {
blockIndex: number
groupIndex: number
itemIndex: number
}

View File

@ -0,0 +1,32 @@
import {
groupSchema,
edgeSchema,
variableSchema,
themeSchema,
settingsSchema,
typebotSchema,
} from './typebot'
import { PublicTypebot as PublicTypebotPrisma } from 'db'
import { z } from 'zod'
import { schemaForType } from './utils'
export const publicTypebotSchema = schemaForType<PublicTypebotPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
groups: z.array(groupSchema),
edges: z.array(edgeSchema),
variables: z.array(variableSchema),
theme: themeSchema,
settings: settingsSchema,
})
)
const publicTypebotWithName = publicTypebotSchema.and(
typebotSchema.pick({ name: true, isArchived: true, isClosed: true })
)
export type PublicTypebot = z.infer<typeof publicTypebotSchema>
export type PublicTypebotWithName = z.infer<typeof publicTypebotWithName>

View File

@ -0,0 +1,65 @@
import { z } from 'zod'
import { answerInputSchema, answerSchema } from './answer'
import { InputBlockType } from './blocks'
import { variableWithValueSchema } from './typebot/variable'
import { Result as ResultPrisma, Log as LogPrisma } from 'db'
import { schemaForType } from './utils'
export const resultSchema = schemaForType<ResultPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
variables: z.array(variableWithValueSchema),
isCompleted: z.boolean(),
hasStarted: z.boolean().nullable(),
isArchived: z.boolean().nullable(),
})
)
export const resultWithAnswersSchema = resultSchema.and(
z.object({
answers: z.array(answerSchema),
})
)
export const resultWithAnswersInputSchema = resultSchema.and(
z.object({
answers: z.array(answerInputSchema),
})
)
export const logSchema = schemaForType<LogPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
resultId: z.string(),
status: z.string(),
description: z.string(),
details: z.string().nullable(),
})
)
export type Result = z.infer<typeof resultSchema>
export type ResultWithAnswers = z.infer<typeof resultWithAnswersSchema>
export type ResultWithAnswersInput = z.infer<
typeof resultWithAnswersInputSchema
>
export type Log = z.infer<typeof logSchema>
export type ResultValues = Pick<
ResultWithAnswersInput,
'answers' | 'createdAt' | 'variables'
>
export type ResultHeaderCell = {
id: string
label: string
blocks?: {
id: string
groupId: string
}[]
blockType?: InputBlockType
variableIds?: string[]
}

View File

@ -0,0 +1,4 @@
export * from './typebot'
export * from './theme'
export * from './settings'
export * from './variable'

View File

@ -0,0 +1,51 @@
import { z } from 'zod'
const generalSettings = z.object({
isBrandingEnabled: z.boolean(),
isTypingEmulationEnabled: z.boolean().optional(),
isInputPrefillEnabled: z.boolean().optional(),
isHideQueryParamsEnabled: z.boolean().optional(),
isNewResultOnRefreshEnabled: z.boolean().optional(),
isResultSavingEnabled: z.boolean().optional(),
})
const typingEmulation = z.object({
enabled: z.boolean(),
speed: z.number(),
maxDelay: z.number(),
})
const metadataSchema = z.object({
title: z.string().optional(),
description: z.string().optional(),
imageUrl: z.string().optional(),
favIconUrl: z.string().optional(),
customHeadCode: z.string().optional(),
googleTagManagerId: z.string().optional(),
})
export const settingsSchema = z.object({
general: generalSettings,
typingEmulation: typingEmulation,
metadata: metadataSchema,
})
export const defaultSettings: Settings = {
general: {
isBrandingEnabled: true,
isNewResultOnRefreshEnabled: false,
isInputPrefillEnabled: true,
isHideQueryParamsEnabled: true,
isResultSavingEnabled: true,
},
typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 },
metadata: {
description:
'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<typeof settingsSchema>
export type GeneralSettings = z.infer<typeof generalSettings>
export type TypingEmulation = z.infer<typeof typingEmulation>
export type Metadata = z.infer<typeof metadataSchema>

View File

@ -0,0 +1,5 @@
export enum BackgroundType {
COLOR = 'Color',
IMAGE = 'Image',
NONE = 'None',
}

View File

@ -0,0 +1,2 @@
export * from './enums'
export * from './schemas'

View File

@ -0,0 +1,68 @@
import { z } from 'zod'
import { BackgroundType } from './enums'
const avatarPropsSchema = z.object({
isEnabled: z.boolean(),
url: z.string().optional(),
})
const containerColorsSchema = z.object({
backgroundColor: z.string(),
color: z.string(),
})
const inputColorsSchema = containerColorsSchema.and(
z.object({
placeholderColor: z.string(),
})
)
export const chatThemeSchema = z.object({
hostAvatar: avatarPropsSchema.optional(),
guestAvatar: avatarPropsSchema.optional(),
hostBubbles: containerColorsSchema,
guestBubbles: containerColorsSchema,
buttons: containerColorsSchema,
inputs: inputColorsSchema,
})
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: {
hostBubbles: { backgroundColor: '#F7F8FF', color: '#303235' },
guestBubbles: { backgroundColor: '#FF8E21', color: '#FFFFFF' },
buttons: { backgroundColor: '#0042DA', color: '#FFFFFF' },
inputs: {
backgroundColor: '#FFFFFF',
color: '#303235',
placeholderColor: '#9095A0',
},
},
general: {
font: 'Open Sans',
background: { type: BackgroundType.COLOR, content: '#ffffff' },
},
}
export type Theme = z.infer<typeof themeSchema>
export type ChatTheme = z.infer<typeof chatThemeSchema>
export type AvatarProps = z.infer<typeof avatarPropsSchema>
export type GeneralTheme = z.infer<typeof generalThemeSchema>
export type Background = z.infer<typeof backgroundSchema>
export type ContainerColors = z.infer<typeof containerColorsSchema>
export type InputColors = z.infer<typeof inputColorsSchema>

View File

@ -0,0 +1,73 @@
import { z } from 'zod'
import { settingsSchema } from './settings'
import { blockSchema } from '../blocks'
import { themeSchema } from './theme'
import { variableSchema } from './variable'
import { Typebot as TypebotPrisma } from 'db'
import { schemaForType } from '../utils'
export const groupSchema = z.object({
id: z.string(),
title: z.string(),
graphCoordinates: z.object({
x: z.number(),
y: z.number(),
}),
blocks: z.array(blockSchema),
})
const sourceSchema = z.object({
groupId: z.string(),
blockId: z.string(),
itemId: z.string().optional(),
})
const targetSchema = z.object({
groupId: z.string(),
blockId: z.string().optional(),
})
export const edgeSchema = z.object({
id: z.string(),
from: sourceSchema,
to: targetSchema,
})
const resultsTablePreferencesSchema = z.object({
columnsOrder: z.array(z.string()),
columnsVisibility: z.record(z.string(), z.boolean()),
columnsWidth: z.record(z.string(), z.number()),
})
export const typebotSchema = schemaForType<TypebotPrisma>()(
z.object({
version: z.enum(['2']).optional(),
id: z.string(),
name: z.string(),
groups: z.array(groupSchema),
edges: z.array(edgeSchema),
variables: z.array(variableSchema),
theme: themeSchema,
settings: settingsSchema,
createdAt: z.date(),
updatedAt: z.date(),
icon: z.string().nullable(),
publishedTypebotId: z.string().nullable(),
folderId: z.string().nullable(),
publicId: z.string().nullable(),
customDomain: z.string().nullable(),
workspaceId: z.string(),
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
isArchived: z.boolean(),
isClosed: z.boolean(),
})
)
export type Typebot = z.infer<typeof typebotSchema>
export type Target = z.infer<typeof targetSchema>
export type Source = z.infer<typeof sourceSchema>
export type Edge = z.infer<typeof edgeSchema>
export type Group = z.infer<typeof groupSchema>
export type ResultsTablePreferences = z.infer<
typeof resultsTablePreferencesSchema
>

View File

@ -0,0 +1,31 @@
import { z } from 'zod'
export const variableSchema = z.object({
id: z.string(),
name: z.string(),
value: z.string().nullish(),
})
/**
* Variable when retrieved from the database
*/
export const variableWithValueSchema = z.object({
id: z.string(),
name: z.string(),
value: z.string(),
})
/**
* Variable when computed or retrieved from a block
*/
const VariableWithUnknowValueSchema = z.object({
id: z.string(),
name: z.string(),
value: z.unknown(),
})
export type Variable = z.infer<typeof variableSchema>
export type VariableWithValue = z.infer<typeof variableWithValueSchema>
export type VariableWithUnknowValue = z.infer<
typeof VariableWithUnknowValueSchema
>

View File

@ -0,0 +1,9 @@
import { z } from 'zod'
export type IdMap<T> = { [id: string]: T }
export const schemaForType =
<T>() =>
<S extends z.ZodType<T, any, any>>(arg: S) => {
return arg
}

View File

@ -0,0 +1,38 @@
import { Webhook as WebhookFromPrisma } from 'db'
export enum HttpMethod {
POST = 'POST',
GET = 'GET',
PUT = 'PUT',
DELETE = 'DELETE',
PATCH = 'PATCH',
HEAD = 'HEAD',
CONNECT = 'CONNECT',
OPTIONS = 'OPTIONS',
TRACE = 'TRACE',
}
export type KeyValue = { id: string; key?: string; value?: string }
export type Webhook = Omit<
WebhookFromPrisma,
'queryParams' | 'headers' | 'method'
> & {
queryParams: KeyValue[]
headers: KeyValue[]
method: HttpMethod
}
export type WebhookResponse = {
statusCode: number
data?: unknown
}
export const defaultWebhookAttributes: Omit<
Webhook,
'id' | 'body' | 'url' | 'typebotId'
> = {
method: HttpMethod.POST,
headers: [],
queryParams: [],
}

View File

@ -0,0 +1,60 @@
import { z } from 'zod'
import { schemaForType } from './utils'
import {
Workspace as WorkspacePrisma,
Plan,
MemberInWorkspace as MemberInWorkspacePrisma,
WorkspaceRole,
User as UserPrisma,
WorkspaceInvitation as WorkspaceInvitationPrisma,
} from 'db'
export const workspaceMemberSchema = schemaForType<
Omit<MemberInWorkspacePrisma, 'userId'> & {
user: Pick<UserPrisma, 'name' | 'email' | 'image'>
}
>()(
z.object({
workspaceId: z.string(),
user: z.object({
name: z.string().nullable(),
email: z.string().nullable(),
image: z.string().nullable(),
}),
role: z.nativeEnum(WorkspaceRole),
})
)
export const workspaceInvitationSchema = schemaForType<
Omit<WorkspaceInvitationPrisma, 'workspaceId' | 'userId' | 'id'>
>()(
z.object({
email: z.string(),
type: z.nativeEnum(WorkspaceRole),
createdAt: z.date(),
})
)
export const workspaceSchema = schemaForType<WorkspacePrisma>()(
z.object({
id: z.string(),
name: z.string(),
createdAt: z.date(),
icon: z.string().nullable(),
plan: z.nativeEnum(Plan),
stripeId: z.string().nullable(),
additionalChatsIndex: z.number(),
additionalStorageIndex: z.number(),
chatsLimitFirstEmailSentAt: z.date().nullable(),
chatsLimitSecondEmailSentAt: z.date().nullable(),
storageLimitFirstEmailSentAt: z.date().nullable(),
storageLimitSecondEmailSentAt: z.date().nullable(),
customChatsLimit: z.number().nullable(),
customStorageLimit: z.number().nullable(),
customSeatsLimit: z.number().nullable(),
})
)
export type Workspace = z.infer<typeof workspaceSchema>
export type WorkspaceMember = z.infer<typeof workspaceMemberSchema>
export type WorkspaceInvitation = z.infer<typeof workspaceInvitationSchema>