diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx
index 335b7da3d..b217d7aa7 100644
--- a/apps/builder/assets/icons.tsx
+++ b/apps/builder/assets/icons.tsx
@@ -185,3 +185,9 @@ export const PencilIcon = (props: IconProps) => (
)
+
+export const EditIcon = (props: IconProps) => (
+
+
+
+)
diff --git a/apps/builder/components/dashboard/FolderContent.tsx b/apps/builder/components/dashboard/FolderContent.tsx
index 058af136f..927f62012 100644
--- a/apps/builder/components/dashboard/FolderContent.tsx
+++ b/apps/builder/components/dashboard/FolderContent.tsx
@@ -22,7 +22,7 @@ import {
import { FolderPlusIcon } from 'assets/icons'
import React, { useState } from 'react'
import { createFolder, useFolders } from 'services/folders'
-import { updateTypebot, useTypebots } from 'services/typebots'
+import { patchTypebot, useTypebots } from 'services/typebots'
import { BackButton } from './FolderContent/BackButton'
import { CreateBotButton } from './FolderContent/CreateBotButton'
import { ButtonSkeleton, FolderButton } from './FolderContent/FolderButton'
@@ -83,7 +83,7 @@ export const FolderContent = ({ folder }: Props) => {
const moveTypebotToFolder = async (typebotId: string, folderId: string) => {
if (!typebots) return
- const { error } = await updateTypebot(typebotId, {
+ const { error } = await patchTypebot(typebotId, {
folderId: folderId === 'root' ? null : folderId,
})
if (error) toast({ description: error.message })
diff --git a/apps/builder/components/share/EditableUrl.tsx b/apps/builder/components/share/EditableUrl.tsx
new file mode 100644
index 000000000..9ccc17a39
--- /dev/null
+++ b/apps/builder/components/share/EditableUrl.tsx
@@ -0,0 +1,70 @@
+import {
+ HStack,
+ Tooltip,
+ EditablePreview,
+ EditableInput,
+ Text,
+ Editable,
+ Button,
+ ButtonProps,
+ useEditableControls,
+} from '@chakra-ui/react'
+import { EditIcon } from 'assets/icons'
+import { CopyButton } from 'components/shared/buttons/CopyButton'
+import React from 'react'
+
+type EditableUrlProps = {
+ publicId?: string
+ onPublicIdChange: (publicId: string) => void
+}
+
+export const EditableUrl = ({
+ publicId,
+ onPublicIdChange,
+}: EditableUrlProps) => {
+ return (
+
+
+ https://
+
+
+
+
+
+
+ .typebot.io/
+
+
+
+
+
+
+
+ )
+}
+
+const EditButton = (props: ButtonProps) => {
+ const { isEditing, getEditButtonProps } = useEditableControls()
+
+ return isEditing ? (
+ <>>
+ ) : (
+ } {...props} {...getEditButtonProps()}>
+ Edit
+
+ )
+}
diff --git a/apps/builder/components/share/ShareContent.tsx b/apps/builder/components/share/ShareContent.tsx
new file mode 100644
index 000000000..b03b57e6d
--- /dev/null
+++ b/apps/builder/components/share/ShareContent.tsx
@@ -0,0 +1,35 @@
+import { Flex, Heading, Stack } from '@chakra-ui/react'
+import { useTypebot } from 'contexts/TypebotContext'
+import React from 'react'
+import { parseDefaultPublicId } from 'services/typebots'
+import { EditableUrl } from './EditableUrl'
+
+export const ShareContent = () => {
+ const { typebot, updatePublicId } = useTypebot()
+
+ const handlePublicIdChange = (publicId: string) => {
+ if (publicId === typebot?.publicId) return
+ updatePublicId(publicId)
+ }
+ return (
+
+
+
+ Your typebot link
+
+ {typebot && (
+
+ )}
+
+ Embed your typebot
+
+
+
+ )
+}
diff --git a/apps/builder/components/shared/buttons/CopyButton.tsx b/apps/builder/components/shared/buttons/CopyButton.tsx
new file mode 100644
index 000000000..937710124
--- /dev/null
+++ b/apps/builder/components/shared/buttons/CopyButton.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import { ButtonProps, Button, useClipboard } from '@chakra-ui/react'
+
+interface CopyButtonProps extends ButtonProps {
+ textToCopy: string
+ onCopied?: () => void
+}
+
+export const CopyButton = (props: CopyButtonProps) => {
+ const { textToCopy, onCopied, ...buttonProps } = props
+ const { hasCopied, onCopy } = useClipboard(textToCopy)
+
+ return (
+
+ )
+}
diff --git a/apps/builder/contexts/TypebotContext.tsx b/apps/builder/contexts/TypebotContext.tsx
index cc2360a48..41d8661a2 100644
--- a/apps/builder/contexts/TypebotContext.tsx
+++ b/apps/builder/contexts/TypebotContext.tsx
@@ -56,6 +56,7 @@ const typebotContext = createContext<{
undo: () => void
updateTheme: (theme: Theme) => void
updateSettings: (settings: Settings) => void
+ updatePublicId: (publicId: string) => void
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
}>({})
@@ -284,6 +285,11 @@ export const TypebotContext = ({
setLocalTypebot({ ...localTypebot, settings })
}
+ const updatePublicId = (publicId: string) => {
+ if (!localTypebot) return
+ setLocalTypebot({ ...localTypebot, publicId })
+ }
+
return (
{children}
diff --git a/apps/builder/pages/api/typebots/[id].ts b/apps/builder/pages/api/typebots/[id].ts
index 6fa5c861a..e53689cf0 100644
--- a/apps/builder/pages/api/typebots/[id].ts
+++ b/apps/builder/pages/api/typebots/[id].ts
@@ -22,6 +22,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})
return res.send({ typebots })
}
+ if (req.method === 'PUT') {
+ const data = JSON.parse(req.body)
+ const typebots = await prisma.typebot.update({
+ where: { id },
+ data,
+ })
+ return res.send({ typebots })
+ }
if (req.method === 'PATCH') {
const data = JSON.parse(req.body)
const typebots = await prisma.typebot.update({
diff --git a/apps/builder/pages/typebots/[id]/share.tsx b/apps/builder/pages/typebots/[id]/share.tsx
new file mode 100644
index 000000000..74ed0239c
--- /dev/null
+++ b/apps/builder/pages/typebots/[id]/share.tsx
@@ -0,0 +1,23 @@
+import { Flex } from '@chakra-ui/layout'
+import withAuth from 'components/HOC/withUser'
+import { Seo } from 'components/Seo'
+import { ShareContent } from 'components/share/ShareContent'
+import { TypebotHeader } from 'components/shared/TypebotHeader'
+import { TypebotContext } from 'contexts/TypebotContext'
+import { useRouter } from 'next/router'
+import React from 'react'
+
+const SharePage = () => {
+ const { query } = useRouter()
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+export default withAuth(SharePage)
diff --git a/apps/builder/services/typebots.ts b/apps/builder/services/typebots.ts
index 1441caa57..a2a14379f 100644
--- a/apps/builder/services/typebots.ts
+++ b/apps/builder/services/typebots.ts
@@ -9,7 +9,7 @@ import {
import shortId from 'short-uuid'
import { Typebot } from 'bot-engine'
import useSWR from 'swr'
-import { fetcher, sendRequest } from './utils'
+import { fetcher, sendRequest, toKebabCase } from './utils'
import { deepEqual } from 'fast-equals'
export const useTypebots = ({
@@ -71,7 +71,14 @@ export const deleteTypebot = async (id: string) =>
method: 'DELETE',
})
-export const updateTypebot = async (id: string, typebot: Partial) =>
+export const updateTypebot = async (id: string, typebot: Typebot) =>
+ sendRequest({
+ url: `/api/typebots/${id}`,
+ method: 'PUT',
+ body: typebot,
+ })
+
+export const patchTypebot = async (id: string, typebot: Partial) =>
sendRequest({
url: `/api/typebots/${id}`,
method: 'PATCH',
@@ -157,4 +164,8 @@ export const parseTypebotToPublicTypebot = (
typebotId: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
+ publicId: typebot.publicId,
})
+
+export const parseDefaultPublicId = (name: string, id: string) =>
+ toKebabCase(`${name}-${id?.slice(0, 5)}`)
diff --git a/apps/builder/services/utils.ts b/apps/builder/services/utils.ts
index db642e648..b9f1fe8dc 100644
--- a/apps/builder/services/utils.ts
+++ b/apps/builder/services/utils.ts
@@ -63,3 +63,11 @@ export const parseHtmlStringToPlainText = (html: string): string => {
parser.end()
return label
}
+
+export const toKebabCase = (value: string) => {
+ const matched = value.match(
+ /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g
+ )
+ if (!matched) return ''
+ return matched.map((x) => x.toLowerCase()).join('-')
+}
diff --git a/packages/db/prisma/migrations/20211223141944_add_public_id/migration.sql b/packages/db/prisma/migrations/20211223141944_add_public_id/migration.sql
new file mode 100644
index 000000000..fa9b4cea6
--- /dev/null
+++ b/packages/db/prisma/migrations/20211223141944_add_public_id/migration.sql
@@ -0,0 +1,18 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[publicId]` on the table `PublicTypebot` will be added. If there are existing duplicate values, this will fail.
+ - A unique constraint covering the columns `[publicId]` on the table `Typebot` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- AlterTable
+ALTER TABLE "PublicTypebot" ADD COLUMN "publicId" TEXT;
+
+-- AlterTable
+ALTER TABLE "Typebot" ADD COLUMN "publicId" TEXT;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "PublicTypebot_publicId_key" ON "PublicTypebot"("publicId");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Typebot_publicId_key" ON "Typebot"("publicId");
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index b2c75aa20..daf9f2d1c 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -91,6 +91,7 @@ model Typebot {
startBlock Json
theme Json
settings Json
+ publicId String? @unique
}
model PublicTypebot {
@@ -102,6 +103,7 @@ model PublicTypebot {
startBlock Json
theme Json
settings Json
+ publicId String? @unique
}
model Result {