2
0

refactor(lp): ♻️ Simplify header

This commit is contained in:
Baptiste Arnaud
2022-04-05 09:51:43 +02:00
parent ceedb05b64
commit 1fdf7e734b
29 changed files with 512 additions and 790 deletions

View File

@ -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>
)

View 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>
)
}

View File

@ -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>
</>
)
}

View File

@ -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

View File

@ -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 = {

View 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>
)
}

View 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' },
]

View File

@ -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>
)
}

View File

@ -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

View File

@ -1,13 +1,15 @@
import React, { SVGProps } from 'react'
import { Icon, IconProps } from '@chakra-ui/react'
import React from 'react'
import { featherIconsBaseProps } from './HamburgerIcon'
export const CloseIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
export const CloseIcon = (props: IconProps) => (
<Icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
fill="#CBD5E0"
viewBox="0 0 24 24"
{...featherIconsBaseProps}
{...props}
>
<title>Close Circle</title>
<path d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208 208-93.31 208-208S370.69 48 256 48zm75.31 260.69a16 16 0 11-22.62 22.62L256 278.63l-52.69 52.68a16 16 0 01-22.62-22.62L233.37 256l-52.68-52.69a16 16 0 0122.62-22.62L256 233.37l52.69-52.68a16 16 0 0122.62 22.62L278.63 256z" />
</svg>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</Icon>
)

View File

@ -0,0 +1,24 @@
import Icon, { IconProps } from '@chakra-ui/icon'
import React from 'react'
export const featherIconsBaseProps: IconProps = {
fill: 'none',
stroke: 'currentColor',
strokeWidth: '2px',
strokeLinecap: 'round',
strokeLinejoin: 'round',
}
export const HamburgerIcon = (props: IconProps) => (
<Icon
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentcolor"
{...featherIconsBaseProps}
{...props}
>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</Icon>
)

View File

@ -1 +1,2 @@
export { GitHubIcon } from './GithubLogo'
export * from './HamburgerIcon'

View File

@ -10,7 +10,7 @@ import {
} from '@chakra-ui/react'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import * as React from 'react'
import { Navbar } from '../../common/Navbar/Navbar'
import { Header } from '../../common/Header/Header'
import { BackgroundPolygons } from './BackgroundPolygons'
import * as Logos from './Brands'
import Image from 'next/image'
@ -19,7 +19,7 @@ import builderScreenshotSrc from 'public/images/homepage/builder.png'
export const Hero = () => {
return (
<Box as="section" overflow="hidden">
<Navbar />
<Header />
<Stack mx="auto" py="10" pos="relative" pb="32" px={[4, 0]}>
<BackgroundPolygons />
<VStack mb="20" spacing={20} alignItems="center">

View File

@ -0,0 +1,109 @@
import {
Button,
Flex,
Heading,
HStack,
IconButton,
useColorModeValue as mode,
useDisclosure,
Box,
} from '@chakra-ui/react'
import { HamburgerIcon } from 'assets/icons'
import { ChevronDownIcon } from 'assets/icons/ChevronDownIcon'
import { CloseIcon } from 'assets/icons/CloseIcon'
import { Logo } from 'assets/icons/Logo'
import * as React from 'react'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
import { MobileMenu } from './MobileMenu'
import { ResourcesMenu } from './ResourcesMenu'
export const Header = () => {
const { isOpen, onToggle } = useDisclosure()
const { isOpen: isMobileMenuOpen, onToggle: onMobileMenuToggle } =
useDisclosure()
return (
<Flex pos="relative" zIndex={10} w="full">
<HStack
as="header"
aria-label="Main navigation"
maxW="7xl"
w="full"
mx="auto"
px={{ base: '6', md: '8' }}
py="4"
justify="space-between"
>
<Flex
align="center"
justify="space-between"
className="nav-content__mobile"
color={mode('white', 'white')}
>
<HStack as={NextChakraLink} href="/" rel="home" ml="2">
<Logo boxSize="35px" />
<Heading as="p" fontSize="lg">
Typebot
</Heading>
</HStack>
</Flex>
<Box display={['block', 'block', 'none']}>
<IconButton
aria-label={'Open menu'}
icon={
isMobileMenuOpen ? (
<CloseIcon boxSize="20px" />
) : (
<HamburgerIcon boxSize="20px" />
)
}
variant="ghost"
colorScheme="gray"
onClick={onMobileMenuToggle}
/>
<MobileMenu isOpen={isMobileMenuOpen} />
</Box>
<HStack as="nav" spacing={4} display={['none', 'none', 'flex']}>
<Flex>
<Button
rightIcon={<ChevronDownIcon />}
onClick={onToggle}
variant="ghost"
colorScheme="gray"
fontWeight={700}
>
Resources
</Button>
<ResourcesMenu isOpen={isOpen} />
</Flex>
<Button
as={NextChakraLink}
href="/pricing"
variant="ghost"
colorScheme="gray"
fontWeight={700}
>
Pricing
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signin"
colorScheme="blue"
variant="outline"
fontWeight={700}
>
Sign in
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/register"
colorScheme="orange"
fontWeight={700}
>
Create a typebot
</Button>
</HStack>
</HStack>
</Flex>
)
}

View File

@ -0,0 +1,59 @@
import { Collapse, Stack, Button, Text } from '@chakra-ui/react'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
import { links } from './_data'
type Props = { isOpen: boolean }
export const MobileMenu = ({ isOpen }: Props) => (
<Collapse in={isOpen}>
<Stack
pos="absolute"
insetX={0}
bgGradient="linear(to-b, gray.900, gray.800)"
px="6"
py="10"
spacing={4}
>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signin"
colorScheme="blue"
variant="outline"
fontWeight={700}
>
Sign in
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/register"
colorScheme="orange"
fontWeight={700}
>
Create a typebot
</Button>
<Button
as={NextChakraLink}
href="/pricing"
variant="outline"
colorScheme="gray"
fontWeight={700}
>
Pricing
</Button>
<Text fontWeight="700">Resources:</Text>
{links[0].children?.map((link, idx) => (
<Button
as={NextChakraLink}
href={link.href}
key={idx}
variant="outline"
colorScheme="gray"
fontWeight={700}
py="6"
>
{link.label}
</Button>
))}
</Stack>
</Collapse>
)

View File

@ -0,0 +1,89 @@
import {
Box,
Center,
Collapse,
HStack,
SimpleGrid,
Text,
useColorModeValue as mode,
} from '@chakra-ui/react'
import { ChevronRightIcon } from 'assets/icons/ChevronRightIcon'
import * as React from 'react'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
import { links } from './_data'
type Props = { isOpen: boolean }
export const ResourcesMenu = ({ isOpen }: Props) => (
<Collapse in={isOpen} animateOpacity unmountOnExit={false}>
<Box
w="full"
shadow="lg"
pos="absolute"
insetX={0}
top="16"
py="12"
px="4"
bgGradient="linear(to-b, gray.900, gray.800)"
>
<Box maxW="7xl" mx="auto" px="8">
<SimpleGrid spacing="10" columns={2}>
{links[0].children?.map((item, idx) => (
<NextChakraLink
key={idx}
className="group"
href={item.href}
m="-3"
p="3"
display="flex"
alignItems="flex-start"
transition="all 0.2s"
rounded="lg"
_hover={{ bg: mode('gray.50', 'gray.600') }}
_focus={{ shadow: 'outline' }}
isExternal={
item.href.startsWith('https') &&
!item.href.includes('app.typebot.io')
}
>
<Center
aria-hidden
as="span"
flexShrink={0}
w="10"
h="10"
fontSize="3xl"
color={'blue.300'}
>
{item.icon}
</Center>
<Box marginStart="3" as="dl">
<HStack as="dt">
<Text
fontWeight="semibold"
color={mode('gray.900', 'white')}
_groupHover={{ color: mode('blue.600', 'inherit') }}
>
{item.label}
</Text>
<Box
fontSize="xs"
as={ChevronRightIcon}
transition="all 0.2s"
_groupHover={{
color: mode('blue.600', 'inherit'),
transform: 'translateX(2px)',
}}
/>
</HStack>
<Text as="dd" color={mode('gray.500', 'gray.400')}>
{item.description}
</Text>
</Box>
</NextChakraLink>
))}
</SimpleGrid>
</Box>
</Box>
</Collapse>
)

View File

@ -4,17 +4,6 @@ import { MapIcon } from 'assets/icons/MapIcon'
import { PeopleCircleIcon } from 'assets/icons/PeopleCircleIcon'
import * as React from 'react'
export interface Link {
label: string
href?: string
children?: Array<{
label: string
description?: string
href: string
icon?: React.ReactElement
}>
}
export const links = [
{
label: 'Resources',

View File

@ -1,5 +0,0 @@
import { Box, BoxProps } from '@chakra-ui/react'
import { HTMLMotionProps, motion } from 'framer-motion'
export type MotionBoxProps = BoxProps & HTMLMotionProps<'div'>
export const MotionBox = motion<BoxProps>(Box)

View File

@ -1,143 +0,0 @@
import {
Box,
Button,
Flex,
FlexProps,
HStack,
useDisclosure,
useColorModeValue as mode,
Heading,
} from '@chakra-ui/react'
import * as React from 'react'
import { Logo } from 'assets/icons/Logo'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
import { NavLink } from './NavLink'
import { NavMenu } from './NavMenu'
import { Submenu } from './Submenu'
import { ToggleButton } from './ToggleButton'
import { Link } from './_data'
const MobileNavContext = ({
links,
...props
}: { links: Link[] } & FlexProps) => {
const { isOpen, onToggle } = useDisclosure()
return (
<>
<Flex
align="center"
justify="space-between"
className="nav-content__mobile"
color={mode('white', 'white')}
{...props}
>
<HStack as={NextChakraLink} href="/" rel="home" ml="2">
<Logo boxSize="35px" />
<Heading as="p" fontSize="lg">
Typebot
</Heading>
</HStack>
<Box>
<ToggleButton isOpen={isOpen} onClick={onToggle} />
</Box>
</Flex>
<NavMenu animate={isOpen ? 'open' : 'closed'}>
{links.map((link, idx) =>
link.children ? (
<Submenu.Mobile key={idx} link={link} />
) : (
<NavLink.Mobile key={idx} href={link.href ?? '#'}>
{link.label}
</NavLink.Mobile>
)
)}
<Button
as={NextChakraLink}
href="https://app.typebot.io/signin"
colorScheme="blue"
variant="outline"
w="full"
size="lg"
mt="5"
>
Sign in
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/register"
colorScheme="orange"
w="full"
size="lg"
mt="5"
>
Create a typebot for free
</Button>
</NavMenu>
</>
)
}
const DesktopNavContent = ({
links,
...props
}: { links: Link[] } & FlexProps) => {
return (
<Flex
className="nav-content__desktop"
align="center"
justify="space-between"
{...props}
color={mode('bg.gray800', 'white')}
>
<HStack as={NextChakraLink} href="/" rel="home">
<Logo boxSize="35px" />
<Heading as="p" fontSize="lg">
Typebot
</Heading>
</HStack>
<HStack spacing="4" minW="240px" justify="space-between">
<HStack
as="ul"
id="nav__primary-menu"
aria-label="Main Menu"
listStyleType="none"
>
{links.map((link, idx) => (
<Box as="li" key={idx} id={`nav__menuitem-${idx}`}>
{link.children ? (
<Submenu.Desktop link={link} />
) : (
<NavLink.Desktop href={link.href ?? '#'}>
{link.label}
</NavLink.Desktop>
)}
</Box>
))}
</HStack>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signin"
colorScheme="blue"
variant="outline"
fontWeight="bold"
>
Sign in
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/register"
colorScheme="orange"
fontWeight="bold"
>
Create a typebot
</Button>
</HStack>
</Flex>
)
}
export const NavContent = {
Mobile: MobileNavContext,
Desktop: DesktopNavContent,
}

View File

@ -1,48 +0,0 @@
import { LinkProps as ChakraLinkProps, Button } from '@chakra-ui/react'
import { LinkProps as NextLinkProps } from 'next/link'
import * as React from 'react'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
type NavLinkProps = NextLinkProps &
Omit<ChakraLinkProps, 'as'> & {
active?: boolean
}
const DesktopNavLink = (props: NavLinkProps) => {
const { href, children } = props
return (
<Button
as={NextChakraLink}
href={href}
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
variant="ghost"
colorScheme="gray"
>
{children}
</Button>
)
}
DesktopNavLink.displayName = 'DesktopNavLink'
export const MobileNavLink = (props: NavLinkProps) => {
const { href, children } = props
return (
<Button
as={NextChakraLink}
href={href ?? '#'}
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
variant="ghost"
colorScheme="gray"
w="full"
h="3rem"
justifyContent="flex-start"
>
{children}
</Button>
)
}
export const NavLink = {
Mobile: MobileNavLink,
Desktop: DesktopNavLink,
}

View File

@ -1,43 +0,0 @@
import { Variants } from 'framer-motion'
import * as React from 'react'
import { MotionBox, MotionBoxProps } from './MotionBox'
export const NavMenu = (props: MotionBoxProps) => (
<MotionBox
initial="init"
variants={variants}
outline="0"
opacity="0"
bgGradient="linear(to-b, gray.900, gray.800)"
w="full"
shadow="lg"
px="4"
pos="absolute"
insetX="0"
py="12"
{...props}
/>
)
const variants: Variants = {
init: {
opacity: 0,
y: -4,
display: 'none',
transition: { duration: 0 },
},
open: {
opacity: 1,
y: 0,
display: 'block',
transition: { duration: 0.15 },
},
closed: {
opacity: 0,
y: -4,
transition: { duration: 0.1 },
transitionEnd: {
display: 'none',
},
},
}

View File

@ -1,30 +0,0 @@
import { Box } from '@chakra-ui/react'
import * as React from 'react'
import { NavContent } from './NavContent'
import { links } from './_data'
export const Navbar = () => {
return (
<Box w="full">
<Box as="header" position="relative" zIndex="10">
<Box
as="nav"
aria-label="Main navigation"
maxW="7xl"
mx="auto"
px={{ base: '6', md: '8' }}
py="4"
>
<NavContent.Mobile
display={{ base: 'flex', lg: 'none' }}
links={links}
/>
<NavContent.Desktop
display={{ base: 'none', lg: 'flex' }}
links={links}
/>
</Box>
</Box>
</Box>
)
}

View File

@ -1,95 +0,0 @@
import { useNavMenu } from './useNavMenu'
import {
Box,
Collapse,
SimpleGrid,
useDisclosure,
Button,
} from '@chakra-ui/react'
import * as React from 'react'
import { Link } from './_data'
import { NavLink } from './NavLink'
import { NavMenu } from './NavMenu'
import { SubmenuItem as DesktopMenuItem } from './SubmenuItem'
import { ChevronDownIcon } from '../../../assets/icons/ChevronDownIcon'
interface SubmenuProps {
link: Link
}
const DesktopSubmenu = (props: SubmenuProps) => {
const { link } = props
const { isOpen, getMenuProps, getTriggerProps } = useNavMenu()
return (
<>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
{/* @ts-ignore */}
<Button
rightIcon={<ChevronDownIcon />}
variant="ghost"
colorScheme="gray"
{...getTriggerProps()}
>
{link.label}
</Button>
<NavMenu {...getMenuProps()} animate={isOpen ? 'open' : 'closed'}>
<Box maxW="7xl" mx="auto" px="8">
<SimpleGrid spacing="10" columns={2}>
{link.children?.map((item, idx) => (
<DesktopMenuItem
key={idx}
title={item.label}
href={item.href ?? '#'}
icon={item.icon}
>
{item.description}
</DesktopMenuItem>
))}
</SimpleGrid>
</Box>
</NavMenu>
</>
)
}
const MobileSubMenu = (props: SubmenuProps) => {
const { link } = props
const { isOpen, onToggle } = useDisclosure()
return (
<Box>
<Button
textAlign="start"
type="button"
cursor="pointer"
onClick={onToggle}
paddingEnd="4"
variant="ghost"
colorScheme="gray"
h="3rem"
w="full"
justifyContent="flex-start"
>
<Box mr="4">{link.label}</Box>
<Box
as={ChevronDownIcon}
transform={`rotate(${isOpen ? '180deg' : '0deg'})`}
/>
</Button>
<Collapse in={isOpen}>
<Box pl="5">
{link.children?.map((item, idx) => (
<NavLink.Mobile key={idx} href={item.href ?? '#'}>
{item.label}
</NavLink.Mobile>
))}
</Box>
</Collapse>
</Box>
)
}
export const Submenu = {
Mobile: MobileSubMenu,
Desktop: DesktopSubmenu,
}

View File

@ -1,75 +0,0 @@
import {
Box,
Center,
HStack,
Text,
useColorModeValue as mode,
LinkProps as ChakraLinkProps,
} from '@chakra-ui/react'
import { LinkProps } from 'next/link'
import * as React from 'react'
import { ChevronRightIcon } from '../../../assets/icons/ChevronRightIcon'
import { NextChakraLink } from '../nextChakraAdapters/NextChakraLink'
type SubmenuItemProps = LinkProps &
Omit<ChakraLinkProps, 'as'> & {
title: string
icon?: React.ReactElement
children: React.ReactNode
href: string
}
export const SubmenuItem = (props: SubmenuItemProps) => {
const { title, icon, children, href, ...rest } = props
return (
<NextChakraLink
className="group"
href={href}
m="-3"
p="3"
display="flex"
alignItems="flex-start"
transition="all 0.2s"
rounded="lg"
_hover={{ bg: mode('gray.50', 'gray.600') }}
_focus={{ shadow: 'outline' }}
isExternal={href.startsWith('https') && !href.includes('app.typebot.io')}
{...rest}
>
<Center
aria-hidden
as="span"
flexShrink={0}
w="10"
h="10"
fontSize="3xl"
color={'blue.300'}
>
{icon}
</Center>
<Box marginStart="3" as="dl">
<HStack as="dt">
<Text
fontWeight="semibold"
color={mode('gray.900', 'white')}
_groupHover={{ color: mode('blue.600', 'inherit') }}
>
{title}
</Text>
<Box
fontSize="xs"
as={ChevronRightIcon}
transition="all 0.2s"
_groupHover={{
color: mode('blue.600', 'inherit'),
transform: 'translateX(2px)',
}}
/>
</HStack>
<Text as="dd" color={mode('gray.500', 'gray.400')}>
{children}
</Text>
</Box>
</NextChakraLink>
)
}

View File

@ -1,65 +0,0 @@
import { Box, Center, chakra, VisuallyHidden } from '@chakra-ui/react'
import React from 'react'
const Bar = chakra('span', {
baseStyle: {
display: 'block',
pos: 'absolute',
w: '1.25rem',
h: '0.125rem',
rounded: 'full',
bg: 'currentcolor',
mx: 'auto',
insetStart: '0.125rem',
transition: 'all 0.12s',
},
})
const ToggleIcon = (props: { active: boolean }) => {
const { active } = props
return (
<Box
className="group"
data-active={active ? '' : undefined}
as="span"
display="block"
w="1.5rem"
h="1.5rem"
pos="relative"
aria-hidden
pointerEvents="none"
>
<Bar
top="0.4375rem"
_groupActive={{ top: '0.6875rem', transform: 'rotate(45deg)' }}
/>
<Bar
bottom="0.4375rem"
_groupActive={{ bottom: '0.6875rem', transform: 'rotate(-45deg)' }}
/>
</Box>
)
}
interface ToggleButtonProps {
isOpen: boolean
onClick(): void
}
export const ToggleButton = (props: ToggleButtonProps) => {
const { isOpen, onClick } = props
return (
<Center
marginStart="-6"
px="4"
py="4"
as="button"
color="gray.400"
_active={{ color: 'blue.600' }}
onClick={onClick}
>
<ToggleIcon active={isOpen} />
<VisuallyHidden>Toggle Menu</VisuallyHidden>
</Center>
)
}

View File

@ -1,127 +0,0 @@
import { useDisclosure } from '@chakra-ui/react'
import { isFocusable, getOwnerDocument, isRightClick } from '@chakra-ui/utils'
import * as React from 'react'
const getTarget = (event: React.FocusEvent) =>
(event.relatedTarget || document.activeElement) as HTMLElement
type OmitMotionProps<T> = Omit<
T,
'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'
>
export function useNavMenu() {
const { isOpen, onClose, onToggle, onOpen } = useDisclosure()
const menuRef = React.useRef<HTMLDivElement>(null)
const triggerRef = React.useRef<HTMLAnchorElement>(null)
const timeoutRef = React.useRef<number>()
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
window.clearTimeout(timeoutRef.current)
}
}
}, [])
const focusMenu = () => {
timeoutRef.current = window.setTimeout(() => {
menuRef.current?.focus({ preventScroll: true })
}, 100)
}
const getTriggerProps = () => {
const triggerProps: React.ComponentPropsWithRef<'a'> = {
ref: triggerRef,
'aria-expanded': isOpen,
'aria-controls': 'menu',
'aria-haspopup': 'true',
}
triggerProps.onClick = (event: React.MouseEvent) => {
if (isRightClick(event)) return
onToggle()
if (!isOpen) {
focusMenu()
}
}
triggerProps.onBlur = (event: React.FocusEvent) => {
const target = getTarget(event)
if (isOpen && !menuRef.current?.contains(target)) {
onClose()
}
}
triggerProps.onKeyDown = (event: React.KeyboardEvent) => {
if (isOpen && event.key === 'Escape') {
onClose()
triggerRef.current?.focus({ preventScroll: true })
}
if (event.key === 'ArrowDown') {
if (!isOpen) onOpen()
focusMenu()
}
}
return triggerProps
}
const getMenuProps = () => {
const menuProps: OmitMotionProps<React.ComponentPropsWithRef<'div'>> = {
ref: menuRef,
id: 'menu',
tabIndex: -1,
}
menuProps.onKeyDown = (event: React.KeyboardEvent) => {
if (!isOpen) return
switch (event.key) {
case 'Escape': {
onClose()
return triggerRef.current?.focus()
}
case 'ArrowDown': {
const doc = getOwnerDocument(menuRef.current)
const next = doc?.activeElement
?.nextElementSibling as HTMLAnchorElement | null
return next?.focus()
}
case 'ArrowUp': {
const doc = getOwnerDocument(menuRef.current)
const prev = doc?.activeElement
?.previousElementSibling as HTMLAnchorElement | null
const el = (prev ?? triggerRef.current) as HTMLElement
return el.focus()
}
default:
break
}
}
menuProps.onBlur = (event: React.FocusEvent) => {
const target = getTarget(event)
const shouldBlur =
isOpen &&
!target.isSameNode(triggerRef.current) &&
!menuRef.current?.contains(target)
if (shouldBlur) {
onClose()
if (!isFocusable(target)) {
triggerRef.current?.focus({ preventScroll: true })
}
}
}
return menuProps
}
return {
isOpen,
onClose,
getTriggerProps,
getMenuProps,
}
}

View File

@ -10,7 +10,7 @@ import {
Stack,
} from '@chakra-ui/react'
import { Footer } from 'components/common/Footer'
import { Navbar } from 'components/common/Navbar/Navbar'
import { Header } from 'components/common/Header/Header'
import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink'
import { SocialMetaTags } from 'components/common/SocialMetaTags'
import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons'
@ -38,7 +38,7 @@ const Pricing = () => {
<SocialMetaTags currentUrl={`https://www.typebot.io/pricing`} />
<BackgroundPolygons />
<DarkMode>
<Navbar />
<Header />
</DarkMode>
<VStack spacing={40} w="full">

View File

@ -1,13 +1,13 @@
import { Heading } from '@chakra-ui/layout'
import { Stack } from '@chakra-ui/react'
import { Navbar } from 'components/common/Navbar/Navbar'
import { Header } from 'components/common/Header/Header'
import { SocialMetaTags } from 'components/common/SocialMetaTags'
import React from 'react'
const PrivacyPolicies = () => {
return (
<div className="flex flex-col items-center w-full overflow-x-hidden ">
<Navbar />
<Header />
<SocialMetaTags currentUrl={`https://www.typebot.io/privacy-policies`} />
<Stack spacing={10} mx="auto" maxW="3xl" my="20">
<Heading as="h1">Privacy Policy for Typebot</Heading>

View File

@ -1,12 +1,12 @@
import { Stack, Heading } from '@chakra-ui/react'
import { Navbar } from 'components/common/Navbar/Navbar'
import { Header } from 'components/common/Header/Header'
import { SocialMetaTags } from 'components/common/SocialMetaTags'
const PrivacyPolicies = () => {
return (
<div className="flex flex-col items-center w-full overflow-x-hidden ">
<SocialMetaTags currentUrl={`https://www.typebot.io/terms-of-service`} />
<Navbar />
<Header />
<Stack spacing={10} mx="auto" maxW="3xl" my="20">
<Heading as="h1">Website Terms and Conditions of Use</Heading>