feat: ⚡️ Add docs and connect Stripe
17
.github/workflows/docsearch-scrap.yml
vendored
Normal 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
@ -12,3 +12,6 @@ dist
|
||||
test-results
|
||||
**/api/scripts
|
||||
.sentryclirc
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
build
|
||||
|
@ -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
|
||||
|
@ -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..." />
|
||||
|
@ -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>) =>
|
||||
|
@ -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>
|
||||
|
16
apps/builder/components/shared/buttons/UpgradeButton.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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',
|
||||
|
11
apps/builder/libs/mailgun.ts
Normal 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',
|
||||
})
|
||||
}
|
@ -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": "*",
|
||||
|
@ -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)
|
||||
// },
|
||||
}),
|
||||
]
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
@ -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.
|
3
apps/docs/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
4
apps/docs/docs/embed/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Embed",
|
||||
"position": 5
|
||||
}
|
11
apps/docs/docs/embed/iframe.md
Normal 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`.
|
65
apps/docs/docs/embed/javascript-commands.md
Normal 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>
|
||||
```
|
28
apps/docs/docs/embed/overview.md
Normal 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"/>
|
103
apps/docs/docs/embed/wordpress.md
Normal 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
|
4
apps/docs/docs/get-started/_category_.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Get Started",
|
||||
"position": 1
|
||||
}
|
9
apps/docs/docs/get-started/overview.md
Normal 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>
|
14
apps/docs/docs/get-started/welcome.md
Normal 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.
|
19
apps/docs/docsearch-scrapper-config.json
Normal 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"]
|
||||
}
|
101
apps/docs/docusaurus.config.js
Normal 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
@ -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
@ -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'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
45
apps/docs/src/css/custom.css
Normal 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
BIN
apps/docs/static/img/docusaurus.png
vendored
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
apps/docs/static/img/embeddings/bubble1.png
vendored
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
apps/docs/static/img/embeddings/bubble2.png
vendored
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
apps/docs/static/img/embeddings/iframe/iframe-preview.png
vendored
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
apps/docs/static/img/embeddings/popup.png
vendored
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
apps/docs/static/img/embeddings/standard.png
vendored
Normal file
After Width: | Height: | Size: 817 KiB |
BIN
apps/docs/static/img/embeddings/wordpress-preview.png
vendored
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
apps/docs/static/img/embeddings/wp-rocket.png
vendored
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
apps/docs/static/img/embeddings/wp-small-container.png
vendored
Normal file
After Width: | Height: | Size: 269 KiB |
BIN
apps/docs/static/img/embeddings/wp-variables.png
vendored
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
apps/docs/static/img/favicon.png
vendored
Normal file
After Width: | Height: | Size: 558 B |
42
apps/docs/static/img/logo.svg
vendored
Normal 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 |
@ -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"
|
||||
|
@ -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' }}
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
>
|
||||
|
@ -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>
|
||||
|