🦴 Add share page backbone
This commit is contained in:
@ -185,3 +185,9 @@ export const PencilIcon = (props: IconProps) => (
|
||||
<circle cx="11" cy="11" r="2"></circle>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const EditIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</Icon>
|
||||
)
|
||||
|
@ -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 })
|
||||
|
70
apps/builder/components/share/EditableUrl.tsx
Normal file
70
apps/builder/components/share/EditableUrl.tsx
Normal file
@ -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 (
|
||||
<Editable
|
||||
as={HStack}
|
||||
spacing={3}
|
||||
defaultValue={publicId}
|
||||
onSubmit={onPublicIdChange}
|
||||
>
|
||||
<HStack spacing={1}>
|
||||
<Text>https://</Text>
|
||||
<Tooltip label="Edit">
|
||||
<EditablePreview
|
||||
mx={1}
|
||||
bgColor="blue.500"
|
||||
color="white"
|
||||
px={3}
|
||||
rounded="md"
|
||||
cursor="pointer"
|
||||
display="flex"
|
||||
fontWeight="semibold"
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<EditableInput px={2} />
|
||||
|
||||
<Text>.typebot.io/</Text>
|
||||
</HStack>
|
||||
|
||||
<HStack>
|
||||
<EditButton size="xs" />
|
||||
<CopyButton size="xs" textToCopy={`https://${publicId}.typebot.io/`} />
|
||||
</HStack>
|
||||
</Editable>
|
||||
)
|
||||
}
|
||||
|
||||
const EditButton = (props: ButtonProps) => {
|
||||
const { isEditing, getEditButtonProps } = useEditableControls()
|
||||
|
||||
return isEditing ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button leftIcon={<EditIcon />} {...props} {...getEditButtonProps()}>
|
||||
Edit
|
||||
</Button>
|
||||
)
|
||||
}
|
35
apps/builder/components/share/ShareContent.tsx
Normal file
35
apps/builder/components/share/ShareContent.tsx
Normal file
@ -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 (
|
||||
<Flex h="full" w="full" justifyContent="center" align="flex-start">
|
||||
<Stack maxW="1000px" w="full" pt="10" spacing={6}>
|
||||
<Heading fontSize="2xl" as="h1">
|
||||
Your typebot link
|
||||
</Heading>
|
||||
{typebot && (
|
||||
<EditableUrl
|
||||
publicId={
|
||||
typebot?.publicId ??
|
||||
parseDefaultPublicId(typebot.name, typebot.id)
|
||||
}
|
||||
onPublicIdChange={handlePublicIdChange}
|
||||
/>
|
||||
)}
|
||||
<Heading fontSize="2xl" as="h1">
|
||||
Embed your typebot
|
||||
</Heading>
|
||||
</Stack>
|
||||
</Flex>
|
||||
)
|
||||
}
|
25
apps/builder/components/shared/buttons/CopyButton.tsx
Normal file
25
apps/builder/components/shared/buttons/CopyButton.tsx
Normal file
@ -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 (
|
||||
<Button
|
||||
isDisabled={hasCopied}
|
||||
onClick={() => {
|
||||
onCopy()
|
||||
if (onCopied) onCopied()
|
||||
}}
|
||||
{...buttonProps}
|
||||
>
|
||||
{!hasCopied ? 'Copy' : 'Copied'}
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -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 (
|
||||
<typebotContext.Provider
|
||||
value={{
|
||||
@ -301,6 +307,7 @@ export const TypebotContext = ({
|
||||
undo,
|
||||
updateTheme,
|
||||
updateSettings,
|
||||
updatePublicId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -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({
|
||||
|
23
apps/builder/pages/typebots/[id]/share.tsx
Normal file
23
apps/builder/pages/typebots/[id]/share.tsx
Normal file
@ -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 (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Share" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ShareContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(SharePage)
|
@ -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<Typebot>) =>
|
||||
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<Typebot>) =>
|
||||
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)}`)
|
||||
|
@ -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('-')
|
||||
}
|
||||
|
@ -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");
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user