diff --git a/apps/builder/tsconfig.json b/apps/builder/tsconfig.json index 53c073685..5329cebd0 100644 --- a/apps/builder/tsconfig.json +++ b/apps/builder/tsconfig.json @@ -18,5 +18,5 @@ "composite": true }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "cypress"] + "exclude": ["node_modules"] } diff --git a/apps/landing-page/.eslintrc.js b/apps/landing-page/.eslintrc.js new file mode 100644 index 000000000..2044c18a2 --- /dev/null +++ b/apps/landing-page/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + ignorePatterns: ['node_modules'], + env: { + browser: true, + es6: true, + }, + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + 'next/core-web-vitals', + 'prettier', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features + sourceType: 'module', // Allows for the use of imports + ecmaFeatures: { + jsx: true, // Allows for the parsing of JSX + }, + }, + settings: { + react: { + version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use + }, + }, + plugins: ['prettier', 'react', '@typescript-eslint'], + rules: { + 'react/no-unescaped-entities': [0], + 'prettier/prettier': 'error', + 'react/display-name': [0], + }, +} diff --git a/apps/landing-page/.gitignore b/apps/landing-page/.gitignore new file mode 100644 index 000000000..c957eabdd --- /dev/null +++ b/apps/landing-page/.gitignore @@ -0,0 +1,38 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.blog_index_data +.blog_index_data_previews + +.now +.vercel + +.yarn/* +!.yarn/releases +!.yarn/plugins +.pnp.* +.yalc +yalc.lock \ No newline at end of file diff --git a/apps/landing-page/LICENSE b/apps/landing-page/LICENSE new file mode 100644 index 000000000..d13cc4b26 --- /dev/null +++ b/apps/landing-page/LICENSE @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/landing-page/README.md b/apps/landing-page/README.md new file mode 100644 index 000000000..0b1f27508 --- /dev/null +++ b/apps/landing-page/README.md @@ -0,0 +1,3 @@ +# Typebot.io + +This repository contains the source code of [Typebot's landing page](https://www.typebot.io) diff --git a/apps/landing-page/assets/icons/AccessibilityIcon.tsx b/apps/landing-page/assets/icons/AccessibilityIcon.tsx new file mode 100644 index 000000000..8a7056986 --- /dev/null +++ b/apps/landing-page/assets/icons/AccessibilityIcon.tsx @@ -0,0 +1,15 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const AccessibilityIcon = (props: IconProps) => ( + + Accessibility + + + +); diff --git a/apps/landing-page/assets/icons/AlbumsIcon.tsx b/apps/landing-page/assets/icons/AlbumsIcon.tsx new file mode 100644 index 000000000..bb6dbff1b --- /dev/null +++ b/apps/landing-page/assets/icons/AlbumsIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const AlbumsIcon = (props: IconProps) => ( + + Albums + + +); diff --git a/apps/landing-page/assets/icons/AnalyticsIcon.tsx b/apps/landing-page/assets/icons/AnalyticsIcon.tsx new file mode 100644 index 000000000..da2a05fd6 --- /dev/null +++ b/apps/landing-page/assets/icons/AnalyticsIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const AnalyticsIcon = (props: IconProps) => ( + + Analytics + + +); diff --git a/apps/landing-page/assets/icons/BookIcon.tsx b/apps/landing-page/assets/icons/BookIcon.tsx new file mode 100644 index 000000000..27fbff62a --- /dev/null +++ b/apps/landing-page/assets/icons/BookIcon.tsx @@ -0,0 +1,12 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const BookIcon = (props: IconProps) => ( + + Book + + +); diff --git a/apps/landing-page/assets/icons/BuildIcon.tsx b/apps/landing-page/assets/icons/BuildIcon.tsx new file mode 100644 index 000000000..b2ba68d34 --- /dev/null +++ b/apps/landing-page/assets/icons/BuildIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const BuildIcon = (props: IconProps) => ( + + Build + + +); diff --git a/apps/landing-page/assets/icons/CaluclatorIcon.tsx b/apps/landing-page/assets/icons/CaluclatorIcon.tsx new file mode 100644 index 000000000..ced96e66c --- /dev/null +++ b/apps/landing-page/assets/icons/CaluclatorIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const CalculatorIcon = (props: IconProps) => ( + + Calculator + + +); diff --git a/apps/landing-page/assets/icons/CheckIcon.tsx b/apps/landing-page/assets/icons/CheckIcon.tsx new file mode 100644 index 000000000..47b74f274 --- /dev/null +++ b/apps/landing-page/assets/icons/CheckIcon.tsx @@ -0,0 +1,15 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const CheckIcon = (props: IconProps) => ( + + Checkmark Circle + + +); diff --git a/apps/landing-page/assets/icons/ChevronDownIcon.tsx b/apps/landing-page/assets/icons/ChevronDownIcon.tsx new file mode 100644 index 000000000..c2179f1da --- /dev/null +++ b/apps/landing-page/assets/icons/ChevronDownIcon.tsx @@ -0,0 +1,21 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React, { SVGProps } from "react"; + +export const ChevronDownIcon = (props: IconProps) => ( + + Chevron Down + + +); diff --git a/apps/landing-page/assets/icons/ChevronRightIcon.tsx b/apps/landing-page/assets/icons/ChevronRightIcon.tsx new file mode 100644 index 000000000..cb556b6b5 --- /dev/null +++ b/apps/landing-page/assets/icons/ChevronRightIcon.tsx @@ -0,0 +1,21 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const ChevronRightIcon = (props: IconProps) => ( + + Chevron Forward + + +); diff --git a/apps/landing-page/assets/icons/CloseIcon.tsx b/apps/landing-page/assets/icons/CloseIcon.tsx new file mode 100644 index 000000000..8c1a3dedd --- /dev/null +++ b/apps/landing-page/assets/icons/CloseIcon.tsx @@ -0,0 +1,13 @@ +import React, { SVGProps } from "react"; + +export const CloseIcon = (props: SVGProps) => ( + + Close Circle + + +); diff --git a/apps/landing-page/assets/icons/ConditionIcon.tsx b/apps/landing-page/assets/icons/ConditionIcon.tsx new file mode 100644 index 000000000..81007c152 --- /dev/null +++ b/apps/landing-page/assets/icons/ConditionIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const ConditionIcon = (props: IconProps) => ( + + Git Branch + + +); diff --git a/apps/landing-page/assets/icons/DocIcon.tsx b/apps/landing-page/assets/icons/DocIcon.tsx new file mode 100644 index 000000000..da3776eaa --- /dev/null +++ b/apps/landing-page/assets/icons/DocIcon.tsx @@ -0,0 +1,15 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const DocIcon = (props: IconProps) => ( + + Document Text + + + +); diff --git a/apps/landing-page/assets/icons/EyeDropIcon.tsx b/apps/landing-page/assets/icons/EyeDropIcon.tsx new file mode 100644 index 000000000..e8e7a20b7 --- /dev/null +++ b/apps/landing-page/assets/icons/EyeDropIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const EyeDropIcon = (props: IconProps) => ( + + Eyedrop + + +); diff --git a/apps/landing-page/assets/icons/FolderIcon.tsx b/apps/landing-page/assets/icons/FolderIcon.tsx new file mode 100644 index 000000000..d9ccecc2c --- /dev/null +++ b/apps/landing-page/assets/icons/FolderIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const FolderIcon = (props: IconProps) => ( + + Folder Open + + +); diff --git a/apps/landing-page/assets/icons/GlobeIcon.tsx b/apps/landing-page/assets/icons/GlobeIcon.tsx new file mode 100644 index 000000000..30a5ffd2e --- /dev/null +++ b/apps/landing-page/assets/icons/GlobeIcon.tsx @@ -0,0 +1,15 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const GlobeIcon = (props: IconProps) => ( + + Globe + + + +); diff --git a/apps/landing-page/assets/icons/Logo.tsx b/apps/landing-page/assets/icons/Logo.tsx new file mode 100644 index 000000000..bcb561ce3 --- /dev/null +++ b/apps/landing-page/assets/icons/Logo.tsx @@ -0,0 +1,51 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const Logo = ({ + isDark, + ...props +}: { isDark?: boolean } & IconProps) => ( + + + + + + + +); diff --git a/apps/landing-page/assets/icons/MagicWandIcon.tsx b/apps/landing-page/assets/icons/MagicWandIcon.tsx new file mode 100644 index 000000000..b504bc313 --- /dev/null +++ b/apps/landing-page/assets/icons/MagicWandIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const MagicWandIcon = (props: IconProps) => ( + + Color Wand + + +); diff --git a/apps/landing-page/assets/icons/MapIcon.tsx b/apps/landing-page/assets/icons/MapIcon.tsx new file mode 100644 index 000000000..67ab23c69 --- /dev/null +++ b/apps/landing-page/assets/icons/MapIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const MapIcon = (props: IconProps) => ( + + Map + + +); diff --git a/apps/landing-page/assets/icons/PeopleCircleIcon.tsx b/apps/landing-page/assets/icons/PeopleCircleIcon.tsx new file mode 100644 index 000000000..f4b7e744f --- /dev/null +++ b/apps/landing-page/assets/icons/PeopleCircleIcon.tsx @@ -0,0 +1,15 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const PeopleCircleIcon = (props: IconProps) => ( + + People Circle + + + +); diff --git a/apps/landing-page/assets/icons/PersonAddIcon.tsx b/apps/landing-page/assets/icons/PersonAddIcon.tsx new file mode 100644 index 000000000..cdcb3fc00 --- /dev/null +++ b/apps/landing-page/assets/icons/PersonAddIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const PersonAddIcon = (props: IconProps) => ( + + Person Add + + +); diff --git a/apps/landing-page/assets/icons/QuoteLeftIcon.tsx b/apps/landing-page/assets/icons/QuoteLeftIcon.tsx new file mode 100644 index 000000000..d85b4f696 --- /dev/null +++ b/apps/landing-page/assets/icons/QuoteLeftIcon.tsx @@ -0,0 +1,13 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const QuoteLeftIcon = (props: IconProps) => ( + + + +); diff --git a/apps/landing-page/assets/icons/ShareIcon.tsx b/apps/landing-page/assets/icons/ShareIcon.tsx new file mode 100644 index 000000000..d3d8e9c53 --- /dev/null +++ b/apps/landing-page/assets/icons/ShareIcon.tsx @@ -0,0 +1,14 @@ +import Icon, { IconProps } from "@chakra-ui/icon"; +import React from "react"; + +export const ShareIcon = (props: IconProps) => ( + + Share Social + + +); diff --git a/apps/landing-page/assets/style.css b/apps/landing-page/assets/style.css new file mode 100644 index 000000000..9a746d8bd --- /dev/null +++ b/apps/landing-page/assets/style.css @@ -0,0 +1,19 @@ +.rounded-full { + border-radius: 99999px; +} + +.floating { + animation: float 6s ease-in-out infinite; +} + +@keyframes float { + 0% { + transform: translatey(0px); + } + 50% { + transform: translatey(-10px); + } + 100% { + transform: translatey(0px); + } +} diff --git a/apps/landing-page/components/Homepage/EndCta.tsx b/apps/landing-page/components/Homepage/EndCta.tsx new file mode 100644 index 000000000..12a06776f --- /dev/null +++ b/apps/landing-page/components/Homepage/EndCta.tsx @@ -0,0 +1,35 @@ +import { Box, Heading, Button, Text } from '@chakra-ui/react' +import React from 'react' +import { BackgroundPolygons } from './Hero/BackgroundPolygons' + +export const EndCta = () => { + return ( + + + + + Take your forms to the next level + + + Try Typebot for free and start improving the performance of your form + + + + + ) +} diff --git a/apps/landing-page/components/Homepage/Features/FeatureCard.tsx b/apps/landing-page/components/Homepage/Features/FeatureCard.tsx new file mode 100644 index 000000000..85c281369 --- /dev/null +++ b/apps/landing-page/components/Homepage/Features/FeatureCard.tsx @@ -0,0 +1,35 @@ +import { Flex, VStack } from '@chakra-ui/layout' +import { IconProps, Text } from '@chakra-ui/react' +import React from 'react' + +type FeatureCardProps = { + Icon: (props: IconProps) => JSX.Element + title: string + content: string +} + +export const FeatureCard = ({ Icon, title, content }: FeatureCardProps) => { + return ( + + + + + + {title} + + + {content} + + + ) +} diff --git a/apps/landing-page/components/Homepage/Features/index.tsx b/apps/landing-page/components/Homepage/Features/index.tsx new file mode 100644 index 000000000..b7bd1cfbd --- /dev/null +++ b/apps/landing-page/components/Homepage/Features/index.tsx @@ -0,0 +1,75 @@ +import React from 'react' +import { + Flex, + Heading, + SimpleGrid, + Stack, + Text, + VStack, +} from '@chakra-ui/react' +import { FeatureCard } from './FeatureCard' +import { FolderIcon } from 'assets/icons/FolderIcon' +import { AccessibilityIcon } from 'assets/icons/AccessibilityIcon' +import { CalculatorIcon } from 'assets/icons/CaluclatorIcon' +import { ConditionIcon } from 'assets/icons/ConditionIcon' +import { PersonAddIcon } from 'assets/icons/PersonAddIcon' +import { ShareIcon } from 'assets/icons/ShareIcon' + +const features = [ + { + Icon: AccessibilityIcon, + title: 'Hidden fields', + content: + 'Include data in your form URL to segment your user and use its data directly in your form.', + }, + { + Icon: PersonAddIcon, + title: 'Team collaboration', + content: 'Invite your teammates to work on your typebot with you', + }, + { + Icon: ConditionIcon, + title: 'Conditional branching', + content: + 'Make your form smarter by adding conditions to display custom answers to your users', + }, + { + Icon: CalculatorIcon, + title: 'Computation', + content: + 'Use the calculator to compute anything in Typebot directly to generate a quote or compute a score', + }, + { + Icon: ShareIcon, + title: 'Custom domain', + content: 'Connect your typebot to the custom URL of your choice', + }, + { + Icon: FolderIcon, + title: 'Folder management', + content: + 'Organize your typebots in specific folders to keep it clean and work with multiple clients', + }, +] + +export const Features = () => { + return ( + + + + + And many more features + + + Typebot makes form building easy and comes with powerful features + + + {features.map((feature, idx) => ( + + ))} + + + + + ) +} diff --git a/apps/landing-page/components/Homepage/Hero/BackgroundPolygons.tsx b/apps/landing-page/components/Homepage/Hero/BackgroundPolygons.tsx new file mode 100644 index 000000000..e00351c47 --- /dev/null +++ b/apps/landing-page/components/Homepage/Hero/BackgroundPolygons.tsx @@ -0,0 +1,108 @@ +import { chakra } from '@chakra-ui/react' +import React from 'react' + +export const BackgroundPolygons = () => { + return ( + <> + + + + + + + + ) +} + +const Triangle = () => ( + + + + + + + + + + + +) + +const DemiCircle = () => ( + + + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/Hero/Brands.tsx b/apps/landing-page/components/Homepage/Hero/Brands.tsx new file mode 100755 index 000000000..580a37a36 --- /dev/null +++ b/apps/landing-page/components/Homepage/Hero/Brands.tsx @@ -0,0 +1,819 @@ +import { Icon, IconProps } from '@chakra-ui/react' +import * as React from 'react' + +export const IbanFirst = (props: IconProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export const Lemlist = (props: IconProps) => ( + + + + + + + + + + + + + + + +) + +export const MakerLead = (props: IconProps) => ( + + + + +) + +export const SocialHackrs = (props: IconProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) + +export const Webisharp = (props: IconProps) => ( + + + + + + + + + + + + + + + + +) + +export const Awwwsome = (props: IconProps) => ( + + + + + + + + + + + + + + +) + +export const Obole = (props: IconProps) => ( + + + + + + + + + + + + +) + +export const PinpointInteractive = (props: IconProps) => ( + + + + + + + + + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/Hero/Testimonials/Testimonial.tsx b/apps/landing-page/components/Homepage/Hero/Testimonials/Testimonial.tsx new file mode 100755 index 000000000..5fdd9f5e8 --- /dev/null +++ b/apps/landing-page/components/Homepage/Hero/Testimonials/Testimonial.tsx @@ -0,0 +1,59 @@ +import { Box, Flex, HStack, Stack, Text } from '@chakra-ui/react' +import * as React from 'react' +import Image from 'next/image' +import { QuoteLeftIcon } from 'assets/icons/QuoteLeftIcon' + +interface TestimonialProps { + image: StaticImageData + name: string + role: string + children: React.ReactNode +} + +export const Testimonial = (props: TestimonialProps) => { + const { image, name, role, children } = props + return ( + + + + + {children} + + + + + {name} + + + {name} + + + {role} + + + + + ) +} diff --git a/apps/landing-page/components/Homepage/Hero/Testimonials/index.tsx b/apps/landing-page/components/Homepage/Hero/Testimonials/index.tsx new file mode 100755 index 000000000..09b2bbb5d --- /dev/null +++ b/apps/landing-page/components/Homepage/Hero/Testimonials/index.tsx @@ -0,0 +1,36 @@ +import { chakra, Stack } from '@chakra-ui/react' +import * as React from 'react' +import joshuaPictureSrc from 'public/images/homepage/joshua.jpg' +import julienPictureSrc from 'public/images/homepage/julien.jpg' +import { Testimonial } from './Testimonial' + +export const Testimonials = () => { + return ( + + + I upgraded my typeforms to typebots and saw a conversion rate increase{' '} + + from 14% to 43% + {' '} + on my marketing campaigns. I noticed the improvement on day one. That + was a game-changer. + + + I run Google ads all year long on our landing page that contains a + typebot. I saw a{' '} + + 2x increase + {' '} + on our conversation rate compared to our old WordPress form. + + + ) +} diff --git a/apps/landing-page/components/Homepage/Hero/index.tsx b/apps/landing-page/components/Homepage/Hero/index.tsx new file mode 100755 index 000000000..e9b85b391 --- /dev/null +++ b/apps/landing-page/components/Homepage/Hero/index.tsx @@ -0,0 +1,92 @@ +import { + Box, + Button, + chakra, + Flex, + Heading, + SimpleGrid, + Stack, + Text, + useColorModeValue as mode, + VStack, +} from '@chakra-ui/react' +import { NextChakraLink } from 'components/common/nextChakraAdapters/NextChakraLink' +import * as React from 'react' +import { Navbar } from '../../common/Navbar/Navbar' +import { BackgroundPolygons } from './BackgroundPolygons' +import * as Logos from './Brands' +import { Testimonials } from './Testimonials' + +export const Hero = () => { + return ( + + + + + + + + + 4x more + {' '} + responses with your forms + + + Typebot offers tools to create high-converting lead forms + specifically designed for your marketing campaigns + + + + + + + + + + + Trusted by 1,200+ companies and freelance marketers + + + + + + + + + + + + + ) +} diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/airtable.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/airtable.tsx new file mode 100644 index 000000000..2b24aaff5 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/airtable.tsx @@ -0,0 +1,28 @@ +import React, { SVGProps } from 'react' + +export const AirtableLogo = (props: SVGProps) => ( + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/facebook-pixel.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/facebook-pixel.tsx new file mode 100644 index 000000000..303d49f24 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/facebook-pixel.tsx @@ -0,0 +1,43 @@ +import React, { SVGProps } from 'react' + +export const FacebookPixelLogo = (props: SVGProps) => ( + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/gmail.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/gmail.tsx new file mode 100644 index 000000000..d6bf912dc --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/gmail.tsx @@ -0,0 +1,61 @@ +import React, { SVGProps } from 'react' + +export const GmailLogo = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-sheets.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-sheets.tsx new file mode 100644 index 000000000..55b4240b9 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-sheets.tsx @@ -0,0 +1,182 @@ +import React, { SVGProps } from 'react' + +export const GoogleSheetLogo = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-tag-manager.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-tag-manager.tsx new file mode 100644 index 000000000..3e0caa6c8 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/google-tag-manager.tsx @@ -0,0 +1,27 @@ +import React, { SVGProps } from 'react' + +export const GoogleTagManagerLogo = (props: SVGProps) => ( + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/hubspot.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/hubspot.tsx new file mode 100644 index 000000000..949e53d61 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/hubspot.tsx @@ -0,0 +1,15 @@ +import React, { SVGProps } from 'react' + +export const HubspotLogo = (props: SVGProps) => ( + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/mailchimp.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/mailchimp.tsx new file mode 100644 index 000000000..12ce717d2 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/mailchimp.tsx @@ -0,0 +1,43 @@ +import React, { SVGProps } from 'react' + +export const MailChimpLogo = (props: SVGProps) => ( + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/notion.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/notion.tsx new file mode 100644 index 000000000..58fec64bf --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/notion.tsx @@ -0,0 +1,17 @@ +import React, { SVGProps } from 'react' + +export const NotionLogo = (props: SVGProps) => ( + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/other.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/other.tsx new file mode 100644 index 000000000..a98603657 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/other.tsx @@ -0,0 +1,29 @@ +import React, { SVGProps } from 'react' + +export const OtherLogo = (props: SVGProps) => ( + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/pipedrive.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/pipedrive.tsx new file mode 100644 index 000000000..d159b18d6 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/pipedrive.tsx @@ -0,0 +1,17 @@ +import React, { SVGProps } from 'react' + +export const PipedriveLogo = (props: SVGProps) => ( + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/salesforce.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/salesforce.tsx new file mode 100644 index 000000000..caaf38db5 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/salesforce.tsx @@ -0,0 +1,55 @@ +import React, { SVGProps } from 'react' + +export const SalesforceLogo = (props: SVGProps) => ( + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/slack.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/slack.tsx new file mode 100644 index 000000000..1bd4af1e9 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/slack.tsx @@ -0,0 +1,35 @@ +import React, { SVGProps } from 'react' + +export const SlackLogo = (props: SVGProps) => ( + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/webflow.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/webflow.tsx new file mode 100644 index 000000000..ea2eb0a8b --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/webflow.tsx @@ -0,0 +1,19 @@ +import React, { SVGProps } from 'react' + +export const WebflowLogo = (props: SVGProps) => ( + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wix.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wix.tsx new file mode 100644 index 000000000..0f8fa550d --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wix.tsx @@ -0,0 +1,27 @@ +import React, { SVGProps } from 'react' + +export const WixLogo = (props: SVGProps) => ( + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wordpress.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wordpress.tsx new file mode 100644 index 000000000..7f6774561 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/wordpress.tsx @@ -0,0 +1,31 @@ +import React from 'react' + +export const WordpressLogo = (props: React.SVGProps) => ( + + + + + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/zapier.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/zapier.tsx new file mode 100644 index 000000000..dddb5ecc7 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/icons/zapier.tsx @@ -0,0 +1,15 @@ +import React, { SVGProps } from 'react' + +export const ZapierLogo = (props: SVGProps) => ( + + + +) diff --git a/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/index.tsx b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/index.tsx new file mode 100644 index 000000000..b2994b169 --- /dev/null +++ b/apps/landing-page/components/Homepage/IntegrateInYourWorkflow/index.tsx @@ -0,0 +1,116 @@ +import React from 'react' +import { Flex, Heading, SimpleGrid, Stack, Text } from '@chakra-ui/react' +import { AirtableLogo } from './icons/airtable' +import { FacebookPixelLogo } from './icons/facebook-pixel' +import { GmailLogo } from './icons/gmail' +import { GoogleSheetLogo } from './icons/google-sheets' +import { GoogleTagManagerLogo } from './icons/google-tag-manager' +import { HubspotLogo } from './icons/hubspot' +import { MailChimpLogo } from './icons/mailchimp' +import { NotionLogo } from './icons/notion' +import { OtherLogo } from './icons/other' +import { SalesforceLogo } from './icons/salesforce' +import { SlackLogo } from './icons/slack' +import { WebflowLogo } from './icons/webflow' +import { WixLogo } from './icons/wix' +import { WordpressLogo } from './icons/wordpress' +import { ZapierLogo } from './icons/zapier' +import { GlobeIcon } from '../../../assets/icons/GlobeIcon' + +export const IntegrateInYourWorkflow = () => { + return ( + + + + + + + + + Integrate it in your workflow in 5 minutes + + + Connect your form to any app in an instant + + + + Typebot comes with native integrations that allow you to connect + your form with your existing eco system + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/apps/landing-page/components/Homepage/IterateQuickly.tsx b/apps/landing-page/components/Homepage/IterateQuickly.tsx new file mode 100644 index 000000000..7a015b3eb --- /dev/null +++ b/apps/landing-page/components/Homepage/IterateQuickly.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import { Box, Flex, Heading, Stack, Text } from '@chakra-ui/react' +import inDepthAnalyticsSrc from 'public/images/homepage/analytics.png' +import Image from 'next/image' +import { AnalyticsIcon } from 'assets/icons/AnalyticsIcon' + +export const IterateQuickly = () => { + return ( + + + + + + + + + Iterate quickly and optimize the conversion + + + Each question has a cost in your drop-off rate. + + + + Typebot generates an in-depth analytics graph report with completion + and drop-off rates. That helps you know at a glance a potential + bottleneck in your form.
+ All you need to do is to fix it and hit the Publish button. +
+
+ + incomplete results illustration + +
+
+ ) +} diff --git a/apps/landing-page/components/Homepage/ListWithVerticalLines/List.tsx b/apps/landing-page/components/Homepage/ListWithVerticalLines/List.tsx new file mode 100755 index 000000000..1e8bfee7f --- /dev/null +++ b/apps/landing-page/components/Homepage/ListWithVerticalLines/List.tsx @@ -0,0 +1,23 @@ +import { Stack, StackProps } from '@chakra-ui/react' +import * as React from 'react' +import { ListItemProps } from './ListItem' + +export const List = (props: StackProps) => { + const { children, ...stackProps } = props + const items = React.useMemo( + () => + React.Children.toArray(children) + .filter>(React.isValidElement) + .map((item, index, array) => + index + 1 === array.length + ? React.cloneElement(item, { isLastItem: true }) + : item + ), + [children] + ) + return ( + + {items} + + ) +} diff --git a/apps/landing-page/components/Homepage/ListWithVerticalLines/ListItem.tsx b/apps/landing-page/components/Homepage/ListWithVerticalLines/ListItem.tsx new file mode 100755 index 000000000..9dac855ea --- /dev/null +++ b/apps/landing-page/components/Homepage/ListWithVerticalLines/ListItem.tsx @@ -0,0 +1,64 @@ +import { + Stack, + Flex, + Circle, + Text, + useColorModeValue, + Heading, + StackProps, +} from '@chakra-ui/react' +import * as React from 'react' + +export interface ListItemProps extends StackProps { + title: string + circleActivated?: boolean + subTitle?: string + icon?: React.ReactElement + isLastItem?: boolean +} + +export const ListItem = (props: ListItemProps) => { + const { + title, + subTitle, + icon, + isLastItem, + children, + circleActivated = true, + ...stackProps + } = props + + return ( + + + ) +} diff --git a/apps/landing-page/components/Homepage/ListWithVerticalLines/Placeholder.tsx b/apps/landing-page/components/Homepage/ListWithVerticalLines/Placeholder.tsx new file mode 100755 index 000000000..07e3e8575 --- /dev/null +++ b/apps/landing-page/components/Homepage/ListWithVerticalLines/Placeholder.tsx @@ -0,0 +1,12 @@ +import { Box, BoxProps, useColorModeValue } from '@chakra-ui/react' +import * as React from 'react' + +export const Placeholder = (props: BoxProps) => ( + +) diff --git a/apps/landing-page/components/Homepage/ListWithVerticalLines/index.tsx b/apps/landing-page/components/Homepage/ListWithVerticalLines/index.tsx new file mode 100755 index 000000000..631954204 --- /dev/null +++ b/apps/landing-page/components/Homepage/ListWithVerticalLines/index.tsx @@ -0,0 +1,38 @@ +import * as React from 'react' +import { Box } from '@chakra-ui/react' +import { List } from './List' +import { ListItem } from './ListItem' + +export type VerticalListItem = { + title: string + isActivated?: boolean + subTitle?: string + icon?: React.ReactElement + content: React.ReactNode +} + +type Props = { + items: VerticalListItem[] +} + +export const ListWithVerticalLines = ({ items }: Props) => { + return ( + + + + {items.map((item, idx) => ( + + {item.content} + + ))} + + + + ) +} diff --git a/apps/landing-page/components/Homepage/MarketingCampaignRecipe.tsx b/apps/landing-page/components/Homepage/MarketingCampaignRecipe.tsx new file mode 100644 index 000000000..69c6f2113 --- /dev/null +++ b/apps/landing-page/components/Homepage/MarketingCampaignRecipe.tsx @@ -0,0 +1,114 @@ +import React from 'react' +import { chakra, Flex, Heading, Stack, Text, VStack } from '@chakra-ui/react' +import { + ListWithVerticalLines, + VerticalListItem, +} from './ListWithVerticalLines' + +export const MarketingCampaignsRecipe = () => { + return ( + + + + + Easy marketing campaign recipe + + + Typebot takes care of almost everything in your campaign. + + + + + + + ) +} + +const items: VerticalListItem[] = [ + { + title: 'Create a Landing Page', + subTitle: 'This is not handled by Typebot', + icon: 1, + content: ( + + You create a personalized landing page for the customers you are + targeting for this campaign. + + ), + isActivated: false, + }, + { + title: 'Create a lead generation form', + icon: 2, + content: ( + + You need to create a form in order to qualify your lead + + Typebot allows you to create a{' '} + high-converting form in a + few minutes with a{' '} + + dead simple building experience + + + + ), + }, + { + title: 'Connect the form to your existing tools', + icon: 3, + content: ( + + + You need to collect your generated leads in your CRM (Hubspot, + Pipedrive) or in a Google Sheets for example. + + + With Typebot, you connect your form to your existing tools in a few + clicks thanks to our native integrations. + + + ), + }, + { + title: 'Embed the form in your landing page', + icon: 4, + content: ( + + + Your form needs to be embedded in your landing page. Most of the time, + it is painful when you're not a coder. + + + Typebot helps you easily embed your form with a dedicated library that + handles everything. + + + ), + }, + { + title: 'Run your campaign', + subTitle: 'This is not handled by Typebot', + icon: 5, + content: This is not handled by Typebot, + isActivated: false, + }, + { + title: 'Iterate & improve your conversion', + icon: 6, + content: ( + + + When a marketing campaign is launched you don't sit and wait for the + results. You have to analyse the results and potentially improve + things in order to increase the conversion rate. + + + Typebot comes with tools to analyse your typebot performance in real + time and helps you iterate quickly on improvements so that you + optimise your conversion rate and your campaign budget.{' '} + + + ), + }, +] diff --git a/apps/landing-page/components/Homepage/NativeFeeling.tsx b/apps/landing-page/components/Homepage/NativeFeeling.tsx new file mode 100644 index 000000000..dd0915c47 --- /dev/null +++ b/apps/landing-page/components/Homepage/NativeFeeling.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import { Box, Flex, Heading, Stack, Text } from '@chakra-ui/react' +import { EyeDropIcon } from 'assets/icons/EyeDropIcon' +import Image from 'next/image' +import nativeFeelingSrc from 'public/images/homepage/native-feeling.png' + +export const NativeFeeling = () => { + return ( + + + + + + + + Native feeling for a higher conversion + + As if you spent time crafting this form by hand + + + + A form that doesn't feel native to your landing page will impact + dramatically its conversion potential +
+ Typebot allows you to integrate the form in your landing page as if + it were specifically designed for it. You can customize pretty much + anything. +
+
+ + native feeling illustration + +
+
+ ) +} diff --git a/apps/landing-page/components/Homepage/StopLoosingData.tsx b/apps/landing-page/components/Homepage/StopLoosingData.tsx new file mode 100644 index 000000000..43f043210 --- /dev/null +++ b/apps/landing-page/components/Homepage/StopLoosingData.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import { Box, chakra, Flex, Heading, Stack, Text } from '@chakra-ui/react' +import incompleteResultsSrc from 'public/images/homepage/incomplete-results.png' +import Image from 'next/image' +import { AlbumsIcon } from 'assets/icons/AlbumsIcon' + +export const DontLooseData = () => { + return ( + + + + + + + + Stop losing data from your forms + + Each answered question has a huge value + + + + nstead of collecting it only when it is fully submitted, with a + Typebot form,{' '} + + you collect the result as soon as the user answers the first + question. + +
+ You have access to all the incomplete results in your dashboard so + that it helps you figure out how you can properly improve your form +
+
+ + + incomplete results illustration + +
+
+ ) +} diff --git a/apps/landing-page/components/Homepage/StopWastingTimeOnBuilding.tsx b/apps/landing-page/components/Homepage/StopWastingTimeOnBuilding.tsx new file mode 100644 index 000000000..16af13fd8 --- /dev/null +++ b/apps/landing-page/components/Homepage/StopWastingTimeOnBuilding.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import { Box, Flex, Heading, Stack, Text } from '@chakra-ui/react' +import ConversionGraderSrc from 'public/images/homepage/conversion-grader.png' +import Image from 'next/image' +import { BuildIcon } from 'assets/icons/BuildIcon' + +export const StopSpendingTimeOnBuilding = () => { + return ( + + + + + + + + + Stop wasting your time on building the form + + + Easy building experience and a grader to make your life easier + + + + + A form should be improved over time based on its performance. You + shouldn't spend time working on the "perfect" first version. +
+ Typebot comes with multiple verified templates to choose from. +
+ And it offers a grader tool that gives a score your form in + real-time based on best practices we collected from high-performing + forms +
+
+ + conversion grader illustration + +
+
+ ) +} diff --git a/apps/landing-page/components/PricingPage/PricingCard/ActionButton.tsx b/apps/landing-page/components/PricingPage/PricingCard/ActionButton.tsx new file mode 100644 index 000000000..438d10474 --- /dev/null +++ b/apps/landing-page/components/PricingPage/PricingCard/ActionButton.tsx @@ -0,0 +1,13 @@ +import { Button, ButtonProps } from '@chakra-ui/react' +import * as React from 'react' + +export const ActionButton = (props: ButtonProps) => ( + + + It's free! + + +) diff --git a/apps/landing-page/components/common/Footer.tsx b/apps/landing-page/components/common/Footer.tsx new file mode 100644 index 000000000..d1e698034 --- /dev/null +++ b/apps/landing-page/components/common/Footer.tsx @@ -0,0 +1,73 @@ +import React, { ReactNode } from 'react' + +import { Box, Container, Heading, SimpleGrid, Stack } from '@chakra-ui/react' +import { NextChakraLink } from './nextChakraAdapters/NextChakraLink' +import { Logo } from 'assets/icons/Logo' + +const facebookGroupUrl = 'https://www.facebook.com/groups/typebot' +const typebotLinkedInUrl = 'https://www.linkedin.com/company/typebot' +const typebotTwitterUrl = 'https://twitter.com/Typebot_io' +export const contactUrl = 'https://bot.typebot.io/landing-page-bubble-en' +export const roadmapLink = 'https://feedback.typebot.io/roadmap' + +export const Footer = () => { + return ( + + + + + + + + + + Product + + Roadmap + + Blog + Pricing + + + Comparisons + VS Typeform + VS Landbot + VS Tally + + + Community + + Facebook Group + + + Twitter + + + LinkedIn + + + + Company + + Contact + + + Terms of Service + + + Privacy Policy + + + + + + ) +} + +const ListHeader = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ) +} diff --git a/apps/landing-page/components/common/Navbar/MotionBox.tsx b/apps/landing-page/components/common/Navbar/MotionBox.tsx new file mode 100755 index 000000000..97ed60b18 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/MotionBox.tsx @@ -0,0 +1,5 @@ +import { Box, BoxProps } from '@chakra-ui/react' +import { HTMLMotionProps, motion } from 'framer-motion' + +export type MotionBoxProps = BoxProps & HTMLMotionProps<'div'> +export const MotionBox = motion(Box) diff --git a/apps/landing-page/components/common/Navbar/NavContent.tsx b/apps/landing-page/components/common/Navbar/NavContent.tsx new file mode 100755 index 000000000..48bec0ee3 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/NavContent.tsx @@ -0,0 +1,142 @@ +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 ( + <> + + + + + Typebot + + + + + + + + {links.map((link, idx) => + link.children ? ( + + ) : ( + + {link.label} + + ) + )} + + + + + ) +} + +const DesktopNavContent = ({ + links, + ...props +}: { links: Link[] } & FlexProps) => { + return ( + + + + + Typebot + + + + {links.map((link, idx) => ( + + {link.children ? ( + + ) : ( + + {link.label} + + )} + + ))} + + + + Sign in + + + + + ) +} + +export const NavContent = { + Mobile: MobileNavContext, + Desktop: DesktopNavContent, +} diff --git a/apps/landing-page/components/common/Navbar/NavLink.tsx b/apps/landing-page/components/common/Navbar/NavLink.tsx new file mode 100755 index 000000000..45cd5a708 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/NavLink.tsx @@ -0,0 +1,48 @@ +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 & { + active?: boolean + } + +const DesktopNavLink = (props: NavLinkProps) => { + const { href, children } = props + return ( + + ) +} +DesktopNavLink.displayName = 'DesktopNavLink' + +export const MobileNavLink = (props: NavLinkProps) => { + const { href, children } = props + return ( + + ) +} + +export const NavLink = { + Mobile: MobileNavLink, + Desktop: DesktopNavLink, +} diff --git a/apps/landing-page/components/common/Navbar/NavMenu.tsx b/apps/landing-page/components/common/Navbar/NavMenu.tsx new file mode 100755 index 000000000..274727c4d --- /dev/null +++ b/apps/landing-page/components/common/Navbar/NavMenu.tsx @@ -0,0 +1,45 @@ +import { useColorModeValue } from '@chakra-ui/react' +import { Variants } from 'framer-motion' +import * as React from 'react' +import { MotionBox, MotionBoxProps } from './MotionBox' + +export const NavMenu = (props: MotionBoxProps) => ( + +) + +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', + }, + }, +} diff --git a/apps/landing-page/components/common/Navbar/Navbar.tsx b/apps/landing-page/components/common/Navbar/Navbar.tsx new file mode 100755 index 000000000..8e41a4514 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/Navbar.tsx @@ -0,0 +1,30 @@ +import { Box } from '@chakra-ui/react' +import * as React from 'react' +import { NavContent } from './NavContent' +import { links } from './_data' + +export const Navbar = () => { + return ( + + + + + + + + + ) +} diff --git a/apps/landing-page/components/common/Navbar/Submenu.tsx b/apps/landing-page/components/common/Navbar/Submenu.tsx new file mode 100755 index 000000000..5a1c46ca4 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/Submenu.tsx @@ -0,0 +1,95 @@ +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 */} + + + + + {link.children?.map((item, idx) => ( + + {item.description} + + ))} + + + + + ) +} + +const MobileSubMenu = (props: SubmenuProps) => { + const { link } = props + const { isOpen, onToggle } = useDisclosure() + + return ( + + + + + {link.children?.map((item, idx) => ( + + {item.label} + + ))} + + + + ) +} + +export const Submenu = { + Mobile: MobileSubMenu, + Desktop: DesktopSubmenu, +} diff --git a/apps/landing-page/components/common/Navbar/SubmenuItem.tsx b/apps/landing-page/components/common/Navbar/SubmenuItem.tsx new file mode 100755 index 000000000..c97ab64aa --- /dev/null +++ b/apps/landing-page/components/common/Navbar/SubmenuItem.tsx @@ -0,0 +1,75 @@ +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 & { + title: string + icon?: React.ReactElement + children: React.ReactNode + href: string + } + +export const SubmenuItem = (props: SubmenuItemProps) => { + const { title, icon, children, href, ...rest } = props + return ( + +
+ {icon} +
+ + + + {title} + + + + + {children} + + +
+ ) +} diff --git a/apps/landing-page/components/common/Navbar/ToggleButton.tsx b/apps/landing-page/components/common/Navbar/ToggleButton.tsx new file mode 100755 index 000000000..d7abea772 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/ToggleButton.tsx @@ -0,0 +1,65 @@ +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 ( + + + + + ) +} + +interface ToggleButtonProps { + isOpen: boolean + onClick(): void +} + +export const ToggleButton = (props: ToggleButtonProps) => { + const { isOpen, onClick } = props + return ( +
+ + Toggle Menu +
+ ) +} diff --git a/apps/landing-page/components/common/Navbar/_data.tsx b/apps/landing-page/components/common/Navbar/_data.tsx new file mode 100755 index 000000000..0c383dc05 --- /dev/null +++ b/apps/landing-page/components/common/Navbar/_data.tsx @@ -0,0 +1,53 @@ +import { BookIcon } from 'assets/icons/BookIcon' +import { DocIcon } from 'assets/icons/DocIcon' +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', + children: [ + { + label: 'Blog', + description: + "Content about high-performing forms and guides on how to leverage Typebot's power", + href: '/blog', + icon: , + }, + { + label: 'Documentation', + description: + "Everything you need to know about how to use Typebot's builder", + href: 'https://docs.typebot.io', + icon: , + }, + { + label: 'Roadmap', + description: + "Follow the development and make suggestions for which features you'd like to see", + href: 'https://feedback.typebot.io/roadmap', + icon: , + }, + { + label: 'Community', + description: + 'Join our facebook community and get insights on how to create high performing surveys', + href: 'https://www.facebook.com/groups/262165102257585', + icon: , + }, + ], + }, + { label: 'Pricing', href: '/pricing' }, +] diff --git a/apps/landing-page/components/common/Navbar/useNavMenu.ts b/apps/landing-page/components/common/Navbar/useNavMenu.ts new file mode 100755 index 000000000..5ba701e4e --- /dev/null +++ b/apps/landing-page/components/common/Navbar/useNavMenu.ts @@ -0,0 +1,127 @@ +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 = Omit< + T, + 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag' +> + +export function useNavMenu() { + const { isOpen, onClose, onToggle, onOpen } = useDisclosure() + const menuRef = React.useRef(null) + const triggerRef = React.useRef(null) + const timeoutRef = React.useRef() + + 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> = { + 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, + } +} diff --git a/apps/landing-page/components/common/SocialMetaTags.tsx b/apps/landing-page/components/common/SocialMetaTags.tsx new file mode 100644 index 000000000..4bdb107a9 --- /dev/null +++ b/apps/landing-page/components/common/SocialMetaTags.tsx @@ -0,0 +1,34 @@ +import Head from 'next/head' +import React from 'react' + +export const SocialMetaTags = ({ + title, + description, + currentUrl, + imagePreviewUrl, +}: { + title: string + description: string + currentUrl: string + imagePreviewUrl: string +}) => ( + + {title} + + + + + + + + + + + + + + + + + +) diff --git a/apps/landing-page/components/common/TableCells.tsx b/apps/landing-page/components/common/TableCells.tsx new file mode 100644 index 000000000..3c4a79c85 --- /dev/null +++ b/apps/landing-page/components/common/TableCells.tsx @@ -0,0 +1,26 @@ +import { CheckIcon } from 'assets/icons/CheckIcon' +import { CloseIcon } from 'assets/icons/CloseIcon' +import { Td, Text } from '@chakra-ui/react' +import React, { ReactNode } from 'react' + +export const Yes = (props: { children?: ReactNode }) => ( + + + {props.children && ( + + {props.children} + + )} + +) + +export const No = (props: { children?: ReactNode }) => ( + + + {props.children && ( + + {props.children} + + )} + +) diff --git a/apps/landing-page/components/common/nextChakraAdapters/NextChakraLink.tsx b/apps/landing-page/components/common/nextChakraAdapters/NextChakraLink.tsx new file mode 100644 index 000000000..587df2cb6 --- /dev/null +++ b/apps/landing-page/components/common/nextChakraAdapters/NextChakraLink.tsx @@ -0,0 +1,50 @@ +import { PropsWithChildren } from 'react' +import NextLink from 'next/link' +import { LinkProps as NextLinkProps } from 'next/dist/client/link' +import { + Link as ChakraLink, + LinkProps as ChakraLinkProps, +} from '@chakra-ui/react' +import React from 'react' + +export type NextChakraLinkProps = PropsWithChildren< + NextLinkProps & Omit +> + +// Has to be a new component because both chakra and next share the `as` keyword +// eslint-disable-next-line react/display-name +export const NextChakraLink = React.forwardRef( + ( + { + href, + as, + replace, + scroll, + shallow, + prefetch, + children, + locale, + ...chakraProps + }: NextChakraLinkProps, + ref + ) => { + return ( + + {/*eslint-disable-next-line @typescript-eslint/ban-ts-comment*/} + {/*@ts-ignore*/} + + {children} + + + ) + } +) diff --git a/apps/landing-page/layouts/Homepage.tsx b/apps/landing-page/layouts/Homepage.tsx new file mode 100644 index 000000000..798ed1e01 --- /dev/null +++ b/apps/landing-page/layouts/Homepage.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { Stack } from '@chakra-ui/react' +import Head from 'next/head' +import { Footer } from 'components/common/Footer' +import { SocialMetaTags } from 'components/common/SocialMetaTags' +import { EndCta } from 'components/Homepage/EndCta' +import { Features } from 'components/Homepage/Features' +import { Hero } from 'components/Homepage/Hero' +import { IntegrateInYourWorkflow } from 'components/Homepage/IntegrateInYourWorkflow' +import { IterateQuickly } from 'components/Homepage/IterateQuickly' +import { MarketingCampaignsRecipe } from 'components/Homepage/MarketingCampaignRecipe' +import { NativeFeeling } from 'components/Homepage/NativeFeeling' +import { DontLooseData } from 'components/Homepage/StopLoosingData' +import { StopSpendingTimeOnBuilding } from 'components/Homepage/StopWastingTimeOnBuilding' + +const Homepage = () => { + return ( + + + + + + + + + + + + + + + +