refactor(lp): ♻️ Simplify header
This commit is contained in:
@ -394,3 +394,12 @@ export const CopyIcon = (props: IconProps) => (
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export const TemplateIcon = (props: IconProps) => (
|
||||
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||
<rect x="3" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="14" width="7" height="7"></rect>
|
||||
<rect x="3" y="14" width="7" height="7"></rect>
|
||||
</Icon>
|
||||
)
|
||||
|
119
apps/builder/components/templates/CreateNewTypebotButtons.tsx
Normal file
119
apps/builder/components/templates/CreateNewTypebotButtons.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
import {
|
||||
VStack,
|
||||
Heading,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { ToolIcon, TemplateIcon, DownloadIcon } from 'assets/icons'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { importTypebot, createTypebot } from 'services/typebots'
|
||||
import { ImportTypebotFromFileButton } from './ImportTypebotFromFileButton'
|
||||
import { TemplatesModal } from './TemplatesModal'
|
||||
|
||||
export const CreateNewTypebotButtons = () => {
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||
useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
title: 'An error occured',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const isFirstBot = router.query.isFirstBot as string | undefined
|
||||
if (isFirstBot) setIsFromScratchTooltipOpened(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||
if (!user) return
|
||||
setIsLoading(true)
|
||||
const folderId = router.query.folderId?.toString() ?? null
|
||||
const { error, data } = typebot
|
||||
? await importTypebot(
|
||||
{
|
||||
...typebot,
|
||||
ownerId: user.id,
|
||||
folderId,
|
||||
theme: {
|
||||
...typebot.theme,
|
||||
chat: {
|
||||
...typebot.theme.chat,
|
||||
hostAvatar: { isEnabled: true, url: user.image ?? undefined },
|
||||
},
|
||||
},
|
||||
},
|
||||
user
|
||||
)
|
||||
: await createTypebot({
|
||||
folderId,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
if (data) router.push(`/typebots/${data.id}/edit`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack maxW="600px" w="full" flex="1" pt="20" spacing={10}>
|
||||
<Heading>Create a new typebot</Heading>
|
||||
<Stack w="full" spacing={6}>
|
||||
<Tooltip
|
||||
isOpen={isFromScratchTooltipOpened}
|
||||
label="Strongly suggested if you're new to Typebot."
|
||||
maxW="200px"
|
||||
hasArrow
|
||||
placement="right"
|
||||
rounded="md"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<ToolIcon color="blue.500" boxSize="25px" mr="2" />}
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<TemplateIcon color="orange.500" boxSize="25px" mr="2" />}
|
||||
onClick={onOpen}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from a template
|
||||
</Button>
|
||||
<ImportTypebotFromFileButton
|
||||
variant="outline"
|
||||
w="full"
|
||||
py="8"
|
||||
fontSize="lg"
|
||||
leftIcon={<DownloadIcon color="purple.500" boxSize="25px" mr="2" />}
|
||||
isLoading={isLoading}
|
||||
onNewTypebot={handleCreateSubmit}
|
||||
>
|
||||
Import a file
|
||||
</ImportTypebotFromFileButton>
|
||||
</Stack>
|
||||
<TemplatesModal isOpen={isOpen} onClose={onClose} />
|
||||
</VStack>
|
||||
)
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
import { chakra, MenuItem, MenuItemProps, useToast } from '@chakra-ui/react'
|
||||
import { FileIcon } from 'assets/icons'
|
||||
import { MoreButton } from 'components/dashboard/FolderContent/MoreButton'
|
||||
import { Button, ButtonProps, chakra, useToast } from '@chakra-ui/react'
|
||||
import { Typebot } from 'models'
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import { readFile } from 'services/utils'
|
||||
|
||||
type Props = {
|
||||
onNewTypebot: (typebot: Typebot) => void
|
||||
} & MenuItemProps
|
||||
} & ButtonProps
|
||||
|
||||
export const CreateTypebotMoreButton = ({ onNewTypebot }: Props) => {
|
||||
export const ImportTypebotFromFileButton = ({
|
||||
onNewTypebot,
|
||||
...props
|
||||
}: Props) => {
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
@ -36,16 +37,9 @@ export const CreateTypebotMoreButton = ({ onNewTypebot }: Props) => {
|
||||
onChange={handleInputChange}
|
||||
accept=".json"
|
||||
/>
|
||||
<MoreButton>
|
||||
<MenuItem
|
||||
as="label"
|
||||
cursor="pointer"
|
||||
icon={<FileIcon />}
|
||||
htmlFor="file-input"
|
||||
>
|
||||
Import from file
|
||||
</MenuItem>
|
||||
</MoreButton>
|
||||
<Button as="label" htmlFor="file-input" cursor="pointer" {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
@ -9,10 +9,10 @@ import {
|
||||
Button,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { TemplateProps } from 'layouts/dashboard/TemplatesContent'
|
||||
import { Typebot } from 'models'
|
||||
import React from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
||||
import { TemplateProps } from './data'
|
||||
|
||||
type Props = {
|
||||
template: TemplateProps
|
||||
|
@ -8,10 +8,10 @@ import {
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react'
|
||||
import { EyeIcon } from 'assets/icons'
|
||||
import { TemplateProps } from 'layouts/dashboard/TemplatesContent'
|
||||
import { Typebot } from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { sendRequest } from 'utils'
|
||||
import { TemplateProps } from './data'
|
||||
import { PreviewModal } from './PreviewModal'
|
||||
|
||||
type Props = {
|
||||
|
62
apps/builder/components/templates/TemplatesModal.tsx
Normal file
62
apps/builder/components/templates/TemplatesModal.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
Button,
|
||||
Heading,
|
||||
HStack,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { TypebotViewer } from 'bot-engine'
|
||||
import { Typebot } from 'models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { parseTypebotToPublicTypebot } from 'services/publicTypebot'
|
||||
import { sendRequest } from 'utils'
|
||||
import { TemplateProps, templates } from './data'
|
||||
|
||||
type Props = { isOpen: boolean; onClose: () => void }
|
||||
|
||||
export const TemplatesModal = ({ isOpen, onClose }: Props) => {
|
||||
const [typebot, setTypebot] = useState<Typebot>()
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplate(templates[0])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const fetchTemplate = async (template: TemplateProps) => {
|
||||
const { data, error } = await sendRequest(`/templates/${template.fileName}`)
|
||||
if (error) return toast({ title: error.name, description: error.message })
|
||||
setTypebot(data as Typebot)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="6xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader as={HStack} justifyContent="space-between" w="full">
|
||||
<Heading fontSize="xl">Lead generation</Heading>
|
||||
</ModalHeader>
|
||||
<ModalBody as={HStack}>
|
||||
{typebot && (
|
||||
<TypebotViewer typebot={parseTypebotToPublicTypebot(typebot)} />
|
||||
)}
|
||||
<Stack>
|
||||
<Button colorScheme="blue">Use this template</Button>
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
6
apps/builder/components/templates/data.ts
Normal file
6
apps/builder/components/templates/data.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||
|
||||
export const templates: TemplateProps[] = [
|
||||
{ name: 'Lead Generation', emoji: '🤝', fileName: 'lead-gen.json' },
|
||||
{ name: 'Customer Support', emoji: '😍', fileName: 'customer-support.json' },
|
||||
]
|
@ -1,110 +0,0 @@
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
Stack,
|
||||
useToast,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react'
|
||||
import { CreateTypebotMoreButton } from 'components/templates/ImportFileMenuItem'
|
||||
import { TemplateButton } from 'components/templates/TemplateButton'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { Typebot } from 'models'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { createTypebot, importTypebot } from 'services/typebots/typebots'
|
||||
|
||||
export type TemplateProps = { name: string; emoji: string; fileName: string }
|
||||
const templates: TemplateProps[] = [
|
||||
{ name: 'Lead Generation', emoji: '🤝', fileName: 'lead-gen.json' },
|
||||
{ name: 'Customer Support', emoji: '😍', fileName: 'customer-support.json' },
|
||||
]
|
||||
export const TemplatesContent = () => {
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
const [isFromScratchTooltipOpened, setIsFromScratchTooltipOpened] =
|
||||
useState(false)
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
title: 'An error occured',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
const isFirstBot = router.query.isFirstBot as string | undefined
|
||||
if (isFirstBot) setIsFromScratchTooltipOpened(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const handleCreateSubmit = async (typebot?: Typebot) => {
|
||||
if (!user) return
|
||||
setIsLoading(true)
|
||||
const folderId = router.query.folderId?.toString() ?? null
|
||||
const { error, data } = typebot
|
||||
? await importTypebot(
|
||||
{
|
||||
...typebot,
|
||||
ownerId: user.id,
|
||||
folderId,
|
||||
theme: {
|
||||
...typebot.theme,
|
||||
chat: {
|
||||
...typebot.theme.chat,
|
||||
hostAvatar: { isEnabled: true, url: user.image ?? undefined },
|
||||
},
|
||||
},
|
||||
},
|
||||
user
|
||||
)
|
||||
: await createTypebot({
|
||||
folderId,
|
||||
})
|
||||
if (error) toast({ description: error.message })
|
||||
if (data) router.push(`/typebots/${data.id}/edit`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex w="full" justifyContent="center">
|
||||
<Stack maxW="1000px" flex="1" pt="6" spacing={4}>
|
||||
<Flex justifyContent="space-between">
|
||||
<Tooltip
|
||||
isOpen={isFromScratchTooltipOpened}
|
||||
label="Strongly suggested if you're new to Typebot."
|
||||
maxW="200px"
|
||||
hasArrow
|
||||
placement="right"
|
||||
rounded="md"
|
||||
>
|
||||
<Button
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
colorScheme="blue"
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<CreateTypebotMoreButton onNewTypebot={handleCreateSubmit} />
|
||||
</Flex>
|
||||
<Divider />
|
||||
<Text>Or start from a template</Text>
|
||||
<SimpleGrid columns={4} spacing={4}>
|
||||
{templates.map((template) => (
|
||||
<TemplateButton
|
||||
key={template.name}
|
||||
onClick={handleCreateSubmit}
|
||||
template={template}
|
||||
/>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { VStack } from '@chakra-ui/react'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
import { TemplatesContent } from 'layouts/dashboard/TemplatesContent'
|
||||
import { CreateNewTypebotButtons } from 'components/templates/CreateNewTypebotButtons'
|
||||
|
||||
const TemplatesPage = () => (
|
||||
<Stack>
|
||||
<VStack>
|
||||
<Seo title="Templates" />
|
||||
<DashboardHeader />
|
||||
<TemplatesContent />
|
||||
</Stack>
|
||||
<CreateNewTypebotButtons />
|
||||
</VStack>
|
||||
)
|
||||
|
||||
export default TemplatesPage
|
||||
|
Reference in New Issue
Block a user