feat(dashboard): 🎨 Improve typebot import
This commit is contained in:
@ -16,11 +16,13 @@ import { isMobile } from 'services/utils'
|
|||||||
import { MoreButton } from 'components/dashboard/FolderContent/MoreButton'
|
import { MoreButton } from 'components/dashboard/FolderContent/MoreButton'
|
||||||
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
import { ConfirmModal } from 'components/modals/ConfirmModal'
|
||||||
import { GripIcon } from 'assets/icons'
|
import { GripIcon } from 'assets/icons'
|
||||||
import { deleteTypebot, duplicateTypebot } from 'services/typebots'
|
import { deleteTypebot, importTypebot, getTypebot } from 'services/typebots'
|
||||||
import { Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import { useTypebotDnd } from 'contexts/TypebotDndContext'
|
import { useTypebotDnd } from 'contexts/TypebotDndContext'
|
||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { TypebotIcon } from 'components/shared/TypebotHeader/TypebotIcon'
|
import { TypebotIcon } from 'components/shared/TypebotHeader/TypebotIcon'
|
||||||
|
import { useUser } from 'contexts/UserContext'
|
||||||
|
import { Plan } from 'db'
|
||||||
|
|
||||||
type ChatbotCardProps = {
|
type ChatbotCardProps = {
|
||||||
typebot: Pick<Typebot, 'id' | 'publishedTypebotId' | 'name' | 'icon'>
|
typebot: Pick<Typebot, 'id' | 'publishedTypebotId' | 'name' | 'icon'>
|
||||||
@ -36,6 +38,7 @@ export const TypebotButton = ({
|
|||||||
onMouseDown,
|
onMouseDown,
|
||||||
}: ChatbotCardProps) => {
|
}: ChatbotCardProps) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { user } = useUser()
|
||||||
const { draggedTypebot } = useTypebotDnd()
|
const { draggedTypebot } = useTypebotDnd()
|
||||||
const [draggedTypebotDebounced] = useDebounce(draggedTypebot, 200)
|
const [draggedTypebotDebounced] = useDebounce(draggedTypebot, 200)
|
||||||
const {
|
const {
|
||||||
@ -71,7 +74,13 @@ export const TypebotButton = ({
|
|||||||
|
|
||||||
const handleDuplicateClick = async (e: React.MouseEvent) => {
|
const handleDuplicateClick = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
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)
|
if (error)
|
||||||
return toast({
|
return toast({
|
||||||
title: "Couldn't duplicate typebot",
|
title: "Couldn't duplicate typebot",
|
||||||
|
@ -11,7 +11,7 @@ import { useUser } from 'contexts/UserContext'
|
|||||||
import { Typebot } from 'models'
|
import { Typebot } from 'models'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { importTypebot, createTypebot } from 'services/typebots'
|
import { createTypebot, importTypebot } from 'services/typebots'
|
||||||
import { ImportTypebotFromFileButton } from './ImportTypebotFromFileButton'
|
import { ImportTypebotFromFileButton } from './ImportTypebotFromFileButton'
|
||||||
import { TemplatesModal } from './TemplatesModal'
|
import { TemplatesModal } from './TemplatesModal'
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export const CreateNewTypebotButtons = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user
|
user.plan
|
||||||
)
|
)
|
||||||
: await createTypebot({
|
: await createTypebot({
|
||||||
folderId,
|
folderId,
|
||||||
|
@ -48,11 +48,11 @@ import {
|
|||||||
} from 'utils'
|
} from 'utils'
|
||||||
import { dequal } from 'dequal'
|
import { dequal } from 'dequal'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { isChoiceInput, isConditionStep, sendRequest, omit } from 'utils'
|
import { isChoiceInput, isConditionStep, sendRequest } from 'utils'
|
||||||
import cuid from 'cuid'
|
import cuid from 'cuid'
|
||||||
import { diff } from 'deep-object-diff'
|
import { diff } from 'deep-object-diff'
|
||||||
import { duplicateWebhook } from 'services/webhook'
|
import { duplicateWebhook } from 'services/webhook'
|
||||||
import { Plan, User } from 'db'
|
import { Plan } from 'db'
|
||||||
|
|
||||||
export type TypebotInDashboard = Pick<
|
export type TypebotInDashboard = Pick<
|
||||||
Typebot,
|
Typebot,
|
||||||
@ -96,91 +96,84 @@ export const createTypebot = async ({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const importTypebot = async (typebot: Typebot, user: User) => {
|
export const importTypebot = async (typebot: Typebot, userPlan: Plan) => {
|
||||||
const typebotToImport: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> = omit(
|
|
||||||
{
|
|
||||||
...typebot,
|
|
||||||
publishedTypebotId: null,
|
|
||||||
publicId: null,
|
|
||||||
customDomain: null,
|
|
||||||
},
|
|
||||||
'id',
|
|
||||||
'updatedAt',
|
|
||||||
'createdAt'
|
|
||||||
)
|
|
||||||
return sendRequest<Typebot>({
|
return sendRequest<Typebot>({
|
||||||
url: `/api/typebots`,
|
url: `/api/typebots`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: cleanUpTypebot(typebotToImport, user),
|
body: await duplicateTypebot(typebot, userPlan),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const duplicateTypebot = async (typebotId: string) => {
|
const duplicateTypebot = async (
|
||||||
const { data } = await getTypebot(typebotId)
|
typebot: Typebot,
|
||||||
const typebotToDuplicate = data?.typebot
|
userPlan: Plan
|
||||||
if (!typebotToDuplicate) return { error: new Error('Typebot not found') }
|
): Promise<Typebot> => {
|
||||||
const duplicatedTypebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> =
|
const blockIdsMapping = generateOldNewIdsMapping(typebot.blocks)
|
||||||
omit(
|
const edgeIdsMapping = generateOldNewIdsMapping(typebot.edges)
|
||||||
{
|
return {
|
||||||
...typebotToDuplicate,
|
...typebot,
|
||||||
name: `${typebotToDuplicate.name} copy`,
|
id: cuid(),
|
||||||
publishedTypebotId: null,
|
name: `${typebot.name} copy`,
|
||||||
publicId: null,
|
publishedTypebotId: null,
|
||||||
customDomain: null,
|
publicId: null,
|
||||||
},
|
customDomain: null,
|
||||||
'id',
|
blocks: await Promise.all(
|
||||||
'updatedAt',
|
typebot.blocks.map(async (b) => ({
|
||||||
'createdAt'
|
...b,
|
||||||
)
|
id: blockIdsMapping.get(b.id) as string,
|
||||||
return sendRequest<Typebot>({
|
steps: await Promise.all(
|
||||||
url: `/api/typebots`,
|
b.steps.map(async (s) => {
|
||||||
method: 'POST',
|
const newIds = {
|
||||||
body: await cleanAndDuplicateWebhooks(duplicatedTypebot),
|
blockId: blockIdsMapping.get(s.blockId) as string,
|
||||||
})
|
outgoingEdgeId: s.outgoingEdgeId
|
||||||
}
|
? edgeIdsMapping.get(s.outgoingEdgeId)
|
||||||
|
: undefined,
|
||||||
const cleanUpTypebot = (
|
}
|
||||||
typebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'>,
|
if (isWebhookStep(s)) {
|
||||||
user: User
|
const newWebhook = await duplicateWebhook(s.webhookId)
|
||||||
): Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'> => ({
|
return {
|
||||||
...typebot,
|
...s,
|
||||||
blocks: typebot.blocks.map((b) => ({
|
webhookId: newWebhook ? newWebhook.id : cuid(),
|
||||||
...b,
|
...newIds,
|
||||||
steps: b.steps.map((s) =>
|
}
|
||||||
isWebhookStep(s) ? { ...s, webhookId: cuid() } : s
|
}
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
...newIds,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}))
|
||||||
),
|
),
|
||||||
})),
|
edges: typebot.edges.map((e) => ({
|
||||||
settings:
|
...e,
|
||||||
typebot.settings.general.isBrandingEnabled === false &&
|
id: edgeIdsMapping.get(e.id) as string,
|
||||||
user.plan === Plan.FREE
|
from: {
|
||||||
? {
|
...e.from,
|
||||||
...typebot.settings,
|
blockId: blockIdsMapping.get(e.from.blockId) as string,
|
||||||
general: { ...typebot.settings.general, isBrandingEnabled: true },
|
},
|
||||||
}
|
to: { ...e.to, blockId: blockIdsMapping.get(e.to.blockId) as string },
|
||||||
: typebot.settings,
|
})),
|
||||||
})
|
settings:
|
||||||
|
typebot.settings.general.isBrandingEnabled === false &&
|
||||||
const cleanAndDuplicateWebhooks = async (
|
userPlan === Plan.FREE
|
||||||
typebot: Omit<Typebot, 'id' | 'updatedAt' | 'createdAt'>
|
? {
|
||||||
) => ({
|
...typebot.settings,
|
||||||
...typebot,
|
general: { ...typebot.settings.general, isBrandingEnabled: true },
|
||||||
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
|
: typebot.settings,
|
||||||
})
|
createdAt: new Date().toISOString(),
|
||||||
),
|
updatedAt: new Date().toISOString(),
|
||||||
}))
|
}
|
||||||
),
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const getTypebot = (typebotId: string) =>
|
const generateOldNewIdsMapping = (itemWithId: { id: string }[]) => {
|
||||||
|
const idsMapping: Map<string, string> = new Map()
|
||||||
|
itemWithId.forEach((item) => idsMapping.set(item.id, cuid()))
|
||||||
|
return idsMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTypebot = (typebotId: string) =>
|
||||||
sendRequest<{ typebot: Typebot }>({
|
sendRequest<{ typebot: Typebot }>({
|
||||||
url: `/api/typebots/${typebotId}`,
|
url: `/api/typebots/${typebotId}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
Reference in New Issue
Block a user