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 ? ( + <> + ) : ( + + ) +} 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 {