2
0

feat: ️ Add docs and connect Stripe

This commit is contained in:
Baptiste Arnaud
2022-02-14 16:41:39 +01:00
parent aeb3e4caa7
commit 56bd5fafc3
50 changed files with 6332 additions and 685 deletions

17
.github/workflows/docsearch-scrap.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Docsearch Scrap
on:
schedule:
- cron: '0 8 * * *'
jobs:
scrap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: darrenjennings/algolia-docsearch-action@master
with:
algolia_application_id: 'DXYNLHZTGJ'
algolia_api_key: ${{secrets.DOCSEARCH_API_KEY}}
file: 'apps/docs/docsearch-scrapper-config.json'

3
.gitignore vendored
View File

@ -12,3 +12,6 @@ dist
test-results
**/api/scripts
.sentryclirc
.docusaurus
.cache-loader
build

View File

@ -38,6 +38,8 @@ FACEBOOK_CLIENT_SECRET=
# (Optional) Subscription Payment
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_PRICE_USD_ID=
STRIPE_PRICE_EUR_ID=
STRIPE_WEBHOOK_SECRET=
# (Optional) Used for GIF search

View File

@ -8,6 +8,7 @@ import {
useToast,
} from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { UpgradeButton } from 'components/shared/buttons/UpgradeButton'
import { useUser } from 'contexts/UserContext'
import { Plan } from 'db'
import { useRouter } from 'next/router'
@ -54,9 +55,7 @@ export const BillingSection = () => {
Manage my subscription
</Button>
)}
{user?.plan === Plan.FREE && (
<Button colorScheme="blue">Upgrade</Button>
)}
{user?.plan === Plan.FREE && <UpgradeButton />}
{user?.plan === Plan.FREE && (
<HStack as="form" onSubmit={handleCouponCodeRedeem}>
<Input name="coupon" placeholder="Coupon code..." />

View File

@ -28,7 +28,8 @@ export const SignInForm = ({
})
useEffect(() => {
if (status === 'authenticated') router.replace('/typebots')
if (status === 'authenticated')
router.replace({ pathname: '/typebots', query: router.query })
}, [status, router])
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) =>

View File

@ -1,16 +1,28 @@
import { Stack, Button } from '@chakra-ui/react'
import { FacebookIcon, GithubIcon, GoogleIcon } from 'assets/icons'
import { signIn, useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import React from 'react'
import { stringify } from 'qs'
export const SocialLoginButtons = () => {
const { query } = useRouter()
const { status } = useSession()
const handleGitHubClick = async () => signIn('github')
const handleGitHubClick = async () =>
signIn('github', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleGoogleClick = async () => signIn('google')
const handleGoogleClick = async () =>
signIn('google', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleFacebookClick = async () => signIn('facebook')
const handleFacebookClick = async () =>
signIn('facebook', {
callbackUrl: `/typebots?${stringify(query)}`,
})
return (
<Stack>

View File

@ -0,0 +1,16 @@
import { Button, ButtonProps, useDisclosure } from '@chakra-ui/react'
import React from 'react'
import { UpgradeModal } from '../modals/UpgradeModal.'
import { LimitReached } from '../modals/UpgradeModal./UpgradeModal'
type Props = { type?: LimitReached } & ButtonProps
export const UpgradeButton = ({ type, ...props }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure()
return (
<Button colorScheme="blue" {...props} onClick={onOpen}>
Upgrade
<UpgradeModal isOpen={isOpen} onClose={onClose} type={type} />
</Button>
)
}

View File

@ -31,10 +31,12 @@ type UpgradeModalProps = {
export const UpgradeModal = ({ type, onClose, isOpen }: UpgradeModalProps) => {
const { user } = useUser()
const [payLoading, setPayLoading] = useState(false)
const [userLanguage, setUserLanguage] = useState<string>('en')
const [currency, setCurrency] = useState<'usd' | 'eur'>('usd')
useEffect(() => {
setUserLanguage(navigator.language.toLowerCase())
setCurrency(
navigator.languages.find((l) => l.includes('fr')) ? 'eur' : 'usd'
)
}, [])
let limitLabel
@ -55,7 +57,7 @@ export const UpgradeModal = ({ type, onClose, isOpen }: UpgradeModalProps) => {
const handlePayClick = async () => {
if (!user) return
setPayLoading(true)
await pay(user)
await pay(user, currency)
}
return (
@ -73,7 +75,7 @@ export const UpgradeModal = ({ type, onClose, isOpen }: UpgradeModalProps) => {
)}
<PricingCard
data={{
price: userLanguage.includes('fr') ? '25€' : '$30',
price: currency === 'eur' ? '25€' : '$30',
name: 'Pro plan',
features: [
'Branding removed',

View File

@ -0,0 +1,11 @@
import Mailgun from 'mailgun.js'
import formData from 'form-data'
export const initMailgun = () => {
const mailgun = new Mailgun(formData)
return mailgun.client({
username: 'api',
key: '0b024a6aac02d3f52d30999674bcde30-53c13666-e8653f58',
url: 'https://api.eu.mailgun.net',
})
}

View File

@ -44,6 +44,7 @@
"deep-object-diff": "^1.1.7",
"fast-equals": "^2.0.4",
"focus-visible": "^5.2.0",
"form-data": "^4.0.0",
"framer-motion": "^4",
"google-auth-library": "^7.11.0",
"google-spreadsheet": "^3.2.0",
@ -52,6 +53,7 @@
"immer": "^9.0.12",
"js-video-url-parser": "^0.5.1",
"kbar": "^0.1.0-beta.24",
"mailgun.js": "^4.2.1",
"micro": "^9.3.4",
"micro-cors": "^0.1.1",
"models": "*",

View File

@ -19,6 +19,13 @@ const providers: Provider[] = [
},
},
from: `"${process.env.AUTH_EMAIL_FROM_NAME}" <${process.env.AUTH_EMAIL_FROM_EMAIL}>`,
// sendVerificationRequest({
// identifier: email,
// url,
// provider: { server, from },
// }) {
// console.log(url)
// },
}),
]

View File

@ -3,7 +3,6 @@ import { methodNotAllowed } from 'utils'
import Stripe from 'stripe'
import { withSentry } from '@sentry/nextjs'
const usdPriceIdTest = 'price_1Jc4TQKexUFvKTWyGvsH4Ff5'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'POST') {
if (!process.env.STRIPE_SECRET_KEY)
@ -11,21 +10,25 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2020-08-27',
})
const { email } = req.body
const { email, currency } = JSON.parse(req.body)
const session = await stripe.checkout.sessions.create({
success_url: `${req.headers.origin}/typebots?stripe=success`,
cancel_url: `${req.headers.origin}/typebots?stripe=cancel`,
automatic_tax: { enabled: true },
allow_promotion_codes: true,
customer_email: email,
mode: 'subscription',
line_items: [
{
price: usdPriceIdTest,
price:
currency === 'eur'
? process.env.STRIPE_PRICE_EUR_ID
: process.env.STRIPE_PRICE_USD_ID,
quantity: 1,
},
],
})
res.status(201).send({ sessionId: session.id })
return res.status(201).send({ sessionId: session.id })
}
return methodNotAllowed(res)
}

View File

@ -20,9 +20,10 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
})
const session = await stripe.billingPortal.sessions.create({
customer: user.stripeId,
return_url: `${req.headers.origin}/account`,
return_url: req.headers.referer,
})
res.status(201).redirect(session.url)
res.redirect(session.url)
return
}
return methodNotAllowed(res)
}

View File

@ -47,6 +47,7 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
where: { email: customer_email },
data: { plan: Plan.PRO, stripeId: session.customer as string },
})
return res.status(200).send({ message: 'user upgraded in DB' })
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription
@ -58,6 +59,10 @@ const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
plan: Plan.FREE,
},
})
return res.status(200).send({ message: 'user downgraded in DB' })
}
default: {
return res.status(304).send({ message: 'event not handled' })
}
}
} catch (err) {

View File

@ -1,17 +1,65 @@
import React from 'react'
import { Stack } from '@chakra-ui/layout'
import React, { useEffect, useState } from 'react'
import { Flex, Stack } from '@chakra-ui/layout'
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
import { Seo } from 'components/Seo'
import { FolderContent } from 'components/dashboard/FolderContent'
import { TypebotDndContext } from 'contexts/TypebotDndContext'
import { useRouter } from 'next/router'
import { redeemCoupon } from 'services/coupons'
import { Spinner, useToast } from '@chakra-ui/react'
import { pay } from 'services/stripe'
import { useUser } from 'contexts/UserContext'
const DashboardPage = () => {
const [isLoading, setIsLoading] = useState(false)
const { query, isReady } = useRouter()
const { user } = useUser()
const toast = useToast({
position: 'top-right',
status: 'success',
})
useEffect(() => {
const subscribe = query.subscribe?.toString()
if (subscribe && user && user.plan === 'FREE') {
setIsLoading(true)
pay(
user,
navigator.languages.find((l) => l.includes('fr')) ? 'eur' : 'usd'
)
}
}, [query.subscribe, user])
useEffect(() => {
if (!isReady) return
const couponCode = query.coupon?.toString()
const stripeStatus = query.stripe?.toString()
if (stripeStatus === 'success')
toast({
title: 'Typebot Pro',
description: "You've successfully subscribed 🎉",
})
if (!couponCode) return
setIsLoading(true)
redeemCoupon(couponCode).then(() => {
location.href = '/typebots'
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isReady])
return (
<Stack minH="100vh">
<Seo title="My typebots" />
<DashboardHeader />
<TypebotDndContext>
<FolderContent folder={null} />
{isLoading ? (
<Flex w="full" justifyContent="center" pt="10">
<Spinner />
</Flex>
) : (
<FolderContent folder={null} />
)}
</TypebotDndContext>
</Stack>
)

View File

@ -2,14 +2,14 @@ import { User } from 'db'
import { loadStripe } from '@stripe/stripe-js'
import { sendRequest } from 'utils'
export const pay = async (user: User) => {
export const pay = async (user: User, currency: 'usd' | 'eur') => {
if (!process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
throw new Error('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is missing in env')
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
const { data, error } = await sendRequest<{ sessionId: string }>({
method: 'POST',
url: '/api/stripe/checkout',
body: { email: user.email },
body: { email: user.email, currency },
})
if (error || !data) return
return stripe?.redirectToCheckout({

33
apps/docs/README.md Normal file
View File

@ -0,0 +1,33 @@
# Typebot docs
This is the source code of Typebot's documentation located at https://docs.typebot.io
# Contribute
If you're not technical or not familiar with how GitHub works, you can send your written documents directly to [baptiste@typebot.io](mailto:baptiste@typebot.io). You will be mentioned as a contributor 🥰
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
## Installation
```console
yarn install
```
## Local Development
```console
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```console
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.

View File

@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@ -0,0 +1,4 @@
{
"label": "Embed",
"position": 5
}

View File

@ -0,0 +1,11 @@
---
sidebar_position: 3
---
# Iframe
You can easily get your typebot iframe code by clicking on the "Iframe" button in the "Share" tab of your typebot.
<img src="/img/embeddings/iframe/iframe-preview.png" width="600" alt="Iframe preview"/>
Here, you can set up its width and height. A good default is a `width` of `100%` and a `height` of `600px`.

View File

@ -0,0 +1,65 @@
---
sidebar_position: 4
---
# Javascript library
Typebot Javascript library is open-source ([check out the repository](https://github.com/typebot-io/typebot-js)). Feel free to contribute if you're a developer and wish to improve its features.
Whenever a typebot is embedded on your website, you have access to commands to automatically trigger actions on your embedding depending on its type.
## Popup
### Open or Close a popup
You can use these commands:
```js
Typebot.getPopupActions().open();
```
```js
Typebot.getPopupActions().close();
```
You can bind these commands on a button element, for example:
```html
<button onclick="Typebot.getPopupActions().open()">Open the popup</button>
```
## Bubble
### Open or close the proactive message
You can use this command:
```js
Typebot.getBubbleActions().openProactiveMessage();
```
You can bind this command on a button element, for example:
```html
<button onclick="Typebot.getBubbleActions().openProactiveMessage()">
Open proactive message
</button>
```
### Open or close the typebot
You can use these commands:
```js
Typebot.getBubbleActions().open();
```
```js
Typebot.getBubbleActions().close();
```
You can bind these commands on a button element, for example:
```html
<button onclick="Typebot.getBubbleActions().open()">Open the chat</button>
```

View File

@ -0,0 +1,28 @@
---
sidebar_position: 1
---
# Overview
You can choose to embed your typebot in 3 different ways.
## Standard
Embeds the typebot in a box with the size of your choice. This type is used at the top of [Typebot's homepage](https://www.typebot.io/)
<img src="/img/embeddings/standard.png" alt="Standard"/>
You can also set the width to `100%` and the height to `100vh` to make it take the entire page dimensions
## Popup
Embeds the typebot in a Popup that overlays your website. It can be triggered after a delay or with a click of a button for example
<img src="/img/embeddings/popup.png" alt="Popup"/>
## Bubble
Embeds the typebot as a "chat bubble" at the bottom right corner of your site. Can be triggered automatically or with a click. It can also come with a "proactive message". An example can be found in [Typebot's pricing page](https://www.typebot.io/pricing)
<img src="/img/embeddings/bubble1.png" alt="Bubble 1" width="600px"/>
<img src="/img/embeddings/bubble2.png" alt="Bubble 2" width="600px"/>

View File

@ -0,0 +1,103 @@
---
sidebar_position: 2
---
# WordPress
Typebot has a native [WordPress plug-in](https://wordpress.org/plugins/typebot/) that helps you embed typebots in your WordPress site.
Of course, before using it, you need to create and publish your first typebot.
<img src="/img/embeddings/wordpress-preview.png" width="600" alt="WP plugin preview"/>
You can either choose to configurate an "easy setup" or "advanced setup".
## Easy setup
### Container
When choosing "container" and click on "Save" you can then use a typebot shortcode in your page builder. [Here is a complete tutorial on how to insert a shortcode](https://www.wpbeginner.com/wp-tutorials/how-to-add-a-shortcode-in-wordpress/).
Here is how it looks like:
```text
[typebot width="100%" height="500px" background-color="#F7F8FF"]
```
`width`, `height`, `background-color` and `url` are optionnal.
You should use `url` parameter only if you need to embed different typebots as containers on your website.
If your typebot appears to have a small height like this:
<img src="/img/embeddings/wp-small-container.png" width="600" alt="WP plugin preview"/>
you need to set a fixed `height` in pixel (`500px` or `600px` is usually a great number).
### Popup & Bubble
Fields are self explanatory.
#### Pages to include separated by a comma
With this field, you can tell the plugin to include the typebot only on specific pages.
Example:
- `/my-page,/other-page/*`: the typebot will appear on these pages: `/my-page`, `/other-page`, `/other-page/sub/path`, `/other-page/other/path`
## Advanced setup
This config allows you to directly paste the code from "HTML & Javascript" instructions in the Share page. So that you can set your own logic if needed.
Here is an example for a bubble config:
```html
<script
type="text/javascript"
src="https://static.typebot.io/typebot-1.0.0.js"
></script>
<script>
const typebot = Typebot.Chat({
publishId: "exemple-lead-gen",
buttonColor: "#0042DA",
buttonIconUrl: "",
loadingColors: {
chatBackground: "#00002e",
bubbleBackground: "#F7F8FF",
typingDots: "#303235",
},
});
</script>
```
## Personalize user experience (Hidden variables)
You can leverage the hidden variables and inject your user information directly into your typebot so that the experience is entirely customized to your user. Here are the available variables from WordPress:
<img src="/img/embeddings/wp-variables.png" alt="WP predefined variables"/>
Then you can use these variables anywhere on your typebot. For more informations, check out the [Hidden variables doc](https://docs.typebot.io/editor/variables/hidden-variables)
## Your typebot isn't showing?
### You have a cache plugin
Plugins like WP Rocket prevent Typebot to work.
For WP Rocket:
1. Go to Settings > WP Rocket > Excluded Inline Javascript:
<img src="/img/embeddings/wp-rocket.png" width="600" alt="WP plugin preview"/>
2. Type "typebot"
3. Save
### You have plugin that adds `defer` attribute to external scripts
You need to add an exception for Typebot in the corresponding plugin config.
### Still not working
Contact me on the application using the typebot at the bottom right corner

View File

@ -0,0 +1,4 @@
{
"label": "Get Started",
"position": 1
}

View File

@ -0,0 +1,9 @@
---
sidebar_position: 2
---
# Overview
The best way for me to show you Typebot's simplicity is through a product tour video:
<iframe width="900" height="500" src="https://www.youtube.com/embed/u8FZHvlYviw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

View File

@ -0,0 +1,14 @@
---
sidebar_position: 1
slug: /
---
# Welcome
[Typebot](https://www.typebot.io) is a conversational form builder that helps you collect more responses compared to other form builders (Typeform, Google Forms, Tally...).
This is the Typebot documentation. It's a great place to find most answers. Please use the search box in the top right or the navigation menu (soon available) on the left-hand side to find the answers you're looking for.
This documentation is a work in progress.
If you can't find what you're looking for, don't hesitate to contact me directly on the web app or at baptiste@typebot.io.

View File

@ -0,0 +1,19 @@
{
"index_name": "typebot",
"start_urls": ["https://docs.typebot.io/"],
"stop_urls": ["open-typebot=true$"],
"selectors": {
"lvl0": {
"selector": ".menu__link--sublist.menu__link--active",
"global": true,
"default_value": "Documentation"
},
"lvl1": "[class^='docItemContainer_'] h1",
"lvl2": "[class^='docItemContainer_'] h2",
"lvl3": "[class^='docItemContainer_'] h3",
"lvl4": "[class^='docItemContainer_'] h4",
"lvl5": "[class^='docItemContainer_'] h5",
"text": "[class^='docItemContainer_'] p, [class^='docItemContainer_'] li"
},
"selectors_exclude": [".hash-link"]
}

View File

@ -0,0 +1,101 @@
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
title: "Typebot docs",
tagline: "Get to Typebot next level with its documentation",
url: "https://docs.typebot.io",
baseUrl: "/",
onBrokenLinks: "warn",
onBrokenMarkdownLinks: "warn",
favicon: "img/favicon.png",
organizationName: "Typebot_io", // Usually your GitHub org/user name.
themeConfig: {
navbar: {
title: "Typebot",
logo: {
alt: "Typebot Logo",
src: "img/logo.svg",
},
items: [
{
href: "https://github.com/typebot-io/docs",
label: "Contribute",
position: "right",
},
],
},
algolia: {
apiKey: "d2e121d4ad4e5346ac2c3329424981a1",
indexName: "typebot",
appId: "DXYNLHZTGJ",
},
footer: {
links: [
{
title: "Product",
items: [
{
label: "Homepage",
to: "https://www.typebot.io",
},
{
label: "Roadmap",
to: "https://feedback.typebot.io",
},
{
label: "Blog",
to: "https://www.typebot.io/blog",
},
],
},
{
title: "Community",
items: [
{
label: "Facebook Group",
href: "https://www.facebook.com/groups/typebot",
},
{
label: "Twitter",
href: "https://twitter.com/Typebot_io",
},
],
},
{
title: "Company",
items: [
{
label: "About",
to: "https://www.typebot.io/about",
},
{
label: "Terms of Service",
href: "https://www.typebot.io/terms-of-service",
},
{
label: "Privacy Policy",
href: "https://www.typebot.io/privacy-policies",
},
],
},
],
},
colorMode: {
disableSwitch: true,
},
},
presets: [
[
"@docusaurus/preset-classic",
{
docs: {
sidebarPath: require.resolve("./sidebars.js"),
routeBasePath: "/",
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
},
],
],
scripts: ["https://unpkg.com/typebot-js", "/scripts/typebot.js"],
};

42
apps/docs/package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "documentation",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start --no-open --port 3004",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"update-search": "docker run -it --rm --env-file=.env -e \"CONFIG=$(cat /Users/baptistearnaud/Dev/typebot-io/docs/docsearch-scrapper-config.json | jq -r tostring)\" algolia/docsearch-scraper"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.15",
"@docusaurus/preset-classic": "2.0.0-beta.15",
"@docusaurus/theme-search-algolia": "^2.0.0-beta.15",
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
"file-loader": "^6.2.0",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"url-loader": "^4.1.1"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

26
apps/docs/sidebars.js Normal file
View File

@ -0,0 +1,26 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
module.exports = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
};

View File

@ -0,0 +1,45 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
@import url("https://fonts.googleapis.com/css2?family=Epilogue:wght@300;400;500;600;700;800&family=Open+Sans:wght@300;400;600;700&display=swap");
body {
font-family: "Open Sans", sans-serif;
}
h1,
h2,
h3,
h4 {
font-family: "Epilogue", sans-serif;
}
img {
border-radius: 0.5rem;
}
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #0042da;
--ifm-color-primary-dark: #003bc4;
--ifm-color-primary-darker: #0038b9;
--ifm-color-primary-darkest: #002e99;
--ifm-color-primary-light: #0049f0;
--ifm-color-primary-lighter: #004cfb;
--ifm-color-primary-lightest: #1c61ff;
--ifm-code-font-size: 95%;
}
.docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.1);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}
html[data-theme="dark"] .docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.3);
}

0
apps/docs/static/.nojekyll vendored Normal file
View File

BIN
apps/docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
apps/docs/static/img/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

42
apps/docs/static/img/logo.svg vendored Normal file
View File

@ -0,0 +1,42 @@
<svg
viewBox="0 0 500 500"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
width="500"
height="500"
rx="75"
fill="#0042DA"
/>
<rect
x="438.709"
y="170.968"
width="64.5161"
height="290.323"
rx="32.2581"
transform="rotate(90 438.709 170.968)"
fill="#FF8E20"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M93.5481 235.484C111.364 235.484 125.806 221.041 125.806 203.226C125.806 185.41 111.364 170.968 93.5481 170.968C75.7325 170.968 61.29 185.41 61.29 203.226C61.29 221.041 75.7325 235.484 93.5481 235.484Z"
fill="#FF8E20"
/>
<rect
x="61.29"
y="332.259"
width="64.5161"
height="290.323"
rx="32.2581"
transform="rotate(-90 61.29 332.259)"
fill="white"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M406.451 267.742C388.635 267.742 374.193 282.184 374.193 300C374.193 317.815 388.635 332.258 406.451 332.258C424.267 332.258 438.709 317.815 438.709 300C438.709 282.184 424.267 267.742 406.451 267.742Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -21,7 +21,7 @@ export const EndCta = () => {
</Text>
<Button
as="a"
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
mt="8"
size="lg"
colorScheme="blue"

View File

@ -46,7 +46,7 @@ export const Hero = () => {
</Text>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
colorScheme="orange"
bgColor="#FF8E20"
_hover={{ bgColor: 'orange.500' }}

View File

@ -10,7 +10,7 @@ export const ArticleCallToAction = () => (
size="lg"
colorScheme="orange"
as="a"
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
>
Create a typebot
</Button>

View File

@ -64,7 +64,7 @@ const MobileNavContext = ({
</Button>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
colorScheme="blue"
w="full"
size="lg"
@ -125,7 +125,7 @@ const DesktopNavContent = ({
</Box>
<Button
as={NextChakraLink}
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
colorScheme="blue"
fontWeight="bold"
>

View File

@ -8,10 +8,14 @@ import { BackgroundPolygons } from 'components/Homepage/Hero/BackgroundPolygons'
import { PricingCard } from 'components/PricingPage/PricingCard'
import { ActionButton } from 'components/PricingPage/PricingCard/ActionButton'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
const Pricing = () => {
const router = useRouter()
const [price, setPrice] = useState<'$30' | '25€' | ''>('')
useEffect(() => {
setPrice(navigator.languages.find((l) => l.includes('fr')) ? '25€' : '$30')
}, [])
return (
<Stack overflowX="hidden">
@ -28,12 +32,8 @@ const Pricing = () => {
description={
"99% of Typebot's features are available to all users for free."
}
currentUrl={`https://www.typebot.io/${
router.locale === 'fr' ? 'fr/' : ''
}pricing`}
imagePreviewUrl={`https://www.typebot.io/images/previews/pricing${
router.locale === 'fr' ? '-fr' : ''
}.png`}
currentUrl={`https://www.typebot.io/pricing`}
imagePreviewUrl={`https://www.typebot.io/images/previews/pricing.png`}
/>
<Head>
<link
@ -77,7 +77,7 @@ const Pricing = () => {
}}
button={
<NextChakraLink
href="https://app.typebot.io/signup"
href="https://app.typebot.io/register"
_hover={{ textDecor: 'none' }}
>
<ActionButton variant="outline">Try now</ActionButton>
@ -86,7 +86,7 @@ const Pricing = () => {
/>
<PricingCard
data={{
price: '$30',
price,
name: 'Pro',
features: [
'Everything in Basic',
@ -102,7 +102,7 @@ const Pricing = () => {
beforeButtonLabel={"The only form builder you'll need"}
button={
<NextChakraLink
href="https://app.typebot.io/signup?chosen_plan=scale"
href="https://app.typebot.io/register?subscribe=true"
_hover={{ textDecor: 'none' }}
>
<ActionButton>Subscribe now</ActionButton>

6263
yarn.lock

File diff suppressed because it is too large Load Diff