Skip to content

Commit

Permalink
feat(dashboard): 🎨 Improve typebot import
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Apr 9, 2022
1 parent 82446c4 commit b38b114
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 81 deletions.
13 changes: 11 additions & 2 deletions apps/builder/components/dashboard/FolderContent/TypebotButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { isMobile } from 'services/utils'
import { MoreButton } from 'components/dashboard/FolderContent/MoreButton'
import { ConfirmModal } from 'components/modals/ConfirmModal'
import { GripIcon } from 'assets/icons'
import { deleteTypebot, duplicateTypebot } from 'services/typebots'
import { deleteTypebot, importTypebot, getTypebot } from 'services/typebots'
import { Typebot } from 'models'
import { useTypebotDnd } from 'contexts/TypebotDndContext'
import { useDebounce } from 'use-debounce'
import { TypebotIcon } from 'components/shared/TypebotHeader/TypebotIcon'
import { useUser } from 'contexts/UserContext'
import { Plan } from 'db'

type ChatbotCardProps = {
typebot: Pick<Typebot, 'id' | 'publishedTypebotId' | 'name' | 'icon'>
Expand All @@ -36,6 +38,7 @@ export const TypebotButton = ({
onMouseDown,
}: ChatbotCardProps) => {
const router = useRouter()
const { user } = useUser()
const { draggedTypebot } = useTypebotDnd()
const [draggedTypebotDebounced] = useDebounce(draggedTypebot, 200)
const {
Expand Down Expand Up @@ -71,7 +74,13 @@ export const TypebotButton = ({

const handleDuplicateClick = async (e: React.MouseEvent) => {
e.stopPropagation()
const { data: createdTypebot, error } = await duplicateTypebot(typebot.id)
const { data } = await getTypebot(typebot.id)
const typebotToDuplicate = data?.typebot
if (!typebotToDuplicate) return { error: new Error('Typebot not found') }
const { data: createdTypebot, error } = await importTypebot(
data.typebot,
user?.plan ?? Plan.FREE
)
if (error)
return toast({
title: "Couldn't duplicate typebot",
Expand Down
4 changes: 2 additions & 2 deletions apps/builder/components/templates/CreateNewTypebotButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useUser } from 'contexts/UserContext'
import { Typebot } from 'models'
import { useRouter } from 'next/router'
import React, { useState } from 'react'
import { importTypebot, createTypebot } from 'services/typebots'
import { createTypebot, importTypebot } from 'services/typebots'
import { ImportTypebotFromFileButton } from './ImportTypebotFromFileButton'
import { TemplatesModal } from './TemplatesModal'

Expand Down Expand Up @@ -46,7 +46,7 @@ export const CreateNewTypebotButtons = () => {
},
},
},
user
user.plan
)
: await createTypebot({
folderId,
Expand Down
147 changes: 70 additions & 77 deletions apps/builder/services/typebots/typebots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ import {
} from 'utils'
import { dequal } from 'dequal'
import { stringify } from 'qs'
import { isChoiceInput, isConditionStep, sendRequest, omit } from 'utils'
import { isChoiceInput, isConditionStep, sendRequest } from 'utils'
import cuid from 'cuid'
import { diff } from 'deep-object-diff'
import { duplicateWebhook } from 'services/webhook'
import { Plan, User } from 'db'
import { Plan } from 'db'

export type TypebotInDashboard = Pick<
Typebot,
Expand Down Expand Up @@ -96,91 +96,84 @@ export const createTypebot = async ({
})
}

export const importTypebot = async (typebot: Typebot, user: User) => {
const typebotToImport: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> = omit(
{
...typebot,
publishedTypebotId: null,
publicId: null,
customDomain: null,
},
'id',
'updatedAt',
'createdAt'
)
export const importTypebot = async (typebot: Typebot, userPlan: Plan) => {
return sendRequest<Typebot>({
url: `/api/typebots`,
method: 'POST',
body: cleanUpTypebot(typebotToImport, user),
body: await duplicateTypebot(typebot, userPlan),
})
}

export const duplicateTypebot = async (typebotId: string) => {
const { data } = await getTypebot(typebotId)
const typebotToDuplicate = data?.typebot
if (!typebotToDuplicate) return { error: new Error('Typebot not found') }
const duplicatedTypebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> =
omit(
{
...typebotToDuplicate,
name: `${typebotToDuplicate.name} copy`,
publishedTypebotId: null,
publicId: null,
customDomain: null,
const duplicateTypebot = async (
typebot: Typebot,
userPlan: Plan
): Promise<Typebot> => {
const blockIdsMapping = generateOldNewIdsMapping(typebot.blocks)
const edgeIdsMapping = generateOldNewIdsMapping(typebot.edges)
return {
...typebot,
id: cuid(),
name: `${typebot.name} copy`,
publishedTypebotId: null,
publicId: null,
customDomain: null,
blocks: await Promise.all(
typebot.blocks.map(async (b) => ({
...b,
id: blockIdsMapping.get(b.id) as string,
steps: await Promise.all(
b.steps.map(async (s) => {
const newIds = {
blockId: blockIdsMapping.get(s.blockId) as string,
outgoingEdgeId: s.outgoingEdgeId
? edgeIdsMapping.get(s.outgoingEdgeId)
: undefined,
}
if (isWebhookStep(s)) {
const newWebhook = await duplicateWebhook(s.webhookId)
return {
...s,
webhookId: newWebhook ? newWebhook.id : cuid(),
...newIds,
}
}
return {
...s,
...newIds,
}
})
),
}))
),
edges: typebot.edges.map((e) => ({
...e,
id: edgeIdsMapping.get(e.id) as string,
from: {
...e.from,
blockId: blockIdsMapping.get(e.from.blockId) as string,
},
'id',
'updatedAt',
'createdAt'
)
return sendRequest<Typebot>({
url: `/api/typebots`,
method: 'POST',
body: await cleanAndDuplicateWebhooks(duplicatedTypebot),
})
to: { ...e.to, blockId: blockIdsMapping.get(e.to.blockId) as string },
})),
settings:
typebot.settings.general.isBrandingEnabled === false &&
userPlan === Plan.FREE
? {
...typebot.settings,
general: { ...typebot.settings.general, isBrandingEnabled: true },
}
: typebot.settings,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
}

const cleanUpTypebot = (
typebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'>,
user: User
): Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> => ({
...typebot,
blocks: typebot.blocks.map((b) => ({
...b,
steps: b.steps.map((s) =>
isWebhookStep(s) ? { ...s, webhookId: cuid() } : s
),
})),
settings:
typebot.settings.general.isBrandingEnabled === false &&
user.plan === Plan.FREE
? {
...typebot.settings,
general: { ...typebot.settings.general, isBrandingEnabled: true },
}
: typebot.settings,
})

const cleanAndDuplicateWebhooks = async (
typebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'>
) => ({
...typebot,
blocks: await Promise.all(
typebot.blocks.map(async (b) => ({
...b,
steps: await Promise.all(
b.steps.map(async (s) => {
if (isWebhookStep(s)) {
const newWebhook = await duplicateWebhook(s.webhookId)
return { ...s, webhookId: newWebhook ? newWebhook.id : cuid() }
}
return s
})
),
}))
),
})
const generateOldNewIdsMapping = (itemWithId: { id: string }[]) => {
const idsMapping: Map<string, string> = new Map()
itemWithId.forEach((item) => idsMapping.set(item.id, cuid()))
return idsMapping
}

const getTypebot = (typebotId: string) =>
export const getTypebot = (typebotId: string) =>
sendRequest<{ typebot: Typebot }>({
url: `/api/typebots/${typebotId}`,
method: 'GET',
Expand Down

4 comments on commit b38b114

@vercel
Copy link

@vercel vercel bot commented on b38b114 Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

app.typebot.io
builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app

@vercel
Copy link

@vercel vercel bot commented on b38b114 Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on b38b114 Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on b38b114 Apr 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.