🧑💻 Improve env variables type safety and management (#718)
Closes #679
This commit is contained in:
@ -3,7 +3,7 @@ Dockerfile
|
|||||||
**/node_modules
|
**/node_modules
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
README.md
|
README.md
|
||||||
.next
|
**/.next
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.turbo
|
.turbo
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
|
|
||||||
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
|
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
|
||||||
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S
|
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
|
||||||
|
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
|
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
|
||||||
|
|
||||||
@ -14,5 +16,4 @@ S3_PORT=9000
|
|||||||
S3_ENDPOINT=localhost
|
S3_ENDPOINT=localhost
|
||||||
S3_SSL=false
|
S3_SSL=false
|
||||||
|
|
||||||
# For more configuration options check out:
|
# For more configuration options check out: https://docs.typebot.io/self-hosting/configuration
|
||||||
# https://docs.typebot.io/self-hosting/configuration
|
|
10
.env.example
Normal file
10
.env.example
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
|
||||||
|
ENCRYPTION_SECRET=do+UspMmB/rewbX2K/rskFmtgGSSZ8Ta
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
|
||||||
|
|
||||||
|
NEXTAUTH_URL=
|
||||||
|
NEXT_PUBLIC_VIEWER_URL=
|
||||||
|
|
||||||
|
ADMIN_EMAIL=
|
||||||
|
# For more configuration options check out: https://docs.typebot.io/self-hosting/configuration
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.next
|
.next
|
||||||
.env
|
|
||||||
.env.local
|
.env.local
|
||||||
workspace.code-workspace
|
workspace.code-workspace
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@ -35,3 +34,5 @@ typebotsToFix.json
|
|||||||
**/scripts/logs
|
**/scripts/logs
|
||||||
|
|
||||||
snapshots
|
snapshots
|
||||||
|
|
||||||
|
.env
|
@ -36,11 +36,7 @@ These apps are built with awesome web technologies including [Typescript](https:
|
|||||||
|
|
||||||
2. Set up environment variables
|
2. Set up environment variables
|
||||||
|
|
||||||
Copy [`apps/builder/.env.local.example`](apps/builder/.env.local.example) to `apps/builder/.env.local`
|
Copy [`.env.dev.example`](./.env.dev.example) to `.env`
|
||||||
|
|
||||||
Copy [`apps/viewer/.env.local.example`](apps/viewer/.env.local.example) to `apps/viewer/.env.local`
|
|
||||||
|
|
||||||
Copy [`packages/prisma/.env.example` ](packages/prisma/.env.example)to `packages/prisma/.env`
|
|
||||||
|
|
||||||
Check out the [Configuration guide](https://docs.typebot.io/self-hosting/configuration) if you want to enable more options
|
Check out the [Configuration guide](https://docs.typebot.io/self-hosting/configuration) if you want to enable more options
|
||||||
|
|
||||||
|
14
Dockerfile
14
Dockerfile
@ -21,7 +21,8 @@ RUN pnpm install
|
|||||||
COPY --from=pruner /app/out/full/ .
|
COPY --from=pruner /app/out/full/ .
|
||||||
COPY turbo.json turbo.json
|
COPY turbo.json turbo.json
|
||||||
|
|
||||||
RUN pnpm turbo run build:docker --filter=${SCOPE}...
|
ENV ENCRYPTION_SECRET=encryption_secret_placeholder123 DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot NEXTAUTH_URL=http://localhost:3000 NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
|
||||||
|
RUN pnpm turbo run build --filter=${SCOPE}...
|
||||||
|
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -32,16 +33,15 @@ RUN apt-get -qy update \
|
|||||||
&& apt-get autoremove -yq \
|
&& apt-get autoremove -yq \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
COPY ./packages/prisma ./packages/prisma
|
COPY ./packages/prisma/postgresql ./packages/prisma/postgresql
|
||||||
COPY ./apps/${SCOPE}/.env.docker ./apps/${SCOPE}/.env.production
|
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
|
||||||
COPY --from=builder /app/apps/${SCOPE}/public ./apps/${SCOPE}/public
|
COPY --from=builder /app/apps/${SCOPE}/public ./apps/${SCOPE}/public
|
||||||
COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/standalone ./
|
COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/standalone ./
|
||||||
COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/static ./apps/${SCOPE}/.next/static
|
COPY --from=builder --chown=node:node /app/apps/${SCOPE}/.next/static ./apps/${SCOPE}/.next/static
|
||||||
|
RUN pnpm install next-runtime-env prisma
|
||||||
|
RUN pnpm prisma generate --schema=packages/prisma/postgresql/schema.prisma;
|
||||||
|
|
||||||
COPY scripts/inject-runtime-env.sh scripts/${SCOPE}-entrypoint.sh ./
|
COPY scripts/${SCOPE}-entrypoint.sh ./
|
||||||
RUN chmod +x ./${SCOPE}-entrypoint.sh \
|
RUN chmod +x ./${SCOPE}-entrypoint.sh
|
||||||
&& chmod +x ./inject-runtime-env.sh
|
|
||||||
ENTRYPOINT ./${SCOPE}-entrypoint.sh
|
ENTRYPOINT ./${SCOPE}-entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
@ -17,7 +17,7 @@ Typebot is an open-source chatbot builder. It allows you to create advanced chat
|
|||||||
<a href="https://github.com/baptistearno/typebot.io/stargazers"><img src="https://img.shields.io/github/stars/baptistearno/typebot.io" alt="Github Stars"></a>
|
<a href="https://github.com/baptistearno/typebot.io/stargazers"><img src="https://img.shields.io/github/stars/baptistearno/typebot.io" alt="Github Stars"></a>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/baptistearno/typebot.io/pulse"><img src="https://img.shields.io/github/commit-activity/m/baptistearno/typebot.io" alt="Commits per month"></a>
|
<a href="https://github.com/baptistearno/typebot.io/pulse"><img src="https://img.shields.io/github/commit-activity/m/baptistearno/typebot.io" alt="Commits per month"></a>
|
||||||
<a href="https://docs.typebot.io/self-hosting/docker">
|
<a href="https://docs.typebot.io/self-hosting/guides/docker">
|
||||||
<img src="https://img.shields.io/docker/pulls/baptistearno/typebot-builder">
|
<img src="https://img.shields.io/docker/pulls/baptistearno/typebot-builder">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/baptistearno/typebot.io/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License">
|
<a href="https://github.com/baptistearno/typebot.io/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPLv3-purple" alt="License">
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
# Don't edit this file
|
|
||||||
NEXT_PUBLIC_VIEWER_URL=
|
|
||||||
NEXT_PUBLIC_SMTP_FROM=
|
|
||||||
NEXT_PUBLIC_GOOGLE_API_KEY=
|
|
||||||
NEXT_PUBLIC_GIPHY_API_KEY=
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
|
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
|
||||||
NEXT_PUBLIC_VIEWER_INTERNAL_URL=
|
|
||||||
NEXT_PUBLIC_E2E_TEST=
|
|
||||||
NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME=
|
|
||||||
NEXT_PUBLIC_UNSPLASH_APP_NAME=
|
|
||||||
NEXT_PUBLIC_UNSPLASH_ACCESS_KEY=
|
|
||||||
NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID=
|
|
@ -1 +0,0 @@
|
|||||||
next.config.js
|
|
@ -1,6 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
import { withSentryConfig } from '@sentry/nextjs'
|
||||||
const { withSentryConfig } = require('@sentry/nextjs')
|
import { join, dirname } from 'path'
|
||||||
const path = require('path')
|
import '@typebot.io/env/dist/env.mjs'
|
||||||
|
import { configureRuntimeEnv } from 'next-runtime-env/build/configure.js'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
configureRuntimeEnv()
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
@ -10,13 +18,14 @@ const nextConfig = {
|
|||||||
'@typebot.io/lib',
|
'@typebot.io/lib',
|
||||||
'@typebot.io/schemas',
|
'@typebot.io/schemas',
|
||||||
'@typebot.io/emails',
|
'@typebot.io/emails',
|
||||||
|
'@typebot.io/env',
|
||||||
],
|
],
|
||||||
i18n: {
|
i18n: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
locales: ['en', 'fr', 'pt', 'de'],
|
locales: ['en', 'fr', 'pt', 'de'],
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: join(__dirname, '../../'),
|
||||||
},
|
},
|
||||||
headers: async () => {
|
headers: async () => {
|
||||||
return [
|
return [
|
||||||
@ -38,7 +47,7 @@ const sentryWebpackPluginOptions = {
|
|||||||
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder',
|
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder',
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
|
export default process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||||
? withSentryConfig(
|
? withSentryConfig(
|
||||||
{
|
{
|
||||||
...nextConfig,
|
...nextConfig,
|
@ -3,13 +3,11 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next dev -p 3000",
|
"dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3000",
|
||||||
"build": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next build",
|
"build": "dotenv -e ./.env -e ../../.env -- next build",
|
||||||
"build:docker": "next build",
|
"start": "dotenv -e ./.env -e ../../.env -- next start",
|
||||||
"build:env": "cd ../.. && cross-env ENVSH_ENV=./apps/builder/.env.docker ENVSH_OUTPUT=./apps/builder/public/__env.js bash scripts/inject-runtime-env.sh",
|
"lint": "dotenv -e ./.env -e ../../.env -- next lint",
|
||||||
"start": "next start",
|
"test": "dotenv -e ./.env -e ../../.env -- pnpm playwright test",
|
||||||
"lint": "next lint",
|
|
||||||
"test": "pnpm playwright test",
|
|
||||||
"test:show-report": "pnpm playwright show-report src/test/reporters"
|
"test:show-report": "pnpm playwright show-report src/test/reporters"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -30,6 +28,7 @@
|
|||||||
"@paralleldrive/cuid2": "2.2.1",
|
"@paralleldrive/cuid2": "2.2.1",
|
||||||
"@sentry/nextjs": "7.58.1",
|
"@sentry/nextjs": "7.58.1",
|
||||||
"@stripe/stripe-js": "1.54.1",
|
"@stripe/stripe-js": "1.54.1",
|
||||||
|
"@t3-oss/env-nextjs": "^0.6.0",
|
||||||
"@tanstack/react-query": "^4.29.19",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@tanstack/react-table": "8.9.3",
|
"@tanstack/react-table": "8.9.3",
|
||||||
"@trpc/client": "10.34.0",
|
"@trpc/client": "10.34.0",
|
||||||
@ -37,6 +36,7 @@
|
|||||||
"@trpc/react-query": "10.34.0",
|
"@trpc/react-query": "10.34.0",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.34.0",
|
||||||
"@typebot.io/emails": "workspace:*",
|
"@typebot.io/emails": "workspace:*",
|
||||||
|
"@typebot.io/env": "workspace:*",
|
||||||
"@typebot.io/nextjs": "workspace:*",
|
"@typebot.io/nextjs": "workspace:*",
|
||||||
"@udecode/plate-basic-marks": "21.1.5",
|
"@udecode/plate-basic-marks": "21.1.5",
|
||||||
"@udecode/plate-common": "^21.1.5",
|
"@udecode/plate-common": "^21.1.5",
|
||||||
@ -81,6 +81,7 @@
|
|||||||
"qs": "6.11.2",
|
"qs": "6.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
|
"sharp": "^0.32.4",
|
||||||
"slate": "0.94.1",
|
"slate": "0.94.1",
|
||||||
"slate-history": "0.93.0",
|
"slate-history": "0.93.0",
|
||||||
"slate-hyperscript": "0.77.0",
|
"slate-hyperscript": "0.77.0",
|
||||||
@ -112,7 +113,8 @@
|
|||||||
"@types/qs": "6.9.7",
|
"@types/qs": "6.9.7",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
"dotenv": "16.3.1",
|
"dotenv-cli": "^7.2.1",
|
||||||
|
"next-runtime-env": "^1.6.2",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.44.0",
|
||||||
"eslint-config-custom": "workspace:*",
|
"eslint-config-custom": "workspace:*",
|
||||||
"superjson": "^1.12.4",
|
"superjson": "^1.12.4",
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
'ResizeObserver loop limit exceeded',
|
'ResizeObserver loop limit exceeded',
|
||||||
'ResizeObserver loop completed with undelivered notifications.',
|
'ResizeObserver loop completed with undelivered notifications.',
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder',
|
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-builder',
|
||||||
})
|
})
|
||||||
|
@ -3,14 +3,14 @@ import { GiphyFetch } from '@giphy/js-fetch-api'
|
|||||||
import { Grid } from '@giphy/react-components'
|
import { Grid } from '@giphy/react-components'
|
||||||
import { GiphyLogo } from '../logos/GiphyLogo'
|
import { GiphyLogo } from '../logos/GiphyLogo'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { env, isEmpty } from '@typebot.io/lib'
|
|
||||||
import { TextInput } from '../inputs'
|
import { TextInput } from '../inputs'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
type GiphySearchFormProps = {
|
type GiphySearchFormProps = {
|
||||||
onSubmit: (url: string) => void
|
onSubmit: (url: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const giphyFetch = new GiphyFetch(env('GIPHY_API_KEY') as string)
|
const giphyFetch = new GiphyFetch(env.NEXT_PUBLIC_GIPHY_API_KEY ?? '')
|
||||||
|
|
||||||
export const GiphyPicker = ({ onSubmit }: GiphySearchFormProps) => {
|
export const GiphyPicker = ({ onSubmit }: GiphySearchFormProps) => {
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
@ -21,7 +21,7 @@ export const GiphyPicker = ({ onSubmit }: GiphySearchFormProps) => {
|
|||||||
const fetchGifsTrending = (offset: number) =>
|
const fetchGifsTrending = (offset: number) =>
|
||||||
giphyFetch.trending({ offset, limit: 10 })
|
giphyFetch.trending({ offset, limit: 10 })
|
||||||
|
|
||||||
return isEmpty(env('GIPHY_API_KEY')) ? (
|
return !env.NEXT_PUBLIC_GIPHY_API_KEY ? (
|
||||||
<Text>NEXT_PUBLIC_GIPHY_API_KEY is missing in environment</Text>
|
<Text>NEXT_PUBLIC_GIPHY_API_KEY is missing in environment</Text>
|
||||||
) : (
|
) : (
|
||||||
<Stack spacing={4} pt="2">
|
<Stack spacing={4} pt="2">
|
||||||
|
@ -14,16 +14,17 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { env, isDefined, isEmpty } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { createApi } from 'unsplash-js'
|
import { createApi } from 'unsplash-js'
|
||||||
import { Basic as UnsplashImage } from 'unsplash-js/dist/methods/photos/types'
|
import { Basic as UnsplashImage } from 'unsplash-js/dist/methods/photos/types'
|
||||||
import { TextInput } from '../inputs'
|
import { TextInput } from '../inputs'
|
||||||
import { UnsplashLogo } from '../logos/UnsplashLogo'
|
import { UnsplashLogo } from '../logos/UnsplashLogo'
|
||||||
import { TextLink } from '../TextLink'
|
import { TextLink } from '../TextLink'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const api = createApi({
|
const api = createApi({
|
||||||
accessKey: env('UNSPLASH_ACCESS_KEY') ?? '',
|
accessKey: env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY ?? '',
|
||||||
})
|
})
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -124,7 +125,7 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
|||||||
searchRandomImages()
|
searchRandomImages()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (isEmpty(env('UNSPLASH_ACCESS_KEY')))
|
if (!env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY)
|
||||||
return (
|
return (
|
||||||
<Text>NEXT_PUBLIC_UNSPLASH_ACCESS_KEY is missing in environment</Text>
|
<Text>NEXT_PUBLIC_UNSPLASH_ACCESS_KEY is missing in environment</Text>
|
||||||
)
|
)
|
||||||
@ -143,9 +144,7 @@ export const UnsplashPicker = ({ imageSize, onImageSelect }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<Link
|
<Link
|
||||||
isExternal
|
isExternal
|
||||||
href={`https://unsplash.com/?utm_source=${env(
|
href={`https://unsplash.com/?utm_source=${env.NEXT_PUBLIC_UNSPLASH_APP_NAME}&utm_medium=referral`}
|
||||||
'UNSPLASH_APP_NAME'
|
|
||||||
)}&utm_medium=referral`}
|
|
||||||
>
|
>
|
||||||
<UnsplashLogo width="80px" fill={unsplashLogoFillColor} />
|
<UnsplashLogo width="80px" fill={unsplashLogoFillColor} />
|
||||||
</Link>
|
</Link>
|
||||||
@ -224,9 +223,7 @@ const UnsplashImage = ({ image, onClick }: UnsplashImageProps) => {
|
|||||||
<TextLink
|
<TextLink
|
||||||
fontSize="xs"
|
fontSize="xs"
|
||||||
isExternal
|
isExternal
|
||||||
href={`https://unsplash.com/@${user.username}?utm_source=${env(
|
href={`https://unsplash.com/@${user.username}?utm_source=${env.NEXT_PUBLIC_UNSPLASH_APP_NAME}&utm_medium=referral`}
|
||||||
'UNSPLASH_APP_NAME'
|
|
||||||
)}&utm_medium=referral`}
|
|
||||||
noOfLines={1}
|
noOfLines={1}
|
||||||
color="white"
|
color="white"
|
||||||
>
|
>
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useState, useRef, useEffect, ReactNode } from 'react'
|
import { useState, useRef, useEffect, ReactNode } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env, isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
import { useOutsideClick } from '@/hooks/useOutsideClick'
|
||||||
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
||||||
import { VariablesButton } from '@/features/variables/components/VariablesButton'
|
import { VariablesButton } from '@/features/variables/components/VariablesButton'
|
||||||
@ -21,6 +21,7 @@ import { Variable } from '@typebot.io/schemas'
|
|||||||
import { injectVariableInText } from '@/features/variables/helpers/injectVariableInTextInput'
|
import { injectVariableInText } from '@/features/variables/helpers/injectVariableInTextInput'
|
||||||
import { focusInput } from '@/helpers/focusInput'
|
import { focusInput } from '@/helpers/focusInput'
|
||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
items: string[]
|
items: string[]
|
||||||
@ -57,7 +58,7 @@ export const AutocompleteInput = ({
|
|||||||
|
|
||||||
const onChange = useDebouncedCallback(
|
const onChange = useDebouncedCallback(
|
||||||
_onChange,
|
_onChange,
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -9,7 +9,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { VariablesButton } from '@/features/variables/components/VariablesButton'
|
import { VariablesButton } from '@/features/variables/components/VariablesButton'
|
||||||
import { Variable } from '@typebot.io/schemas'
|
import { Variable } from '@typebot.io/schemas'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'
|
||||||
import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night'
|
import { tokyoNight } from '@uiw/codemirror-theme-tokyo-night'
|
||||||
import { githubLight } from '@uiw/codemirror-theme-github'
|
import { githubLight } from '@uiw/codemirror-theme-github'
|
||||||
@ -53,7 +53,7 @@ export const CodeEditor = ({
|
|||||||
_setValue(value)
|
_setValue(value)
|
||||||
onChange && onChange(value)
|
onChange && onChange(value)
|
||||||
},
|
},
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleVariableSelected = (variable?: Pick<Variable, 'id' | 'name'>) => {
|
const handleVariableSelected = (variable?: Pick<Variable, 'id' | 'name'>) => {
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
import { Variable, VariableString } from '@typebot.io/schemas'
|
import { Variable, VariableString } from '@typebot.io/schemas'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
|
||||||
type Value<HasVariable> = HasVariable extends true | undefined
|
type Value<HasVariable> = HasVariable extends true | undefined
|
||||||
@ -47,7 +47,7 @@ export const NumberInput = <HasVariable extends boolean>({
|
|||||||
|
|
||||||
const onValueChangeDebounced = useDebouncedCallback(
|
const onValueChangeDebounced = useDebouncedCallback(
|
||||||
onValueChange,
|
onValueChange,
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
@ -19,7 +19,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
|
||||||
export type TextInputProps = {
|
export type TextInputProps = {
|
||||||
@ -69,7 +69,7 @@ export const TextInput = forwardRef(function TextInput(
|
|||||||
const onChange = useDebouncedCallback(
|
const onChange = useDebouncedCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
_onChange ?? (() => {}),
|
_onChange ?? (() => {}),
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { Variable } from '@typebot.io/schemas'
|
import { Variable } from '@typebot.io/schemas'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
import { MoreInfoTooltip } from '../MoreInfoTooltip'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -46,7 +46,7 @@ export const Textarea = ({
|
|||||||
)
|
)
|
||||||
const onChange = useDebouncedCallback(
|
const onChange = useDebouncedCallback(
|
||||||
_onChange,
|
_onChange,
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { createContext, ReactNode, useEffect, useState } from 'react'
|
import { createContext, ReactNode, useEffect, useState } from 'react'
|
||||||
import { env, isDefined, isNotDefined } from '@typebot.io/lib'
|
import { isDefined, isNotDefined } from '@typebot.io/lib'
|
||||||
import { User } from '@typebot.io/prisma'
|
import { User } from '@typebot.io/prisma'
|
||||||
import { setUser as setSentryUser } from '@sentry/nextjs'
|
import { setUser as setSentryUser } from '@sentry/nextjs'
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { updateUserQuery } from './queries/updateUserQuery'
|
import { updateUserQuery } from './queries/updateUserQuery'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const userContext = createContext<{
|
export const userContext = createContext<{
|
||||||
user?: User
|
user?: User
|
||||||
@ -66,7 +67,7 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
if (error) showToast({ title: error.name, description: error.message })
|
if (error) showToast({ title: error.name, description: error.message })
|
||||||
await refreshUser()
|
await refreshUser()
|
||||||
},
|
},
|
||||||
env('E2E_TEST') === 'true' ? 0 : debounceTimeout
|
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import test, { expect } from '@playwright/test'
|
import test, { expect } from '@playwright/test'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { userId } from '@typebot.io/lib/playwright/databaseSetup'
|
import { userId } from '@typebot.io/lib/playwright/databaseSetup'
|
||||||
|
|
||||||
test.describe.configure({ mode: 'parallel' })
|
test.describe.configure({ mode: 'parallel' })
|
||||||
@ -15,9 +16,9 @@ test('should display user info properly', async ({ page }) => {
|
|||||||
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
await expect(page.locator('img >> nth=1')).toHaveAttribute(
|
||||||
'src',
|
'src',
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`${process.env.S3_ENDPOINT}${
|
`${env.S3_ENDPOINT}${env.S3_PORT ? `:${env.S3_PORT}` : ''}/${
|
||||||
process.env.S3_PORT ? `:${process.env.S3_PORT}` : ''
|
env.S3_BUCKET
|
||||||
}/${process.env.S3_BUCKET}/public/users/${userId}/avatar`,
|
}/public/users/${userId}/avatar`,
|
||||||
'gm'
|
'gm'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { fetcher } from '@/helpers/fetcher'
|
import { fetcher } from '@/helpers/fetcher'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import { ApiTokenFromServer } from '../types'
|
import { ApiTokenFromServer } from '../types'
|
||||||
|
|
||||||
type ServerResponse = {
|
type ServerResponse = {
|
||||||
@ -18,7 +18,7 @@ export const useApiTokens = ({
|
|||||||
userId ? `/api/users/${userId}/api-tokens` : null,
|
userId ? `/api/users/${userId}/api-tokens` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
|
@ -14,6 +14,7 @@ import { convertInvitationsToCollaborations } from '@/features/auth/helpers/conv
|
|||||||
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
||||||
import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces'
|
import { joinWorkspaces } from '@/features/auth/helpers/joinWorkspaces'
|
||||||
import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan'
|
import { parseWorkspaceDefaultPlan } from '@/features/workspace/helpers/parseWorkspaceDefaultPlan'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export function customAdapter(p: PrismaClient): Adapter {
|
export function customAdapter(p: PrismaClient): Adapter {
|
||||||
return {
|
return {
|
||||||
@ -26,8 +27,8 @@ export function customAdapter(p: PrismaClient): Adapter {
|
|||||||
user.email
|
user.email
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
process.env.DISABLE_SIGNUP === 'true' &&
|
env.DISABLE_SIGNUP &&
|
||||||
process.env.ADMIN_EMAIL !== user.email &&
|
env.ADMIN_EMAIL !== user.email &&
|
||||||
invitations.length === 0 &&
|
invitations.length === 0 &&
|
||||||
workspaceInvitations.length === 0
|
workspaceInvitations.length === 0
|
||||||
)
|
)
|
||||||
|
@ -13,8 +13,8 @@ import { useRouter } from 'next/router'
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import confetti from 'canvas-confetti'
|
import confetti from 'canvas-confetti'
|
||||||
import { useUser } from '@/features/account/hooks/useUser'
|
import { useUser } from '@/features/account/hooks/useUser'
|
||||||
import { env, isEmpty } from '@typebot.io/lib'
|
|
||||||
import { useI18n } from '@/locales'
|
import { useI18n } from '@/locales'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const totalSteps = 5
|
const totalSteps = 5
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ export const OnboardingPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user?.createdAt) return
|
if (!user?.createdAt) return
|
||||||
if (isNewUser === false || isEmpty(env('ONBOARDING_TYPEBOT_ID')))
|
if (isNewUser === false || !env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID)
|
||||||
replace('/typebots')
|
replace('/typebots')
|
||||||
}, [isNewUser, replace, user?.createdAt])
|
}, [isNewUser, replace, user?.createdAt])
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ export const OnboardingPage = () => {
|
|||||||
<Dots currentStep={currentStep} pos="fixed" top="9" />
|
<Dots currentStep={currentStep} pos="fixed" top="9" />
|
||||||
<Flex w="full" maxW="800px" h="full" maxH="70vh" rounded="lg">
|
<Flex w="full" maxW="800px" h="full" maxH="70vh" rounded="lg">
|
||||||
<Standard
|
<Standard
|
||||||
typebot={env('ONBOARDING_TYPEBOT_ID')}
|
typebot={env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID}
|
||||||
style={{ borderRadius: '1rem' }}
|
style={{ borderRadius: '1rem' }}
|
||||||
prefilledVariables={{ Name: user?.name, Email: user?.email }}
|
prefilledVariables={{ Name: user?.name, Email: user?.email }}
|
||||||
onEnd={() => {
|
onEnd={() => {
|
||||||
|
@ -5,7 +5,7 @@ import { User } from '@typebot.io/prisma'
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { mockedUser } from '../mockedUser'
|
import { mockedUser } from '../mockedUser'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const getAuthenticatedUser = async (
|
export const getAuthenticatedUser = async (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -13,12 +13,11 @@ export const getAuthenticatedUser = async (
|
|||||||
): Promise<User | undefined> => {
|
): Promise<User | undefined> => {
|
||||||
const bearerToken = extractBearerToken(req)
|
const bearerToken = extractBearerToken(req)
|
||||||
if (bearerToken) return authenticateByToken(bearerToken)
|
if (bearerToken) return authenticateByToken(bearerToken)
|
||||||
const user =
|
const user = env.NEXT_PUBLIC_E2E_TEST
|
||||||
env('E2E_TEST') === 'true'
|
? mockedUser
|
||||||
? mockedUser
|
: ((await getServerSession(req, res, authOptions))?.user as
|
||||||
: ((await getServerSession(req, res, authOptions))?.user as
|
| User
|
||||||
| User
|
| undefined)
|
||||||
| undefined)
|
|
||||||
if (!user || !('id' in user)) return
|
if (!user || !('id' in user)) return
|
||||||
setUser({ id: user.id })
|
setUser({ id: user.id })
|
||||||
return user
|
return user
|
||||||
|
@ -6,6 +6,7 @@ import Stripe from 'stripe'
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { parseSubscriptionItems } from '../helpers/parseSubscriptionItems'
|
import { parseSubscriptionItems } from '../helpers/parseSubscriptionItems'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const createCheckoutSession = authenticatedProcedure
|
export const createCheckoutSession = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -57,7 +58,7 @@ export const createCheckoutSession = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
ctx: { user },
|
ctx: { user },
|
||||||
}) => {
|
}) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'Stripe environment variables are missing',
|
message: 'Stripe environment variables are missing',
|
||||||
@ -82,14 +83,13 @@ export const createCheckoutSession = authenticatedProcedure
|
|||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Workspace not found',
|
message: 'Workspace not found',
|
||||||
})
|
})
|
||||||
|
|
||||||
if (workspace.stripeId)
|
if (workspace.stripeId)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Customer already exists, use updateSubscription endpoint.',
|
message: 'Customer already exists, use updateSubscription endpoint.',
|
||||||
})
|
})
|
||||||
|
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { Plan } from '@typebot.io/prisma'
|
|||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const createCustomCheckoutSession = authenticatedProcedure
|
export const createCustomCheckoutSession = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -31,7 +32,7 @@ export const createCustomCheckoutSession = authenticatedProcedure
|
|||||||
)
|
)
|
||||||
.mutation(
|
.mutation(
|
||||||
async ({ input: { email, workspaceId, returnUrl }, ctx: { user } }) => {
|
async ({ input: { email, workspaceId, returnUrl }, ctx: { user } }) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'Stripe environment variables are missing',
|
message: 'Stripe environment variables are missing',
|
||||||
@ -61,7 +62,7 @@ export const createCustomCheckoutSession = authenticatedProcedure
|
|||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Custom plan not found',
|
message: 'Custom plan not found',
|
||||||
})
|
})
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { TRPCError } from '@trpc/server'
|
|||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const getBillingPortalUrl = authenticatedProcedure
|
export const getBillingPortalUrl = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -26,7 +27,7 @@ export const getBillingPortalUrl = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'STRIPE_SECRET_KEY var is missing',
|
message: 'STRIPE_SECRET_KEY var is missing',
|
||||||
@ -50,12 +51,12 @@ export const getBillingPortalUrl = authenticatedProcedure
|
|||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Workspace not found',
|
message: 'Workspace not found',
|
||||||
})
|
})
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
const portalSession = await stripe.billingPortal.sessions.create({
|
const portalSession = await stripe.billingPortal.sessions.create({
|
||||||
customer: workspace.stripeId,
|
customer: workspace.stripeId,
|
||||||
return_url: `${process.env.NEXTAUTH_URL}/typebots`,
|
return_url: `${env.NEXTAUTH_URL}/typebots`,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
billingPortalUrl: portalSession.url,
|
billingPortalUrl: portalSession.url,
|
||||||
|
@ -4,8 +4,9 @@ import { TRPCError } from '@trpc/server'
|
|||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { subscriptionSchema } from '@typebot.io/schemas/features/billing/subscription'
|
import { subscriptionSchema } from '@typebot.io/schemas/features/billing/subscription'
|
||||||
import { priceIds } from '@typebot.io/lib/pricing'
|
|
||||||
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
|
||||||
|
import { priceIds } from '@typebot.io/lib/api/pricing'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const getSubscription = authenticatedProcedure
|
export const getSubscription = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -28,7 +29,7 @@ export const getSubscription = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'Stripe environment variables are missing',
|
message: 'Stripe environment variables are missing',
|
||||||
@ -55,7 +56,7 @@ export const getSubscription = authenticatedProcedure
|
|||||||
return {
|
return {
|
||||||
subscription: null,
|
subscription: null,
|
||||||
}
|
}
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
const subscriptions = await stripe.subscriptions.list({
|
const subscriptions = await stripe.subscriptions.list({
|
||||||
|
@ -6,6 +6,7 @@ import { isDefined } from '@typebot.io/lib'
|
|||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { invoiceSchema } from '@typebot.io/schemas/features/billing/invoice'
|
import { invoiceSchema } from '@typebot.io/schemas/features/billing/invoice'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const listInvoices = authenticatedProcedure
|
export const listInvoices = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -28,7 +29,7 @@ export const listInvoices = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
.query(async ({ input: { workspaceId }, ctx: { user } }) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'STRIPE_SECRET_KEY var is missing',
|
message: 'STRIPE_SECRET_KEY var is missing',
|
||||||
@ -52,7 +53,7 @@ export const listInvoices = authenticatedProcedure
|
|||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Workspace not found',
|
message: 'Workspace not found',
|
||||||
})
|
})
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
const invoices = await stripe.invoices.list({
|
const invoices = await stripe.invoices.list({
|
||||||
|
@ -7,15 +7,13 @@ import { workspaceSchema } from '@typebot.io/schemas'
|
|||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
import { isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import {
|
import { getChatsLimit, getStorageLimit } from '@typebot.io/lib/pricing'
|
||||||
getChatsLimit,
|
|
||||||
getStorageLimit,
|
|
||||||
priceIds,
|
|
||||||
} from '@typebot.io/lib/pricing'
|
|
||||||
import { chatPriceIds, storagePriceIds } from './getSubscription'
|
import { chatPriceIds, storagePriceIds } from './getSubscription'
|
||||||
import { createCheckoutSessionUrl } from './createCheckoutSession'
|
import { createCheckoutSessionUrl } from './createCheckoutSession'
|
||||||
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
import { isAdminWriteWorkspaceForbidden } from '@/features/workspace/helpers/isAdminWriteWorkspaceForbidden'
|
||||||
import { getUsage } from '@typebot.io/lib/api/getUsage'
|
import { getUsage } from '@typebot.io/lib/api/getUsage'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
import { priceIds } from '@typebot.io/lib/api/pricing'
|
||||||
|
|
||||||
export const updateSubscription = authenticatedProcedure
|
export const updateSubscription = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -57,7 +55,7 @@ export const updateSubscription = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
ctx: { user },
|
ctx: { user },
|
||||||
}) => {
|
}) => {
|
||||||
if (!process.env.STRIPE_SECRET_KEY)
|
if (!env.STRIPE_SECRET_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message: 'Stripe environment variables are missing',
|
message: 'Stripe environment variables are missing',
|
||||||
@ -85,7 +83,7 @@ export const updateSubscription = authenticatedProcedure
|
|||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
message: 'Workspace not found',
|
message: 'Workspace not found',
|
||||||
})
|
})
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
const { data } = await stripe.subscriptions.list({
|
const { data } = await stripe.subscriptions.list({
|
||||||
@ -95,10 +93,9 @@ export const updateSubscription = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
const subscription = data[0] as Stripe.Subscription | undefined
|
const subscription = data[0] as Stripe.Subscription | undefined
|
||||||
const currentPlanItemId = subscription?.items.data.find((item) =>
|
const currentPlanItemId = subscription?.items.data.find((item) =>
|
||||||
[
|
[env.STRIPE_STARTER_PRODUCT_ID, env.STRIPE_PRO_PRODUCT_ID].includes(
|
||||||
process.env.STRIPE_STARTER_PRODUCT_ID,
|
item.price.product.toString()
|
||||||
process.env.STRIPE_PRO_PRODUCT_ID,
|
)
|
||||||
].includes(item.price.product.toString())
|
|
||||||
)?.id
|
)?.id
|
||||||
const currentAdditionalChatsItemId = subscription?.items.data.find(
|
const currentAdditionalChatsItemId = subscription?.items.data.find(
|
||||||
(item) => chatPriceIds.includes(item.price.id)
|
(item) => chatPriceIds.includes(item.price.id)
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
deleteWorkspaces,
|
deleteWorkspaces,
|
||||||
injectFakeResults,
|
injectFakeResults,
|
||||||
} from '@typebot.io/lib/playwright/databaseActions'
|
} from '@typebot.io/lib/playwright/databaseActions'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const usageWorkspaceId = createId()
|
const usageWorkspaceId = createId()
|
||||||
const usageTypebotId = createId()
|
const usageTypebotId = createId()
|
||||||
@ -147,7 +148,7 @@ test('plan changes should work', async ({ page }) => {
|
|||||||
planChangeWorkspaceId,
|
planChangeWorkspaceId,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
price: process.env.STRIPE_STARTER_MONTHLY_PRICE_ID,
|
price: env.STRIPE_STARTER_MONTHLY_PRICE_ID,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import {
|
import { getChatsLimit, getStorageLimit } from '@typebot.io/lib/pricing'
|
||||||
getChatsLimit,
|
import { priceIds } from '@typebot.io/lib/api/pricing'
|
||||||
getStorageLimit,
|
|
||||||
priceIds,
|
|
||||||
} from '@typebot.io/lib/pricing'
|
|
||||||
|
|
||||||
export const parseSubscriptionItems = (
|
export const parseSubscriptionItems = (
|
||||||
plan: 'STARTER' | 'PRO',
|
plan: 'STARTER' | 'PRO',
|
||||||
|
@ -4,6 +4,7 @@ import { parseDefaultGroupWithBlock } from '@typebot.io/lib/playwright/databaseH
|
|||||||
import { defaultPaymentInputOptions, InputBlockType } from '@typebot.io/schemas'
|
import { defaultPaymentInputOptions, InputBlockType } from '@typebot.io/schemas'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { stripePaymentForm } from '@/test/utils/selectorUtils'
|
import { stripePaymentForm } from '@/test/utils/selectorUtils'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
test.describe('Payment input block', () => {
|
test.describe('Payment input block', () => {
|
||||||
test('Can configure Stripe account', async ({ page }) => {
|
test('Can configure Stripe account', async ({ page }) => {
|
||||||
@ -23,21 +24,15 @@ test.describe('Payment input block', () => {
|
|||||||
await page.getByRole('button', { name: 'Select an account' }).click()
|
await page.getByRole('button', { name: 'Select an account' }).click()
|
||||||
await page.click('text=Connect new')
|
await page.click('text=Connect new')
|
||||||
await page.fill('[placeholder="Typebot"]', 'My Stripe Account')
|
await page.fill('[placeholder="Typebot"]', 'My Stripe Account')
|
||||||
await page.fill(
|
await page.fill('[placeholder="sk_test_..."]', env.STRIPE_SECRET_KEY ?? '')
|
||||||
'[placeholder="sk_test_..."]',
|
await page.fill('[placeholder="sk_live_..."]', env.STRIPE_SECRET_KEY ?? '')
|
||||||
process.env.STRIPE_SECRET_KEY ?? ''
|
|
||||||
)
|
|
||||||
await page.fill(
|
|
||||||
'[placeholder="sk_live_..."]',
|
|
||||||
process.env.STRIPE_SECRET_KEY ?? ''
|
|
||||||
)
|
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'[placeholder="pk_test_..."]',
|
'[placeholder="pk_test_..."]',
|
||||||
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
|
env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
|
||||||
)
|
)
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'[placeholder="pk_live_..."]',
|
'[placeholder="pk_live_..."]',
|
||||||
process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
|
env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? ''
|
||||||
)
|
)
|
||||||
await expect(page.locator('button >> text="Connect"')).toBeEnabled()
|
await expect(page.locator('button >> text="Connect"')).toBeEnabled()
|
||||||
await page.click('button >> text="Connect"')
|
await page.click('button >> text="Connect"')
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||||
import { SendEmailOptions, Variable } from '@typebot.io/schemas'
|
import { SendEmailOptions, Variable } from '@typebot.io/schemas'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { env, isNotEmpty } from '@typebot.io/lib'
|
import { isNotEmpty } from '@typebot.io/lib'
|
||||||
import { SmtpConfigModal } from './SmtpConfigModal'
|
import { SmtpConfigModal } from './SmtpConfigModal'
|
||||||
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
|
||||||
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
|
||||||
@ -18,6 +18,7 @@ import { CredentialsDropdown } from '@/features/credentials/components/Credentia
|
|||||||
import { TextInput, Textarea } from '@/components/inputs'
|
import { TextInput, Textarea } from '@/components/inputs'
|
||||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||||
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
import { MoreInfoTooltip } from '@/components/MoreInfoTooltip'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options: SendEmailOptions
|
options: SendEmailOptions
|
||||||
@ -117,9 +118,9 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
currentCredentialsId={options.credentialsId}
|
currentCredentialsId={options.credentialsId}
|
||||||
onCredentialsSelect={handleCredentialsSelect}
|
onCredentialsSelect={handleCredentialsSelect}
|
||||||
onCreateNewClick={onOpen}
|
onCreateNewClick={onOpen}
|
||||||
defaultCredentialLabel={env('SMTP_FROM')
|
defaultCredentialLabel={env.NEXT_PUBLIC_SMTP_FROM?.match(
|
||||||
?.match(/<(.*)>/)
|
/<(.*)>/
|
||||||
?.pop()}
|
)?.pop()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -2,17 +2,18 @@ import test, { expect } from '@playwright/test'
|
|||||||
import { importTypebotInDatabase } from '@typebot.io/lib/playwright/databaseActions'
|
import { importTypebotInDatabase } from '@typebot.io/lib/playwright/databaseActions'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const typebotId = createId()
|
const typebotId = createId()
|
||||||
|
|
||||||
test.describe('Send email block', () => {
|
test.describe('Send email block', () => {
|
||||||
test('its configuration should work', async ({ page }) => {
|
test('its configuration should work', async ({ page }) => {
|
||||||
if (
|
if (
|
||||||
!process.env.SMTP_USERNAME ||
|
!env.SMTP_USERNAME ||
|
||||||
!process.env.SMTP_PORT ||
|
!env.SMTP_PORT ||
|
||||||
!process.env.SMTP_HOST ||
|
!env.SMTP_HOST ||
|
||||||
!process.env.SMTP_PASSWORD ||
|
!env.SMTP_PASSWORD ||
|
||||||
!process.env.NEXT_PUBLIC_SMTP_FROM
|
!env.NEXT_PUBLIC_SMTP_FROM
|
||||||
)
|
)
|
||||||
throw new Error('SMTP_ env vars are missing')
|
throw new Error('SMTP_ env vars are missing')
|
||||||
await importTypebotInDatabase(
|
await importTypebotInDatabase(
|
||||||
@ -30,21 +31,18 @@ test.describe('Send email block', () => {
|
|||||||
await expect(createButton).toBeDisabled()
|
await expect(createButton).toBeDisabled()
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'[placeholder="notifications@provider.com"]',
|
'[placeholder="notifications@provider.com"]',
|
||||||
process.env.SMTP_USERNAME
|
env.SMTP_USERNAME
|
||||||
)
|
)
|
||||||
await page.fill('[placeholder="John Smith"]', 'John Smith')
|
await page.fill('[placeholder="John Smith"]', 'John Smith')
|
||||||
await page.fill('[placeholder="mail.provider.com"]', process.env.SMTP_HOST)
|
await page.fill('[placeholder="mail.provider.com"]', env.SMTP_HOST)
|
||||||
await page.fill(
|
await page.fill('[placeholder="user@provider.com"]', env.SMTP_USERNAME)
|
||||||
'[placeholder="user@provider.com"]',
|
await page.fill('[type="password"]', env.SMTP_PASSWORD)
|
||||||
process.env.SMTP_USERNAME
|
await page.fill('input[role="spinbutton"]', env.SMTP_PORT.toString())
|
||||||
)
|
|
||||||
await page.fill('[type="password"]', process.env.SMTP_PASSWORD)
|
|
||||||
await page.fill('input[role="spinbutton"]', process.env.SMTP_PORT)
|
|
||||||
await expect(createButton).toBeEnabled()
|
await expect(createButton).toBeEnabled()
|
||||||
await createButton.click()
|
await createButton.click()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.locator(`button >> text=${process.env.SMTP_USERNAME}`)
|
page.locator(`button >> text=${env.SMTP_USERNAME}`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.fill(
|
await page.fill(
|
||||||
|
@ -7,6 +7,7 @@ import { HttpMethod } from '@typebot.io/schemas/features/blocks/integrations/web
|
|||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import { apiToken } from '@typebot.io/lib/playwright/databaseSetup'
|
import { apiToken } from '@typebot.io/lib/playwright/databaseSetup'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
test.describe('Builder', () => {
|
test.describe('Builder', () => {
|
||||||
test('easy configuration should work', async ({ page }) => {
|
test('easy configuration should work', async ({ page }) => {
|
||||||
@ -22,7 +23,7 @@ test.describe('Builder', () => {
|
|||||||
await page.click('text=Configure...')
|
await page.click('text=Configure...')
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'input[placeholder="Paste webhook URL..."]',
|
'input[placeholder="Paste webhook URL..."]',
|
||||||
`${process.env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
|
`${env.NEXTAUTH_URL}/api/mock/webhook-easy-config`
|
||||||
)
|
)
|
||||||
await page.click('text=Test the request')
|
await page.click('text=Test the request')
|
||||||
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
await expect(page.locator('div[role="textbox"] >> nth=-1')).toContainText(
|
||||||
@ -45,7 +46,7 @@ test.describe('Builder', () => {
|
|||||||
await page.click('text=Configure...')
|
await page.click('text=Configure...')
|
||||||
await page.fill(
|
await page.fill(
|
||||||
'input[placeholder="Paste webhook URL..."]',
|
'input[placeholder="Paste webhook URL..."]',
|
||||||
`${process.env.NEXTAUTH_URL}/api/mock/webhook`
|
`${env.NEXTAUTH_URL}/api/mock/webhook`
|
||||||
)
|
)
|
||||||
await page.click('text=Advanced configuration')
|
await page.click('text=Advanced configuration')
|
||||||
await page.getByRole('button', { name: 'GET' }).click()
|
await page.getByRole('button', { name: 'GET' }).click()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { fetcher } from '@/helpers/fetcher'
|
import { fetcher } from '@/helpers/fetcher'
|
||||||
import { Invitation } from '@typebot.io/prisma'
|
import { Invitation } from '@typebot.io/prisma'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const useInvitations = ({
|
export const useInvitations = ({
|
||||||
typebotId,
|
typebotId,
|
||||||
@ -14,7 +14,7 @@ export const useInvitations = ({
|
|||||||
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
|
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { publicProcedure } from '@/helpers/server/trpc'
|
import { publicProcedure } from '@/helpers/server/trpc'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const getAppVersionProcedure = publicProcedure.query(async () => {
|
export const getAppVersionProcedure = publicProcedure.query(async () => {
|
||||||
return { commitSha: process.env.VERCEL_GIT_COMMIT_SHA }
|
return { commitSha: env.VERCEL_GIT_COMMIT_SHA }
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,7 @@ import { fetcher } from '@/helpers/fetcher'
|
|||||||
import { DashboardFolder } from '@typebot.io/prisma'
|
import { DashboardFolder } from '@typebot.io/prisma'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const useFolders = ({
|
export const useFolders = ({
|
||||||
parentId,
|
parentId,
|
||||||
@ -18,7 +18,7 @@ export const useFolders = ({
|
|||||||
workspaceId ? `/api/folders?${params}` : null,
|
workspaceId ? `/api/folders?${params}` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
import { isDefined, getViewerUrl, isNotDefined, env } from '@typebot.io/lib'
|
import { isDefined, isNotDefined } from '@typebot.io/lib'
|
||||||
import { isPublicDomainAvailableQuery } from '../queries/isPublicDomainAvailableQuery'
|
import { isPublicDomainAvailableQuery } from '../queries/isPublicDomainAvailableQuery'
|
||||||
import { EditableUrl } from './EditableUrl'
|
import { EditableUrl } from './EditableUrl'
|
||||||
import { integrationsList } from './embeds/EmbedButton'
|
import { integrationsList } from './embeds/EmbedButton'
|
||||||
@ -25,6 +25,7 @@ import { CustomDomainsDropdown } from '@/features/customDomains/components/Custo
|
|||||||
import { TypebotHeader } from '@/features/editor/components/TypebotHeader'
|
import { TypebotHeader } from '@/features/editor/components/TypebotHeader'
|
||||||
import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId'
|
import { parseDefaultPublicId } from '../helpers/parseDefaultPublicId'
|
||||||
import { useI18n } from '@/locales'
|
import { useI18n } from '@/locales'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const SharePage = () => {
|
export const SharePage = () => {
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@ -97,7 +98,7 @@ export const SharePage = () => {
|
|||||||
</Heading>
|
</Heading>
|
||||||
{typebot && (
|
{typebot && (
|
||||||
<EditableUrl
|
<EditableUrl
|
||||||
hostname={getViewerUrl() ?? 'https://typebot.io'}
|
hostname={env.NEXT_PUBLIC_VIEWER_URL[0]}
|
||||||
pathname={publicId}
|
pathname={publicId}
|
||||||
isValid={checkIfPublicIdIsValid}
|
isValid={checkIfPublicIdIsValid}
|
||||||
onPathnameChange={handlePublicIdChange}
|
onPathnameChange={handlePublicIdChange}
|
||||||
@ -120,7 +121,7 @@ export const SharePage = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
{isNotDefined(typebot?.customDomain) &&
|
{isNotDefined(typebot?.customDomain) &&
|
||||||
env('VERCEL_VIEWER_PROJECT_NAME') ? (
|
env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME ? (
|
||||||
<>
|
<>
|
||||||
{isProPlan(workspace) ? (
|
{isProPlan(workspace) ? (
|
||||||
<CustomDomainsDropdown
|
<CustomDomainsDropdown
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Stack,
|
Stack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { env, getViewerUrl } from '@typebot.io/lib'
|
import { getViewerUrl } from '@typebot.io/lib'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { ModalProps } from '../EmbedButton'
|
||||||
|
|
||||||
export const FlutterFlowModal = ({
|
export const FlutterFlowModal = ({
|
||||||
@ -51,16 +51,12 @@ export const FlutterFlowModal = ({
|
|||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input
|
<Input
|
||||||
type={'text'}
|
type={'text'}
|
||||||
defaultValue={`${
|
defaultValue={`${getViewerUrl()}/${publicId}`}
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
|
||||||
}/${publicId}`}
|
|
||||||
/>
|
/>
|
||||||
<InputRightElement width="60px">
|
<InputRightElement width="60px">
|
||||||
<CopyButton
|
<CopyButton
|
||||||
size="sm"
|
size="sm"
|
||||||
textToCopy={`${
|
textToCopy={`${getViewerUrl()}/${publicId}`}
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
|
||||||
}/${publicId}`}
|
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FlexProps } from '@chakra-ui/react'
|
import { FlexProps } from '@chakra-ui/react'
|
||||||
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
|
||||||
import { env, getViewerUrl } from '@typebot.io/lib'
|
import { getViewerUrl } from '@typebot.io/lib'
|
||||||
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
import { CodeEditor } from '@/components/inputs/CodeEditor'
|
||||||
import prettier from 'prettier/standalone'
|
import prettier from 'prettier/standalone'
|
||||||
import parserHtml from 'prettier/parser-html'
|
import parserHtml from 'prettier/parser-html'
|
||||||
@ -13,9 +13,7 @@ type Props = {
|
|||||||
|
|
||||||
export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => {
|
export const IframeSnippet = ({ widthLabel, heightLabel }: Props) => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const src = `${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}/${
|
const src = `${getViewerUrl()}/${typebot?.publicId}`
|
||||||
typebot?.publicId
|
|
||||||
}`
|
|
||||||
const code = prettier.format(
|
const code = prettier.format(
|
||||||
`<iframe src="${src}" style="border: none; width='${widthLabel}'; height='${heightLabel}'"></iframe>`,
|
`<iframe src="${src}" style="border: none; width='${widthLabel}'; height='${heightLabel}'"></iframe>`,
|
||||||
{ parser: 'html', plugins: [parserHtml] }
|
{ parser: 'html', plugins: [parserHtml] }
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Stack,
|
Stack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { env, getViewerUrl } from '@typebot.io/lib'
|
import { getViewerUrl } from '@typebot.io/lib'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { ModalProps } from '../EmbedButton'
|
||||||
|
|
||||||
export const NotionModal = ({
|
export const NotionModal = ({
|
||||||
@ -49,16 +49,12 @@ export const NotionModal = ({
|
|||||||
<InputGroup size="sm">
|
<InputGroup size="sm">
|
||||||
<Input
|
<Input
|
||||||
type={'text'}
|
type={'text'}
|
||||||
defaultValue={`${
|
defaultValue={`${getViewerUrl()}/${publicId}`}
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
|
||||||
}/${publicId}`}
|
|
||||||
/>
|
/>
|
||||||
<InputRightElement width="60px">
|
<InputRightElement width="60px">
|
||||||
<CopyButton
|
<CopyButton
|
||||||
size="sm"
|
size="sm"
|
||||||
textToCopy={`${
|
textToCopy={`${getViewerUrl()}/${publicId}`}
|
||||||
env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
|
||||||
}/${publicId}`}
|
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { StandardSettings } from '../../../settings/StandardSettings'
|
import { StandardSettings } from '../../../settings/StandardSettings'
|
||||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||||
import { env, getViewerUrl } from '@typebot.io/lib'
|
import { getViewerUrl } from '@typebot.io/lib'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
publicId: string
|
publicId: string
|
||||||
@ -76,9 +76,7 @@ const parseWordpressShortcode = ({
|
|||||||
publicId: string
|
publicId: string
|
||||||
}) => {
|
}) => {
|
||||||
return `[typebot typebot="${publicId}"${
|
return `[typebot typebot="${publicId}"${
|
||||||
isCloudProdInstance
|
isCloudProdInstance ? '' : ` host="${getViewerUrl()}"`
|
||||||
? ''
|
|
||||||
: ` host="${env('VIEWER_INTERNAL_URL') ?? getViewerUrl()}"`
|
|
||||||
}${width ? ` width="${width}"` : ''}${height ? ` height="${height}"` : ''}]
|
}${width ? ` width="${width}"` : ''}${height ? ` height="${height}"` : ''}]
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BotProps } from '@typebot.io/nextjs'
|
import { BotProps } from '@typebot.io/nextjs'
|
||||||
import parserBabel from 'prettier/parser-babel'
|
import parserBabel from 'prettier/parser-babel'
|
||||||
import prettier from 'prettier/standalone'
|
import prettier from 'prettier/standalone'
|
||||||
import { env, getViewerUrl, isDefined } from '@typebot.io/lib'
|
import { getViewerUrl, isDefined } from '@typebot.io/lib'
|
||||||
import { Typebot } from '@typebot.io/schemas'
|
import { Typebot } from '@typebot.io/schemas'
|
||||||
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
import { isCloudProdInstance } from '@/helpers/isCloudProdInstance'
|
||||||
import packageJson from '../../../../../../../../packages/embeds/js/package.json'
|
import packageJson from '../../../../../../../../packages/embeds/js/package.json'
|
||||||
@ -58,7 +58,7 @@ export const parseApiHost = (
|
|||||||
customDomain: Typebot['customDomain'] | undefined
|
customDomain: Typebot['customDomain'] | undefined
|
||||||
) => {
|
) => {
|
||||||
if (customDomain) return new URL(`https://${customDomain}`).origin
|
if (customDomain) return new URL(`https://${customDomain}`).origin
|
||||||
return env('VIEWER_INTERNAL_URL') ?? getViewerUrl()
|
return getViewerUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const parseApiHostValue = (
|
export const parseApiHostValue = (
|
||||||
|
@ -4,6 +4,7 @@ import { PostHog } from 'posthog-node'
|
|||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
import got from 'got'
|
import got from 'got'
|
||||||
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
import { authenticatedProcedure } from '@/helpers/server/trpc'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
// Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services.
|
// Only used for the cloud version of Typebot. It's the way it processes telemetry events and inject it to thrid-party services.
|
||||||
export const processTelemetryEvent = authenticatedProcedure
|
export const processTelemetryEvent = authenticatedProcedure
|
||||||
@ -26,17 +27,17 @@ export const processTelemetryEvent = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { events }, ctx: { user } }) => {
|
.query(async ({ input: { events }, ctx: { user } }) => {
|
||||||
if (user.email !== process.env.ADMIN_EMAIL)
|
if (user.email !== env.ADMIN_EMAIL)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Only app admin can process telemetry events',
|
message: 'Only app admin can process telemetry events',
|
||||||
})
|
})
|
||||||
if (!process.env.POSTHOG_API_KEY)
|
if (!env.POSTHOG_API_KEY)
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: 'Server does not have POSTHOG_API_KEY configured',
|
message: 'Server does not have POSTHOG_API_KEY configured',
|
||||||
})
|
})
|
||||||
const client = new PostHog(process.env.POSTHOG_API_KEY, {
|
const client = new PostHog(env.POSTHOG_API_KEY, {
|
||||||
host: 'https://eu.posthog.com',
|
host: 'https://eu.posthog.com',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -65,11 +66,8 @@ export const processTelemetryEvent = authenticatedProcedure
|
|||||||
groupKey: event.typebotId,
|
groupKey: event.typebotId,
|
||||||
properties: { name: event.data.name },
|
properties: { name: event.data.name },
|
||||||
})
|
})
|
||||||
if (
|
if (event.name === 'User created' && env.USER_CREATED_WEBHOOK_URL) {
|
||||||
event.name === 'User created' &&
|
await got.post(env.USER_CREATED_WEBHOOK_URL, {
|
||||||
process.env.USER_CREATED_WEBHOOK_URL
|
|
||||||
) {
|
|
||||||
await got.post(process.env.USER_CREATED_WEBHOOK_URL, {
|
|
||||||
json: {
|
json: {
|
||||||
email: event.data.email,
|
email: event.data.email,
|
||||||
name: event.data.name ? event.data.name.split(' ')[0] : undefined,
|
name: event.data.name ? event.data.name.split(' ')[0] : undefined,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Text, HStack } from '@chakra-ui/react'
|
import { Text, HStack } from '@chakra-ui/react'
|
||||||
import { env, isEmpty } from '@typebot.io/lib'
|
|
||||||
import { AutocompleteInput } from '@/components/inputs/AutocompleteInput'
|
import { AutocompleteInput } from '@/components/inputs/AutocompleteInput'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
type FontSelectorProps = {
|
type FontSelectorProps = {
|
||||||
activeFont?: string
|
activeFont?: string
|
||||||
@ -20,11 +20,9 @@ export const FontSelector = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const fetchPopularFonts = async () => {
|
const fetchPopularFonts = async () => {
|
||||||
if (isEmpty(env('GOOGLE_API_KEY'))) return []
|
if (!env.NEXT_PUBLIC_GOOGLE_API_KEY) return []
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://www.googleapis.com/webfonts/v1/webfonts?key=${env(
|
`https://www.googleapis.com/webfonts/v1/webfonts?key=${env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity`
|
||||||
'GOOGLE_API_KEY'
|
|
||||||
)}&sort=popularity`
|
|
||||||
)
|
)
|
||||||
return (await response.json()).items.map(
|
return (await response.json()).items.map(
|
||||||
(item: { family: string }) => item.family
|
(item: { family: string }) => item.family
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { CollaboratorsOnTypebots, User } from '@typebot.io/prisma'
|
import { CollaboratorsOnTypebots, User } from '@typebot.io/prisma'
|
||||||
import { Typebot } from '@typebot.io/schemas'
|
import { Typebot } from '@typebot.io/schemas'
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ export const isReadTypebotForbidden = async (
|
|||||||
user: Pick<User, 'email' | 'id'>
|
user: Pick<User, 'email' | 'id'>
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
process.env.ADMIN_EMAIL === user.email ||
|
env.ADMIN_EMAIL === user.email ||
|
||||||
typebot.collaborators.find(
|
typebot.collaborators.find(
|
||||||
(collaborator) => collaborator.userId === user.id
|
(collaborator) => collaborator.userId === user.id
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
|
|
||||||
export const parseWorkspaceDefaultPlan = (userEmail: string) => {
|
export const parseWorkspaceDefaultPlan = (userEmail: string) => {
|
||||||
if (process.env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED
|
if (env.ADMIN_EMAIL === userEmail) return Plan.UNLIMITED
|
||||||
const defaultPlan = process.env.DEFAULT_WORKSPACE_PLAN as Plan | undefined
|
const defaultPlan = env.DEFAULT_WORKSPACE_PLAN as Plan | undefined
|
||||||
if (defaultPlan && Object.values(Plan).includes(defaultPlan))
|
if (defaultPlan && Object.values(Plan).includes(defaultPlan))
|
||||||
return defaultPlan
|
return defaultPlan
|
||||||
return Plan.FREE
|
return Plan.FREE
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { WorkspaceInvitation } from '@typebot.io/prisma'
|
import { WorkspaceInvitation } from '@typebot.io/prisma'
|
||||||
import { fetcher } from '@/helpers/fetcher'
|
import { fetcher } from '@/helpers/fetcher'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
import { Member } from '../types'
|
import { Member } from '../types'
|
||||||
|
|
||||||
export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
|
export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
|
||||||
@ -9,7 +9,7 @@ export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
|
|||||||
{ members: Member[]; invitations: WorkspaceInvitation[] },
|
{ members: Member[]; invitations: WorkspaceInvitation[] },
|
||||||
Error
|
Error
|
||||||
>(workspaceId ? `/api/workspaces/${workspaceId}/members` : null, fetcher, {
|
>(workspaceId ? `/api/workspaces/${workspaceId}/members` : null, fetcher, {
|
||||||
dedupingInterval: env('E2E_TEST') === 'true' ? 0 : undefined,
|
dedupingInterval: env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
members: data?.members ?? [],
|
members: data?.members ?? [],
|
||||||
|
@ -7,14 +7,14 @@ import {
|
|||||||
} from '@typebot.io/prisma'
|
} from '@typebot.io/prisma'
|
||||||
import prisma from '@/lib/prisma'
|
import prisma from '@/lib/prisma'
|
||||||
import { NextApiResponse } from 'next'
|
import { NextApiResponse } from 'next'
|
||||||
import { env, isNotEmpty } from '@typebot.io/lib'
|
|
||||||
import { forbidden } from '@typebot.io/lib/api'
|
import { forbidden } from '@typebot.io/lib/api'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const canWriteTypebots = (
|
export const canWriteTypebots = (
|
||||||
typebotIds: string[] | string,
|
typebotIds: string[] | string,
|
||||||
user: Pick<User, 'email' | 'id'>
|
user: Pick<User, 'email' | 'id'>
|
||||||
): Prisma.TypebotWhereInput =>
|
): Prisma.TypebotWhereInput =>
|
||||||
isNotEmpty(env('E2E_TEST'))
|
env.NEXT_PUBLIC_E2E_TEST
|
||||||
? { id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds } }
|
? { id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds } }
|
||||||
: {
|
: {
|
||||||
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
||||||
@ -40,7 +40,7 @@ export const canReadTypebots = (
|
|||||||
) => ({
|
) => ({
|
||||||
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
||||||
workspace:
|
workspace:
|
||||||
user.email === process.env.ADMIN_EMAIL || isNotEmpty(env('E2E_TEST'))
|
user.email === env.ADMIN_EMAIL || env.NEXT_PUBLIC_E2E_TEST
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
members: {
|
members: {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { Client } from 'minio'
|
import { Client } from 'minio'
|
||||||
|
|
||||||
export const deleteFilesFromBucket = async ({
|
export const deleteFilesFromBucket = async ({
|
||||||
@ -5,32 +6,26 @@ export const deleteFilesFromBucket = async ({
|
|||||||
}: {
|
}: {
|
||||||
urls: string[]
|
urls: string[]
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
if (
|
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||||
!process.env.S3_ENDPOINT ||
|
|
||||||
!process.env.S3_ACCESS_KEY ||
|
|
||||||
!process.env.S3_SECRET_KEY
|
|
||||||
)
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
||||||
)
|
)
|
||||||
|
|
||||||
const useSSL =
|
|
||||||
process.env.S3_SSL && process.env.S3_SSL === 'false' ? false : true
|
|
||||||
const minioClient = new Client({
|
const minioClient = new Client({
|
||||||
endPoint: process.env.S3_ENDPOINT,
|
endPoint: env.S3_ENDPOINT,
|
||||||
port: process.env.S3_PORT ? parseInt(process.env.S3_PORT) : undefined,
|
port: env.S3_PORT,
|
||||||
useSSL,
|
useSSL: env.S3_SSL,
|
||||||
accessKey: process.env.S3_ACCESS_KEY,
|
accessKey: env.S3_ACCESS_KEY,
|
||||||
secretKey: process.env.S3_SECRET_KEY,
|
secretKey: env.S3_SECRET_KEY,
|
||||||
region: process.env.S3_REGION,
|
region: env.S3_REGION,
|
||||||
})
|
})
|
||||||
|
|
||||||
const bucket = process.env.S3_BUCKET ?? 'typebot'
|
const bucket = env.S3_BUCKET ?? 'typebot'
|
||||||
|
|
||||||
return minioClient.removeObjects(
|
return minioClient.removeObjects(
|
||||||
bucket,
|
bucket,
|
||||||
urls
|
urls
|
||||||
.filter((url) => url.includes(process.env.S3_ENDPOINT as string))
|
.filter((url) => url.includes(env.S3_ENDPOINT as string))
|
||||||
.map((url) => url.split(`/${bucket}/`)[1])
|
.map((url) => url.split(`/${bucket}/`)[1])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@ import { GoogleSheetsCredentials } from '@typebot.io/schemas'
|
|||||||
import { isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import { decrypt, encrypt } from '@typebot.io/lib/api'
|
import { decrypt, encrypt } from '@typebot.io/lib/api'
|
||||||
import prisma from './prisma'
|
import prisma from './prisma'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const oauth2Client = new OAuth2Client(
|
export const oauth2Client = new OAuth2Client(
|
||||||
process.env.GOOGLE_CLIENT_ID,
|
env.GOOGLE_CLIENT_ID,
|
||||||
process.env.GOOGLE_CLIENT_SECRET,
|
env.GOOGLE_CLIENT_SECRET,
|
||||||
`${process.env.NEXTAUTH_URL}/api/credentials/google-sheets/callback`
|
`${env.NEXTAUTH_URL}/api/credentials/google-sheets/callback`
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getAuthenticatedGoogleClient = async (
|
export const getAuthenticatedGoogleClient = async (
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* Instantiates a single instance PrismaClient and save it on the global object.
|
* Instantiates a single instance PrismaClient and save it on the global object.
|
||||||
* @link https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices
|
* @link https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices
|
||||||
*/
|
*/
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { PrismaClient } from '@typebot.io/prisma'
|
import { PrismaClient } from '@typebot.io/prisma'
|
||||||
|
|
||||||
const prismaGlobal = global as typeof global & {
|
const prismaGlobal = global as typeof global & {
|
||||||
@ -11,10 +12,10 @@ const prismaGlobal = global as typeof global & {
|
|||||||
const prisma: PrismaClient =
|
const prisma: PrismaClient =
|
||||||
prismaGlobal.prisma ||
|
prismaGlobal.prisma ||
|
||||||
new PrismaClient({
|
new PrismaClient({
|
||||||
log: process.env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'],
|
log: env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (env.NODE_ENV !== 'production') {
|
||||||
prismaGlobal.prisma = prisma
|
prismaGlobal.prisma = prisma
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,9 @@ import { createTRPCProxyClient, httpBatchLink, loggerLink } from '@trpc/client'
|
|||||||
import { createTRPCNext } from '@trpc/next'
|
import { createTRPCNext } from '@trpc/next'
|
||||||
import type { AppRouter } from '../helpers/server/routers/v1/trpcRouter'
|
import type { AppRouter } from '../helpers/server/routers/v1/trpcRouter'
|
||||||
import superjson from 'superjson'
|
import superjson from 'superjson'
|
||||||
import { env } from '@typebot.io/lib'
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const getBaseUrl = () =>
|
const getBaseUrl = () => (typeof window !== 'undefined' ? '' : env.NEXTAUTH_URL)
|
||||||
typeof window !== 'undefined' ? '' : process.env.NEXTAUTH_URL
|
|
||||||
|
|
||||||
export const trpc = createTRPCNext<AppRouter>({
|
export const trpc = createTRPCNext<AppRouter>({
|
||||||
config() {
|
config() {
|
||||||
@ -36,5 +35,5 @@ export const trpcVanilla = createTRPCProxyClient<AppRouter>({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const defaultQueryOptions = {
|
export const defaultQueryOptions = {
|
||||||
refetchOnMount: env('E2E_TEST') === 'true',
|
refetchOnMount: env.NEXT_PUBLIC_E2E_TEST,
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ const Document = () => (
|
|||||||
/>
|
/>
|
||||||
<meta name="google" content="notranslate" />
|
<meta name="google" content="notranslate" />
|
||||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||||
<script src="/__env.js" />
|
<script src="/__ENV.js" />
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<ColorModeScript initialColorMode={customTheme.config.initialColorMode} />
|
<ColorModeScript initialColorMode={customTheme.config.initialColorMode} />
|
||||||
|
@ -10,139 +10,114 @@ import { Provider } from 'next-auth/providers'
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { customAdapter } from '../../../features/auth/api/customAdapter'
|
import { customAdapter } from '../../../features/auth/api/customAdapter'
|
||||||
import { User } from '@typebot.io/prisma'
|
import { User } from '@typebot.io/prisma'
|
||||||
import { env, getAtPath, isDefined, isNotEmpty } from '@typebot.io/lib'
|
import { getAtPath, isDefined } from '@typebot.io/lib'
|
||||||
import { mockedUser } from '@/features/auth/mockedUser'
|
import { mockedUser } from '@/features/auth/mockedUser'
|
||||||
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
import { getNewUserInvitations } from '@/features/auth/helpers/getNewUserInvitations'
|
||||||
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
|
import { sendVerificationRequest } from '@/features/auth/helpers/sendVerificationRequest'
|
||||||
import { Ratelimit } from '@upstash/ratelimit'
|
import { Ratelimit } from '@upstash/ratelimit'
|
||||||
import { Redis } from '@upstash/redis/nodejs'
|
import { Redis } from '@upstash/redis/nodejs'
|
||||||
import got from 'got'
|
import got from 'got'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const providers: Provider[] = []
|
const providers: Provider[] = []
|
||||||
|
|
||||||
let rateLimit: Ratelimit | undefined
|
let rateLimit: Ratelimit | undefined
|
||||||
|
|
||||||
if (
|
if (env.UPSTASH_REDIS_REST_URL && env.UPSTASH_REDIS_REST_TOKEN) {
|
||||||
process.env.UPSTASH_REDIS_REST_URL &&
|
|
||||||
process.env.UPSTASH_REDIS_REST_TOKEN
|
|
||||||
) {
|
|
||||||
rateLimit = new Ratelimit({
|
rateLimit = new Ratelimit({
|
||||||
redis: Redis.fromEnv(),
|
redis: Redis.fromEnv(),
|
||||||
limiter: Ratelimit.slidingWindow(1, '60 s'),
|
limiter: Ratelimit.slidingWindow(1, '60 s'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET)
|
||||||
isNotEmpty(process.env.GITHUB_CLIENT_ID) &&
|
|
||||||
isNotEmpty(process.env.GITHUB_CLIENT_SECRET)
|
|
||||||
)
|
|
||||||
providers.push(
|
providers.push(
|
||||||
GitHubProvider({
|
GitHubProvider({
|
||||||
clientId: process.env.GITHUB_CLIENT_ID,
|
clientId: env.GITHUB_CLIENT_ID,
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
clientSecret: env.GITHUB_CLIENT_SECRET,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isNotEmpty(env('SMTP_FROM')) && process.env.SMTP_AUTH_DISABLED !== 'true')
|
if (env.NEXT_PUBLIC_SMTP_FROM && env.SMTP_AUTH_DISABLED)
|
||||||
providers.push(
|
providers.push(
|
||||||
EmailProvider({
|
EmailProvider({
|
||||||
server: {
|
server: {
|
||||||
host: process.env.SMTP_HOST,
|
host: env.SMTP_HOST,
|
||||||
port: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : 25,
|
port: env.SMTP_PORT ? Number(env.SMTP_PORT) : 25,
|
||||||
secure: process.env.SMTP_SECURE
|
secure: env.SMTP_SECURE ? env.SMTP_SECURE : false,
|
||||||
? process.env.SMTP_SECURE === 'true'
|
|
||||||
: false,
|
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.SMTP_USERNAME,
|
user: env.SMTP_USERNAME,
|
||||||
pass: process.env.SMTP_PASSWORD,
|
pass: env.SMTP_PASSWORD,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
from: env('SMTP_FROM'),
|
from: env.NEXT_PUBLIC_SMTP_FROM,
|
||||||
sendVerificationRequest,
|
sendVerificationRequest,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET)
|
||||||
isNotEmpty(process.env.GOOGLE_CLIENT_ID) &&
|
|
||||||
isNotEmpty(process.env.GOOGLE_CLIENT_SECRET)
|
|
||||||
)
|
|
||||||
providers.push(
|
providers.push(
|
||||||
GoogleProvider({
|
GoogleProvider({
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
clientId: env.GOOGLE_CLIENT_ID,
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (env.FACEBOOK_CLIENT_ID && env.FACEBOOK_CLIENT_SECRET)
|
||||||
isNotEmpty(process.env.FACEBOOK_CLIENT_ID) &&
|
|
||||||
isNotEmpty(process.env.FACEBOOK_CLIENT_SECRET)
|
|
||||||
)
|
|
||||||
providers.push(
|
providers.push(
|
||||||
FacebookProvider({
|
FacebookProvider({
|
||||||
clientId: process.env.FACEBOOK_CLIENT_ID,
|
clientId: env.FACEBOOK_CLIENT_ID,
|
||||||
clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
|
clientSecret: env.FACEBOOK_CLIENT_SECRET,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (env.GITLAB_CLIENT_ID && env.GITLAB_CLIENT_SECRET) {
|
||||||
isNotEmpty(process.env.GITLAB_CLIENT_ID) &&
|
const BASE_URL = env.GITLAB_BASE_URL || 'https://gitlab.com'
|
||||||
isNotEmpty(process.env.GITLAB_CLIENT_SECRET)
|
|
||||||
) {
|
|
||||||
const BASE_URL = process.env.GITLAB_BASE_URL || 'https://gitlab.com'
|
|
||||||
providers.push(
|
providers.push(
|
||||||
GitlabProvider({
|
GitlabProvider({
|
||||||
clientId: process.env.GITLAB_CLIENT_ID,
|
clientId: env.GITLAB_CLIENT_ID,
|
||||||
clientSecret: process.env.GITLAB_CLIENT_SECRET,
|
clientSecret: env.GITLAB_CLIENT_SECRET,
|
||||||
authorization: `${BASE_URL}/oauth/authorize?scope=read_api`,
|
authorization: `${BASE_URL}/oauth/authorize?scope=read_api`,
|
||||||
token: `${BASE_URL}/oauth/token`,
|
token: `${BASE_URL}/oauth/token`,
|
||||||
userinfo: `${BASE_URL}/api/v4/user`,
|
userinfo: `${BASE_URL}/api/v4/user`,
|
||||||
name: process.env.GITLAB_NAME || 'GitLab',
|
name: env.GITLAB_NAME || 'GitLab',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isNotEmpty(process.env.AZURE_AD_CLIENT_ID) &&
|
env.AZURE_AD_CLIENT_ID &&
|
||||||
isNotEmpty(process.env.AZURE_AD_CLIENT_SECRET) &&
|
env.AZURE_AD_CLIENT_SECRET &&
|
||||||
isNotEmpty(process.env.AZURE_AD_TENANT_ID)
|
env.AZURE_AD_TENANT_ID
|
||||||
) {
|
) {
|
||||||
providers.push(
|
providers.push(
|
||||||
AzureADProvider({
|
AzureADProvider({
|
||||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
clientId: env.AZURE_AD_CLIENT_ID,
|
||||||
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
|
clientSecret: env.AZURE_AD_CLIENT_SECRET,
|
||||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
tenantId: env.AZURE_AD_TENANT_ID,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotEmpty(process.env.CUSTOM_OAUTH_WELL_KNOWN_URL)) {
|
if (env.CUSTOM_OAUTH_WELL_KNOWN_URL) {
|
||||||
providers.push({
|
providers.push({
|
||||||
id: 'custom-oauth',
|
id: 'custom-oauth',
|
||||||
name: process.env.CUSTOM_OAUTH_NAME ?? 'Custom OAuth',
|
name: env.CUSTOM_OAUTH_NAME ?? 'Custom OAuth',
|
||||||
type: 'oauth',
|
type: 'oauth',
|
||||||
authorization: {
|
authorization: {
|
||||||
params: {
|
params: {
|
||||||
scope: process.env.CUSTOM_OAUTH_SCOPE ?? 'openid profile email',
|
scope: env.CUSTOM_OAUTH_SCOPE ?? 'openid profile email',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
clientId: process.env.CUSTOM_OAUTH_CLIENT_ID,
|
clientId: env.CUSTOM_OAUTH_CLIENT_ID,
|
||||||
clientSecret: process.env.CUSTOM_OAUTH_CLIENT_SECRET,
|
clientSecret: env.CUSTOM_OAUTH_CLIENT_SECRET,
|
||||||
wellKnown: process.env.CUSTOM_OAUTH_WELL_KNOWN_URL,
|
wellKnown: env.CUSTOM_OAUTH_WELL_KNOWN_URL,
|
||||||
profile(profile) {
|
profile(profile) {
|
||||||
return {
|
return {
|
||||||
id: getAtPath(profile, process.env.CUSTOM_OAUTH_USER_ID_PATH ?? 'id'),
|
id: getAtPath(profile, env.CUSTOM_OAUTH_USER_ID_PATH ?? 'id'),
|
||||||
name: getAtPath(
|
name: getAtPath(profile, env.CUSTOM_OAUTH_USER_NAME_PATH ?? 'name'),
|
||||||
profile,
|
email: getAtPath(profile, env.CUSTOM_OAUTH_USER_EMAIL_PATH ?? 'email'),
|
||||||
process.env.CUSTOM_OAUTH_USER_NAME_PATH ?? 'name'
|
image: getAtPath(profile, env.CUSTOM_OAUTH_USER_IMAGE_PATH ?? 'image'),
|
||||||
),
|
|
||||||
email: getAtPath(
|
|
||||||
profile,
|
|
||||||
process.env.CUSTOM_OAUTH_USER_EMAIL_PATH ?? 'email'
|
|
||||||
),
|
|
||||||
image: getAtPath(
|
|
||||||
profile,
|
|
||||||
process.env.CUSTOM_OAUTH_USER_IMAGE_PATH ?? 'image'
|
|
||||||
),
|
|
||||||
} as User
|
} as User
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -150,16 +125,14 @@ if (isNotEmpty(process.env.CUSTOM_OAUTH_WELL_KNOWN_URL)) {
|
|||||||
|
|
||||||
export const authOptions: AuthOptions = {
|
export const authOptions: AuthOptions = {
|
||||||
adapter: customAdapter(prisma),
|
adapter: customAdapter(prisma),
|
||||||
secret: process.env.ENCRYPTION_SECRET,
|
secret: env.ENCRYPTION_SECRET,
|
||||||
providers,
|
providers,
|
||||||
session: {
|
session: {
|
||||||
strategy: 'database',
|
strategy: 'database',
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
signIn: '/signin',
|
signIn: '/signin',
|
||||||
newUser: process.env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID
|
newUser: env.NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID ? '/onboarding' : undefined,
|
||||||
? '/onboarding'
|
|
||||||
: undefined,
|
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
session: async ({ session, user }) => {
|
session: async ({ session, user }) => {
|
||||||
@ -181,7 +154,7 @@ export const authOptions: AuthOptions = {
|
|||||||
if (disposableEmailDomains.includes(user.email.split('@')[1]))
|
if (disposableEmailDomains.includes(user.email.split('@')[1]))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (process.env.DISABLE_SIGNUP === 'true' && isNewUser && user.email) {
|
if (env.DISABLE_SIGNUP && isNewUser && user.email) {
|
||||||
const { invitations, workspaceInvitations } =
|
const { invitations, workspaceInvitations } =
|
||||||
await getNewUserInvitations(prisma, user.email)
|
await getNewUserInvitations(prisma, user.email)
|
||||||
if (invitations.length === 0 && workspaceInvitations.length === 0)
|
if (invitations.length === 0 && workspaceInvitations.length === 0)
|
||||||
@ -201,7 +174,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const isMockingSession =
|
const isMockingSession =
|
||||||
req.method === 'GET' &&
|
req.method === 'GET' &&
|
||||||
req.url === '/api/auth/session' &&
|
req.url === '/api/auth/session' &&
|
||||||
env('E2E_TEST') === 'true'
|
env.NEXT_PUBLIC_E2E_TEST
|
||||||
if (isMockingSession) return res.send({ user: mockedUser })
|
if (isMockingSession) return res.send({ user: mockedUser })
|
||||||
const requestIsFromCompanyFirewall = req.method === 'HEAD'
|
const requestIsFromCompanyFirewall = req.method === 'HEAD'
|
||||||
if (requestIsFromCompanyFirewall) return res.status(200).end()
|
if (requestIsFromCompanyFirewall) return res.status(200).end()
|
||||||
@ -248,7 +221,7 @@ const getUserGroups = async (account: Account): Promise<string[]> => {
|
|||||||
): Promise<{ full_path: string }[]> => {
|
): Promise<{ full_path: string }[]> => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${
|
`${
|
||||||
process.env.GITLAB_BASE_URL || 'https://gitlab.com'
|
env.GITLAB_BASE_URL || 'https://gitlab.com'
|
||||||
}/api/v4/groups?per_page=100&page=${page}`,
|
}/api/v4/groups?per_page=100&page=${page}`,
|
||||||
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
{ headers: { Authorization: `Bearer ${accessToken}` } }
|
||||||
)
|
)
|
||||||
@ -269,7 +242,7 @@ const getUserGroups = async (account: Account): Promise<string[]> => {
|
|||||||
const getRequiredGroups = (provider: string): string[] => {
|
const getRequiredGroups = (provider: string): string[] => {
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'gitlab':
|
case 'gitlab':
|
||||||
return process.env.GITLAB_REQUIRED_GROUPS?.split(',') || []
|
return env.GITLAB_REQUIRED_GROUPS ?? []
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { stringify } from 'querystring'
|
|||||||
import { badRequest, encrypt, notAuthenticated } from '@typebot.io/lib/api'
|
import { badRequest, encrypt, notAuthenticated } from '@typebot.io/lib/api'
|
||||||
import { oauth2Client } from '@/lib/googleSheets'
|
import { oauth2Client } from '@/lib/googleSheets'
|
||||||
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await getAuthenticatedUser(req, res)
|
const user = await getAuthenticatedUser(req, res)
|
||||||
@ -49,9 +50,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
data: credentials,
|
data: credentials,
|
||||||
})
|
})
|
||||||
const queryParams = stringify({ blockId, credentialsId })
|
const queryParams = stringify({ blockId, credentialsId })
|
||||||
res.redirect(
|
res.redirect(`${redirectUrl}?${queryParams}` ?? `${env.NEXTAUTH_URL}`)
|
||||||
`${redirectUrl}?${queryParams}` ?? `${process.env.NEXTAUTH_URL}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
} from '@typebot.io/lib/api'
|
} from '@typebot.io/lib/api'
|
||||||
import { got } from 'got'
|
import { got } from 'got'
|
||||||
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await getAuthenticatedUser(req, res)
|
const user = await getAuthenticatedUser(req, res)
|
||||||
@ -31,8 +32,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
|
|
||||||
const deleteDomainOnVercel = (name: string) =>
|
const deleteDomainOnVercel = (name: string) =>
|
||||||
got.delete({
|
got.delete({
|
||||||
url: `https://api.vercel.com/v8/projects/${process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${process.env.VERCEL_TEAM_ID}`,
|
url: `https://api.vercel.com/v8/projects/${env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains/${name}?teamId=${env.VERCEL_TEAM_ID}`,
|
||||||
headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` },
|
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` },
|
||||||
})
|
})
|
||||||
|
|
||||||
export default handler
|
export default handler
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
methodNotAllowed,
|
methodNotAllowed,
|
||||||
notAuthenticated,
|
notAuthenticated,
|
||||||
} from '@typebot.io/lib/api'
|
} from '@typebot.io/lib/api'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const handler = async (
|
const handler = async (
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -16,11 +17,7 @@ const handler = async (
|
|||||||
const user = await getAuthenticatedUser(req, res)
|
const user = await getAuthenticatedUser(req, res)
|
||||||
if (!user) return notAuthenticated(res)
|
if (!user) return notAuthenticated(res)
|
||||||
|
|
||||||
if (
|
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||||
!process.env.S3_ENDPOINT ||
|
|
||||||
!process.env.S3_ACCESS_KEY ||
|
|
||||||
!process.env.S3_SECRET_KEY
|
|
||||||
)
|
|
||||||
return badRequest(
|
return badRequest(
|
||||||
res,
|
res,
|
||||||
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
'S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY'
|
||||||
|
@ -8,10 +8,11 @@ import { Plan, WorkspaceRole } from '@typebot.io/prisma'
|
|||||||
import { RequestHandler } from 'next/dist/server/next'
|
import { RequestHandler } from 'next/dist/server/next'
|
||||||
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent'
|
||||||
import { Settings } from '@typebot.io/schemas'
|
import { Settings } from '@typebot.io/schemas'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET)
|
if (!env.STRIPE_SECRET_KEY || !env.STRIPE_WEBHOOK_SECRET)
|
||||||
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
|
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ const cors = Cors({
|
|||||||
allowMethods: ['POST', 'HEAD'],
|
allowMethods: ['POST', 'HEAD'],
|
||||||
})
|
})
|
||||||
|
|
||||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string
|
const webhookSecret = env.STRIPE_WEBHOOK_SECRET as string
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
|
@ -13,8 +13,8 @@ import {
|
|||||||
notAuthenticated,
|
notAuthenticated,
|
||||||
} from '@typebot.io/lib/api'
|
} from '@typebot.io/lib/api'
|
||||||
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
||||||
import { env } from '@typebot.io/lib'
|
|
||||||
import { sendGuestInvitationEmail } from '@typebot.io/emails'
|
import { sendGuestInvitationEmail } from '@typebot.io/emails'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await getAuthenticatedUser(req, res)
|
const user = await getAuthenticatedUser(req, res)
|
||||||
@ -80,11 +80,11 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
await prisma.invitation.create({
|
await prisma.invitation.create({
|
||||||
data: { email: email.toLowerCase().trim(), type, typebotId },
|
data: { email: email.toLowerCase().trim(), type, typebotId },
|
||||||
})
|
})
|
||||||
if (env('E2E_TEST') !== 'true')
|
if (!env.NEXT_PUBLIC_E2E_TEST)
|
||||||
await sendGuestInvitationEmail({
|
await sendGuestInvitationEmail({
|
||||||
to: email,
|
to: email,
|
||||||
hostEmail: user.email ?? '',
|
hostEmail: user.email ?? '',
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${typebot.workspaceId}`,
|
url: `${env.NEXTAUTH_URL}/typebots?workspaceId=${typebot.workspaceId}`,
|
||||||
guestEmail: email.toLowerCase(),
|
guestEmail: email.toLowerCase(),
|
||||||
typebotName: typebot.name,
|
typebotName: typebot.name,
|
||||||
workspaceName: typebot.workspace?.name ?? '',
|
workspaceName: typebot.workspace?.name ?? '',
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
} from '@typebot.io/lib/api'
|
} from '@typebot.io/lib/api'
|
||||||
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
|
||||||
import { sendWorkspaceMemberInvitationEmail } from '@typebot.io/emails'
|
import { sendWorkspaceMemberInvitationEmail } from '@typebot.io/emails'
|
||||||
import { env } from '@typebot.io/lib'
|
|
||||||
import { isSeatsLimitReached } from '@typebot.io/lib/pricing'
|
import { isSeatsLimitReached } from '@typebot.io/lib/pricing'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const user = await getAuthenticatedUser(req, res)
|
const user = await getAuthenticatedUser(req, res)
|
||||||
@ -52,12 +52,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
userId: existingUser.id,
|
userId: existingUser.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (env('E2E_TEST') !== 'true')
|
if (!env.NEXT_PUBLIC_E2E_TEST)
|
||||||
await sendWorkspaceMemberInvitationEmail({
|
await sendWorkspaceMemberInvitationEmail({
|
||||||
to: data.email,
|
to: data.email,
|
||||||
workspaceName: workspace.name,
|
workspaceName: workspace.name,
|
||||||
guestEmail: data.email,
|
guestEmail: data.email,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
url: `${env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
||||||
hostEmail: user.email ?? '',
|
hostEmail: user.email ?? '',
|
||||||
})
|
})
|
||||||
return res.send({
|
return res.send({
|
||||||
@ -71,12 +71,12 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const invitation = await prisma.workspaceInvitation.create({ data })
|
const invitation = await prisma.workspaceInvitation.create({ data })
|
||||||
if (env('E2E_TEST') !== 'true')
|
if (!env.NEXT_PUBLIC_E2E_TEST)
|
||||||
await sendWorkspaceMemberInvitationEmail({
|
await sendWorkspaceMemberInvitationEmail({
|
||||||
to: data.email,
|
to: data.email,
|
||||||
workspaceName: workspace.name,
|
workspaceName: workspace.name,
|
||||||
guestEmail: data.email,
|
guestEmail: data.email,
|
||||||
url: `${process.env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
url: `${env.NEXTAUTH_URL}/typebots?workspaceId=${workspace.id}`,
|
||||||
hostEmail: user.email ?? '',
|
hostEmail: user.email ?? '',
|
||||||
})
|
})
|
||||||
return res.send({ invitation })
|
return res.send({ invitation })
|
||||||
|
@ -4,6 +4,7 @@ import { isNotDefined } from '@typebot.io/lib'
|
|||||||
import { sign } from 'jsonwebtoken'
|
import { sign } from 'jsonwebtoken'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { authOptions } from './api/auth/[...nextauth]'
|
import { authOptions } from './api/auth/[...nextauth]'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return null
|
return null
|
||||||
@ -28,7 +29,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createSSOToken = (user: User) => {
|
const createSSOToken = (user: User) => {
|
||||||
if (!process.env.SLEEKPLAN_SSO_KEY) return
|
if (!env.SLEEKPLAN_SSO_KEY) return
|
||||||
const userData = {
|
const userData = {
|
||||||
mail: user.email,
|
mail: user.email,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
@ -36,5 +37,5 @@ const createSSOToken = (user: User) => {
|
|||||||
img: user.image,
|
img: user.image,
|
||||||
}
|
}
|
||||||
|
|
||||||
return sign(userData, process.env.SLEEKPLAN_SSO_KEY, { algorithm: 'HS256' })
|
return sign(userData, env.SLEEKPLAN_SSO_KEY, { algorithm: 'HS256' })
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { isNotDefined } from '@typebot.io/lib'
|
|||||||
import { sign } from 'jsonwebtoken'
|
import { sign } from 'jsonwebtoken'
|
||||||
import { authOptions } from '../api/auth/[...nextauth]'
|
import { authOptions } from '../api/auth/[...nextauth]'
|
||||||
import { GetServerSidePropsContext } from 'next'
|
import { GetServerSidePropsContext } from 'next'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return null
|
return null
|
||||||
@ -29,7 +30,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createSSOToken = (user: User) => {
|
const createSSOToken = (user: User) => {
|
||||||
if (!process.env.SLEEKPLAN_SSO_KEY) return
|
if (!env.SLEEKPLAN_SSO_KEY) return
|
||||||
const userData = {
|
const userData = {
|
||||||
mail: user.email,
|
mail: user.email,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
@ -37,5 +38,5 @@ const createSSOToken = (user: User) => {
|
|||||||
img: user.image,
|
img: user.image,
|
||||||
}
|
}
|
||||||
|
|
||||||
return sign(userData, process.env.SLEEKPLAN_SSO_KEY, { algorithm: 'HS256' })
|
return sign(userData, env.SLEEKPLAN_SSO_KEY, { algorithm: 'HS256' })
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@ import {
|
|||||||
} from '@typebot.io/prisma'
|
} from '@typebot.io/prisma'
|
||||||
import Stripe from 'stripe'
|
import Stripe from 'stripe'
|
||||||
import { proWorkspaceId } from '@typebot.io/lib/playwright/databaseSetup'
|
import { proWorkspaceId } from '@typebot.io/lib/playwright/databaseSetup'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const prisma = new PrismaClient()
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '', {
|
const stripe = new Stripe(env.STRIPE_SECRET_KEY ?? '', {
|
||||||
apiVersion: '2022-11-15',
|
apiVersion: '2022-11-15',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 3
|
|
||||||
---
|
|
||||||
|
|
||||||
import { SponsorButton } from '../../src/js/SponsorButton.jsx'
|
|
||||||
|
|
||||||
# Cloudron
|
|
||||||
|
|
||||||
:::note
|
|
||||||
The easiest way to get started with Typebot is with [the official managed service in the Cloud](https://app.typebot.io). You'll have high availability, backups, security, and maintenance all managed for you by me, Baptiste, Typebot's founder.
|
|
||||||
|
|
||||||
The cloud version can save a substantial amount of developer time and resources. For most sites this ends up being the best value option and the revenue goes to funding the maintenance and further development of Typebot. So you’ll be supporting open source software and getting a great service!
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
You need a server with [Cloudron](https://www.cloudron.io/) installed and a machine with the [Cloudron CLI](https://docs.cloudron.io/packaging/cli/) installed.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### 1. Download the compose file
|
|
||||||
|
|
||||||
On the machine that has the `cloudron` CLI, download the latest `CloudronManifest.json` file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
wget https://raw.githubusercontent.com/baptisteArno/typebot.io/latest/packages/cloudron/CloudronManifest.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Install the app
|
|
||||||
|
|
||||||
Install the app:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cloudron install --image baptistearno/typebot-cloudron:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
You can further configure the app by opening the app File Manager in Cloudron and edit the `env.sh` file.
|
|
||||||
|
|
||||||
There, you can add any environment variable you want, like:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export GITHUB_CLIENT_ID="your_github_client_id"
|
|
||||||
export GITHUB_CLIENT_SECRET="your_github_client_secret"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then restart the app to apply the changes.
|
|
||||||
|
|
||||||
## Update
|
|
||||||
|
|
||||||
To update the app, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cloudron update --app the_name_of_your_app --image baptistearno/typebot-cloudron:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
:::note
|
|
||||||
If you're self-hosting Typebot, [sponsoring me](https://github.com/sponsors/baptisteArno) is a great way to give back to the community and to contribute to the long-term sustainability of the project.
|
|
||||||
|
|
||||||
<SponsorButton />
|
|
||||||
|
|
||||||
Thank you for supporting independent creators of Free Open Source Software!
|
|
||||||
:::
|
|
@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Builder
|
sidebar_position: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
import { SponsorButton } from '../../../src/js/SponsorButton.jsx'
|
import { SponsorButton } from '../../src/js/SponsorButton.jsx'
|
||||||
import { Asterix } from '../../../src/js/Asterix.jsx'
|
import { Asterix } from '../../src/js/Asterix.jsx'
|
||||||
|
|
||||||
# Builder configuration
|
# Configuration
|
||||||
|
|
||||||
Parameters marked with <Asterix/> are required.
|
Parameters marked with <Asterix/> are required.
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ Parameters marked with <Asterix/> are required.
|
|||||||
| DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` |
|
| DEFAULT_WORKSPACE_PLAN | FREE | Default workspace plan on user creation or when a user creates a new workspace. Possible values are `FREE`, `STARTER`, `PRO`, `LIFETIME`, `UNLIMITED`. The default plan for admin user is `UNLIMITED` |
|
||||||
| DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. |
|
| DISABLE_SIGNUP | false | Disable new user sign ups. Invited users are still able to sign up. |
|
||||||
| NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID | | Typebot ID used for the onboarding. Onboarding page is skipped if not provided. |
|
| NEXT_PUBLIC_ONBOARDING_TYPEBOT_ID | | Typebot ID used for the onboarding. Onboarding page is skipped if not provided. |
|
||||||
|
| DEBUG | false | If enabled, the server will print valuable logs to debug config issues. |
|
||||||
|
|
||||||
## Email (Auth, notifications)
|
## Email (Auth, notifications)
|
||||||
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"label": "Configuration",
|
|
||||||
"position": 2
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
sidebar_position: 1
|
|
||||||
title: Overview
|
|
||||||
slug: /self-hosting/configuration
|
|
||||||
---
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
|
|
||||||
:::note
|
|
||||||
The easiest way to get started with Typebot is with [the official managed service in the Cloud](https://app.typebot.io). You'll have high availability, backups, security, and maintenance all managed for you by me, Baptiste, Typebot's founder.
|
|
||||||
|
|
||||||
The cloud version can save a substantial amount of developer time and resources. For most sites this ends up being the best value option and the revenue goes to funding the maintenance and further development of Typebot. So you’ll be supporting open source software and getting a great service!
|
|
||||||
:::
|
|
||||||
|
|
||||||
When running Typebot on your machine, the following configuration parameters can be supplied as environment variables.
|
|
||||||
|
|
||||||
Typebot is composed of 2 main applications:
|
|
||||||
|
|
||||||
- The builder, where you build your typebots
|
|
||||||
- The viewer, where your user answer the typebot
|
|
||||||
|
|
||||||
Both apps have their environment to configure properly:
|
|
||||||
|
|
||||||
- [Builder configuration](/self-hosting/configuration/builder)
|
|
||||||
- [Viewer configuration](/self-hosting/configuration/viewer)
|
|
@ -1,105 +0,0 @@
|
|||||||
---
|
|
||||||
title: Viewer
|
|
||||||
---
|
|
||||||
|
|
||||||
import { SponsorButton } from '../../../src/js/SponsorButton.jsx'
|
|
||||||
import { Asterix } from '../../../src/js/Asterix.jsx'
|
|
||||||
|
|
||||||
# Viewer configuration
|
|
||||||
|
|
||||||
Parameters marked with <Asterix/> are required.
|
|
||||||
|
|
||||||
## General
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
| --------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| DATABASE_URL <Asterix/> | | The database URL |
|
|
||||||
| ENCRYPTION_SECRET <Asterix/> | | A 256-bit key used to encrypt sensitive data. It is strongly recommended to [generate](https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx) a new one. The secret should be the same between builder and viewer. |
|
|
||||||
| NEXT_PUBLIC_VIEWER_URL <Asterix/> | | The viewer base URL. Should be the publicly accessible URL (i.e. `https://bot.domain.com`) |
|
|
||||||
| NEXTAUTH_URL <Asterix/> | | The builder base URL. Should be the publicly accessible URL (i.e. `https://typebot.domain.com`) |
|
|
||||||
| DEBUG | false | If enabled, the server will print valuable logs to debug config issues. |
|
|
||||||
|
|
||||||
## Emails (Notifications)
|
|
||||||
|
|
||||||
Used for sending email notifications and authentication
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| SMTP_USERNAME | | SMTP username |
|
|
||||||
| SMTP_PASSWORD | | SMTP password |
|
|
||||||
| SMTP_HOST | | SMTP host. (i.e. `smtp.host.com`) |
|
|
||||||
| SMTP_PORT | 25 | SMTP port |
|
|
||||||
| SMTP_FROM | | From name and email (i.e. `'Typebot Notifications' <notifications@typebot.io>`) |
|
|
||||||
| SMTP_SECURE | false | If true the connection will use TLS when connecting to server. If false (the default) then TLS is used if server supports the STARTTLS extension. In most cases set this value to true if you are connecting to port 465. For port 587 or 25 keep it false |
|
|
||||||
|
|
||||||
## Google (Sheets)
|
|
||||||
|
|
||||||
Used when executing a Google Sheets block. Make sure to set the required scopes (`userinfo.email`, `spreadsheets`, `drive.readonly`) in your console
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
| -------------------- | ------- | --------------------------------------------- |
|
|
||||||
| GOOGLE_CLIENT_ID | | The Client ID from the Google API Console |
|
|
||||||
| GOOGLE_CLIENT_SECRET | | The Client secret from the Google API Console |
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
https://console.developers.google.com/apis/credentials
|
|
||||||
|
|
||||||
The "Authorized redirect URIs" used when creating the credentials must include your full domain and end in the callback path:
|
|
||||||
|
|
||||||
- For production: https://{YOUR_DOMAIN}/api/credentials/google-sheets/callback
|
|
||||||
- For development: http://localhost:3000/api/credentials/google-sheets/callback
|
|
||||||
|
|
||||||
## S3 Storage (File upload input)
|
|
||||||
|
|
||||||
Used for the file upload input. It can be any S3 compatible object storage service (Minio, Digital Oceans Space, AWS S3...)
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
| ------------- | ------- | -------------------------------------------------------------- |
|
|
||||||
| S3_ACCESS_KEY | | S3 access key. Also used to check if upload feature is enabled |
|
|
||||||
| S3_SECRET_KEY | | S3 secret key. |
|
|
||||||
| S3_BUCKET | typebot | Name of the bucket where assets will be uploaded in. |
|
|
||||||
| S3_PORT | | S3 Host port number |
|
|
||||||
| S3_ENDPOINT | | S3 endpoint (i.e. `s3.domain.com`). |
|
|
||||||
| S3_SSL | true | Use SSL when establishing the connection. |
|
|
||||||
| S3_REGION | | S3 region. |
|
|
||||||
|
|
||||||
Note that for AWS S3, your endpoint is usually: `s3.<S3_REGION>.amazonaws.com`
|
|
||||||
|
|
||||||
Your bucket must have the following policy that tells S3 to allow public read when an object is located under the public folder:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Sid": "PublicRead",
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": "*",
|
|
||||||
"Action": "s3:GetObject",
|
|
||||||
"Resource": "arn:aws:s3:::<BUCKET_NAME>/public/*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You also need to configure CORS so that an object can be uploaded from the browser:
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"AllowedHeaders": ["*"],
|
|
||||||
"AllowedMethods": ["PUT", "POST"],
|
|
||||||
"AllowedOrigins": ["*"],
|
|
||||||
"ExposeHeaders": ["ETag"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
:::note
|
|
||||||
If you're self-hosting Typebot, [sponsoring me](https://github.com/sponsors/baptisteArno) is a great way to give back to the community and to contribute to the long-term sustainability of the project.
|
|
||||||
|
|
||||||
<SponsorButton />
|
|
||||||
|
|
||||||
Thank you for supporting independent creators of Free Open Source Software!
|
|
||||||
:::
|
|
@ -73,6 +73,6 @@ Typebot is composed of 2 Next.js applications you need to deploy:
|
|||||||
|
|
||||||
I've written guides on how to deploy Typebot using:
|
I've written guides on how to deploy Typebot using:
|
||||||
|
|
||||||
- [Docker](/self-hosting/docker)
|
- [Docker](/self-hosting/guides/docker)
|
||||||
- [Vercel](/self-hosting/vercel)
|
- [Vercel](/self-hosting/guides/vercel)
|
||||||
- [Manual](/self-hosting/manual)
|
- [Manual](/self-hosting/guides/manual)
|
||||||
|
4
apps/docs/docs/self-hosting/guides/_category_.json
Normal file
4
apps/docs/docs/self-hosting/guides/_category_.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "Guides",
|
||||||
|
"position": 3
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
import { SponsorButton } from '../../src/js/SponsorButton.jsx'
|
import { SponsorButton } from '../../../src/js/SponsorButton.jsx'
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
|
||||||
@ -20,27 +20,23 @@ You need a server with Docker installed. If your server doesn't come with Docker
|
|||||||
|
|
||||||
### 1. Download the compose file
|
### 1. Download the compose file
|
||||||
|
|
||||||
On your server, download the latest `docker-compose.yml` file:
|
On your server, download the latest `docker-compose.yml` and the starter `.env` file:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
wget https://raw.githubusercontent.com/baptisteArno/typebot.io/latest/docker-compose.yml
|
wget https://raw.githubusercontent.com/baptisteArno/typebot.io/latest/docker-compose.yml
|
||||||
|
wget https://raw.githubusercontent.com/baptisteArno/typebot.io/latest/.env.example -O .env
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Add the required configuration
|
### 2. Add the required configuration
|
||||||
|
|
||||||
The compose file has placeholders for the required parameters. To set the parameters you'll first need a random 32-character secret key which will be used to encrypt sensitive data. Here is a simple way to generate one:
|
1. You'll first need a random 32-character secret key which will be used to encrypt sensitive data. Here is a simple way to generate one:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
openssl rand -base64 24 | tr -d '\n' ; echo
|
openssl rand -base64 24 | tr -d '\n' ; echo
|
||||||
```
|
```
|
||||||
|
|
||||||
Now edit `docker-compose.yml` and:
|
2. Fill the `.env` file with your values.
|
||||||
|
3. Configure at least one authentication provider (Email, Google, GitHub, Facebook or GitLab). More info here: [Configuration](https://docs.typebot.io/self-hosting/configuration).
|
||||||
- Replace `<your-encryption-secret>` with the generated secret.
|
|
||||||
- Replace `<your-builder-url>` with the public URL of the builder (i.e. `https://typebot.domain.com:8080`).
|
|
||||||
- Replace `<your-viewer-url>` with the public URL of the viewer (i.e. `https://typebot.domain.com:8081`).
|
|
||||||
- Replace `<your-admin-email>` with the email address of the administrator.
|
|
||||||
- Configure at least one authentication provider (Email, Google, GitHub, Facebook or GitLab). More info here: [Configuration](https://docs.typebot.io/self-hosting/configuration).
|
|
||||||
|
|
||||||
By default the compose file will pull the latest stable Typebot images: `baptistearno/typebot-builder:latest` and `baptistearno/typebot-viewer:latest`. You can decide to replace `latest` with a specific version or with `main` to get the latest modifications. You can find all the existing tags [here](https://hub.docker.com/r/baptistearno/typebot-builder/tags)
|
By default the compose file will pull the latest stable Typebot images: `baptistearno/typebot-builder:latest` and `baptistearno/typebot-viewer:latest`. You can decide to replace `latest` with a specific version or with `main` to get the latest modifications. You can find all the existing tags [here](https://hub.docker.com/r/baptistearno/typebot-builder/tags)
|
||||||
|
|
||||||
@ -52,12 +48,13 @@ Once you've added your configuration to the compose file, you're ready to start
|
|||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
When you run this command it does the following:
|
When you run this command, by default, it does the following:
|
||||||
|
|
||||||
- Create a database
|
- Create a database
|
||||||
- Run the migrations
|
- Run the migrations
|
||||||
- Start the builder on port 8080
|
- Start the builder on port 8080
|
||||||
- Start the viewer on port 8081
|
- Start the viewer on port 8081
|
||||||
|
- All Typebot's data is stored in the `.typebot` folder in the current directory
|
||||||
|
|
||||||
You can now navigate to `http://typebot.domain.com:8080` and see the login screen. Login with the admin email to have access to a Team plan workspace automatically.
|
You can now navigate to `http://typebot.domain.com:8080` and see the login screen. Login with the admin email to have access to a Team plan workspace automatically.
|
||||||
|
|
||||||
@ -67,14 +64,33 @@ Typebot server itself does not perform SSL termination. It only runs on unencryp
|
|||||||
|
|
||||||
Typebot is updated regularly, but it is up to you to apply these updates on your server. By virtue of using Docker, these updates are safe and easy to apply.
|
Typebot is updated regularly, but it is up to you to apply these updates on your server. By virtue of using Docker, these updates are safe and easy to apply.
|
||||||
|
|
||||||
```sh
|
1. Pull the new images:
|
||||||
docker-compose down --remove-orphans
|
|
||||||
docker-compose pull typebot-builder
|
|
||||||
docker-compose pull typebot-viewer
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
The self-hosted version is somewhat of a LTS, only getting the changes after they have been battle tested on the hosted version. If you want features as soon as they are available, consider becoming a hosted customer.
|
```sh
|
||||||
|
docker-compose pull typebot-builder
|
||||||
|
docker-compose pull typebot-viewer
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can pull specific versions:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker-compose pull typebot-builder:1.0.0
|
||||||
|
docker-compose pull typebot-viewer:1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Stop the server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the server (with the new images):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The self-hosted version is somewhat of a LTS, only getting the changes (~ once per month) after they have been battle tested on the cloud version. If you want features as soon as they are available, consider becoming a [cloud user](https://app.typebot.io).
|
||||||
|
|
||||||
## Optional extras
|
## Optional extras
|
||||||
|
|
||||||
@ -99,7 +115,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- caddy-certificates:/data/caddy
|
- {$PWD}/.typebot/caddy-certificates:/data/caddy
|
||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
- '443:443'
|
- '443:443'
|
||||||
@ -118,10 +134,6 @@ services:
|
|||||||
virtual.host: 'bot.domain.com' # change to your domain name
|
virtual.host: 'bot.domain.com' # change to your domain name
|
||||||
virtual.port: '3000'
|
virtual.port: '3000'
|
||||||
virtual.tls-email: 'admin@example.com' # change to your email
|
virtual.tls-email: 'admin@example.com' # change to your email
|
||||||
|
|
||||||
volumes:
|
|
||||||
caddy-certificates:
|
|
||||||
driver: local
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This config requires you to add the following DNS entry:
|
This config requires you to add the following DNS entry:
|
||||||
@ -147,7 +159,7 @@ If you're already running a reverse proxy, the most important things to note are
|
|||||||
|
|
||||||
### SMTP
|
### SMTP
|
||||||
|
|
||||||
I highly recommend using an external SMTP service. There are tons of options out there, including [SendInBlue](https://www.sendinblue.com/), [Mailgun](https://www.mailgun.com/) and [SendGrid](https://sendgrid.com/). It will avoid severe headaches 😅. Then, you will only need to add the required [SMTP configuration variables](/self-hosting/configuration/builder#email-auth-notifications).
|
I highly recommend using an external SMTP service. There are tons of options out there, including [SendInBlue](https://www.sendinblue.com/), [Mailgun](https://www.mailgun.com/) and [SendGrid](https://sendgrid.com/). It will avoid severe headaches 😅. Then, you will only need to add the required [SMTP configuration variables](/self-hosting/configuration#email-auth-notifications).
|
||||||
|
|
||||||
If, however, you don't want to, you can instantiate an SMTP server in the docker-compose file.
|
If, however, you don't want to, you can instantiate an SMTP server in the docker-compose file.
|
||||||
|
|
||||||
@ -157,13 +169,13 @@ services:
|
|||||||
mail:
|
mail:
|
||||||
image: bytemark/smtp
|
image: bytemark/smtp
|
||||||
restart: always
|
restart: always
|
||||||
typebot-builder:
|
```
|
||||||
environment:
|
|
||||||
- SMTP_HOST=mail
|
And add the following variables to your `.env` file:
|
||||||
- NEXT_PUBLIC_SMTP_FROM=notifications@typebot.domain.com # change to your domain name
|
|
||||||
typebot-viewer:
|
```
|
||||||
- SMTP_HOST=mail
|
SMTP_HOST=mail
|
||||||
- NEXT_PUBLIC_SMTP_FROM=notifications@typebot.domain.com # change to your domain name
|
NEXT_PUBLIC_SMTP_FROM=notifications@typebot.domain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
You will probably need to make sure that `typebot.domain.com` has a valid SPF record and that your server IP has a rDNS set up.
|
You will probably need to make sure that `typebot.domain.com` has a valid SPF record and that your server IP has a rDNS set up.
|
||||||
@ -186,7 +198,7 @@ services:
|
|||||||
MINIO_ROOT_USER: minio
|
MINIO_ROOT_USER: minio
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
MINIO_ROOT_PASSWORD: minio123
|
||||||
volumes:
|
volumes:
|
||||||
- s3_data:/data
|
- ${PWD}/.typebot/s3:/data
|
||||||
# This service just makes sure a bucket with the right policies is created
|
# This service just makes sure a bucket with the right policies is created
|
||||||
createbuckets:
|
createbuckets:
|
||||||
image: minio/mc
|
image: minio/mc
|
||||||
@ -200,21 +212,15 @@ services:
|
|||||||
/usr/bin/mc anonymous set public minio/typebot/public;
|
/usr/bin/mc anonymous set public minio/typebot/public;
|
||||||
exit 0;
|
exit 0;
|
||||||
"
|
"
|
||||||
typebot-builder:
|
```
|
||||||
environment:
|
|
||||||
- S3_ACCESS_KEY=minio
|
|
||||||
- S3_SECRET_KEY=minio123
|
|
||||||
- S3_BUCKET=typebot
|
|
||||||
- S3_ENDPOINT=storage.domain.com # change to your domain name
|
|
||||||
typebot-viewer:
|
|
||||||
environment:
|
|
||||||
- S3_ACCESS_KEY=minio
|
|
||||||
- S3_SECRET_KEY=minio123
|
|
||||||
- S3_BUCKET=typebot
|
|
||||||
- S3_ENDPOINT=storage.domain.com # change to your domain name
|
|
||||||
|
|
||||||
volumes:
|
And add the following variables to your `.env` file:
|
||||||
s3_data:
|
|
||||||
|
```
|
||||||
|
S3_ACCESS_KEY=minio
|
||||||
|
S3_SECRET_KEY=minio123
|
||||||
|
S3_BUCKET=typebot
|
||||||
|
S3_ENDPOINT=storage.domain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
This config requires you to add the following DNS entry:
|
This config requires you to add the following DNS entry:
|
||||||
@ -237,7 +243,7 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- caddy-certificates:/data/caddy
|
- {$PWD}/.typebot/caddy-certificates:/data/caddy
|
||||||
ports:
|
ports:
|
||||||
- '80:80'
|
- '80:80'
|
||||||
- '443:443'
|
- '443:443'
|
||||||
@ -248,7 +254,7 @@ services:
|
|||||||
image: postgres:13
|
image: postgres:13
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/postgresql/data
|
- {$PWD}/.typebot/database:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=typebot
|
- POSTGRES_DB=typebot
|
||||||
- POSTGRES_PASSWORD=typebot
|
- POSTGRES_PASSWORD=typebot
|
||||||
@ -264,18 +270,8 @@ services:
|
|||||||
extra_hosts:
|
extra_hosts:
|
||||||
- 'host.docker.internal:host-gateway'
|
- 'host.docker.internal:host-gateway'
|
||||||
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
||||||
environment:
|
env_file:
|
||||||
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
|
- .env
|
||||||
- NEXTAUTH_URL=https://typebot.domain.com
|
|
||||||
- NEXT_PUBLIC_VIEWER_URL=https://bot.domain.com
|
|
||||||
- ENCRYPTION_SECRET=K+Bar660Ofaec7v1jHC25tAn3l2b7c81
|
|
||||||
- ADMIN_EMAIL=baptiste.arnaud95@gmail.com
|
|
||||||
- SMTP_HOST=mail
|
|
||||||
- NEXT_PUBLIC_SMTP_FROM=notifications@typebot.domain.com
|
|
||||||
- S3_ACCESS_KEY=minio
|
|
||||||
- S3_SECRET_KEY=minio123
|
|
||||||
- S3_BUCKET=typebot
|
|
||||||
- S3_ENDPOINT=storage.domain.com
|
|
||||||
typebot-viewer:
|
typebot-viewer:
|
||||||
labels:
|
labels:
|
||||||
virtual.host: 'bot.domain.com' # change to your domain
|
virtual.host: 'bot.domain.com' # change to your domain
|
||||||
@ -284,16 +280,8 @@ services:
|
|||||||
image: baptistearno/typebot-viewer:latest
|
image: baptistearno/typebot-viewer:latest
|
||||||
restart: always
|
restart: always
|
||||||
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
||||||
environment:
|
env_file:
|
||||||
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
|
- .env
|
||||||
- NEXT_PUBLIC_VIEWER_URL=https://bot.domain.com
|
|
||||||
- ENCRYPTION_SECRET=K+Bar660Ofaec7v1jHC25tAn3l2b7c81
|
|
||||||
- SMTP_HOST=mail
|
|
||||||
- NEXT_PUBLIC_SMTP_FROM=notifications@typebot.domain.com
|
|
||||||
- S3_ACCESS_KEY=minio
|
|
||||||
- S3_SECRET_KEY=minio123
|
|
||||||
- S3_BUCKET=typebot
|
|
||||||
- S3_ENDPOINT=storage.domain.com
|
|
||||||
mail:
|
mail:
|
||||||
image: bytemark/smtp
|
image: bytemark/smtp
|
||||||
restart: always
|
restart: always
|
||||||
@ -310,7 +298,7 @@ services:
|
|||||||
MINIO_ROOT_USER: minio
|
MINIO_ROOT_USER: minio
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
MINIO_ROOT_PASSWORD: minio123
|
||||||
volumes:
|
volumes:
|
||||||
- s3_data:/data
|
- {$PWD}/.typebot/s3:/data
|
||||||
# This service just make sure a bucket with the right policies is created
|
# This service just make sure a bucket with the right policies is created
|
||||||
createbuckets:
|
createbuckets:
|
||||||
image: minio/mc
|
image: minio/mc
|
||||||
@ -324,11 +312,6 @@ services:
|
|||||||
/usr/bin/mc anonymous set public minio/typebot/public;
|
/usr/bin/mc anonymous set public minio/typebot/public;
|
||||||
exit 0;
|
exit 0;
|
||||||
"
|
"
|
||||||
volumes:
|
|
||||||
db_data:
|
|
||||||
s3_data:
|
|
||||||
caddy-certificates:
|
|
||||||
driver: local
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
@ -18,22 +18,18 @@ The cloud version can save a substantial amount of developer time and resources.
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
1. Fork/clone the repository
|
1. Fork/clone the repository and checkout the latest stable version.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone git@github.com:<username>/typebot.io.git
|
git clone git@github.com:<username>/typebot.io.git
|
||||||
|
cd typebot.io
|
||||||
|
git checkout latest
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Setup environment variables by copying the example files and following the [configuration guide](/self-hosting/configuration) to fill in the missing values.
|
2. Setup environment variables by copying the example files and following the [configuration guide](/self-hosting/configuration) to fill in the missing values.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd typebot.io
|
cp .env.example .env
|
||||||
# check out the latest stable version or the one you want to use
|
|
||||||
git checkout latest
|
|
||||||
# copy the example env file
|
|
||||||
cp packages/prisma/.env.example packages/prisma/.env
|
|
||||||
cp apps/builder/.env.local.example apps/builder/.env.local
|
|
||||||
cp apps/viewer/.env.local.example apps/viewer/.env.local
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
@ -50,9 +46,6 @@ pnpm install
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm run build:apps
|
pnpm run build:apps
|
||||||
# or build them separately
|
|
||||||
pnpm run build:builder
|
|
||||||
pnpm run build:viewer
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
@ -1,10 +1,10 @@
|
|||||||
# Using a Planetscale database
|
# Using a Planetscale database
|
||||||
|
|
||||||
Typebot is also pluggable to a Planetscale database. But it means, you'll need to push schema changes manually, yourself.
|
Typebot is also pluggable to a Planetscale database. But it means, you'll need to push schema changes manually.
|
||||||
|
|
||||||
To do so, follow these instructions:
|
To do so, follow these instructions:
|
||||||
|
|
||||||
1. Copy `packages/prisma/.env.example` to `packages/prisma/.env` and replace `DATABASE_URL` with a development branch
|
1. Replace `DATABASE_URL` with a Planetscale development branch URL.
|
||||||
2. From the `packages/prisma` directory, run a the db push command: `pnpm run db:push`
|
2. From the `packages/prisma` directory, run a the db push command: `pnpm run db:push`
|
||||||
3. Then, in Planetscale dashboard, or using their CLI, you can create a new deploy request from this development branch to your production branch.
|
3. Then, in Planetscale dashboard, or using their CLI, you can create a new deploy request from this development branch to your production branch.
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
sidebar_position: 2
|
sidebar_position: 4
|
||||||
---
|
---
|
||||||
|
|
||||||
# Vercel
|
# Vercel
|
||||||
@ -27,7 +27,7 @@ Fork the repository
|
|||||||
5. Change the build command to:
|
5. Change the build command to:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd ../.. && pnpm build:builder && pnpm db:migrate
|
cd ../.. && pnpm turbo build --filter=builder... && pnpm db:migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Add the required environment variables ([Check out the configuration guide](/self-hosting/configuration))
|
6. Add the required environment variables ([Check out the configuration guide](/self-hosting/configuration))
|
||||||
@ -42,7 +42,7 @@ Fork the repository
|
|||||||
5. Change the build command to:
|
5. Change the build command to:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd ../.. && pnpm build:viewer && pnpm db:migrate
|
cd ../.. && pnpm pnpm turbo build --filter=viewer... && pnpm db:migrate
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Add the required environment variables ([Check out the configuration guide](/self-hosting/configuration))
|
6. Add the required environment variables ([Check out the configuration guide](/self-hosting/configuration))
|
@ -6,4 +6,4 @@ You most likely forgot to set up an `ADMIN_EMAIL` variable or did not signed up
|
|||||||
|
|
||||||
## I can't upload files
|
## I can't upload files
|
||||||
|
|
||||||
You need to add an [S3 configuration](./configuration/builder#s3-storage-media-uploads) to your project. If you are self-hosting with Docker, you can [add a S3 service to your docker-compose file](./docker#s3-storage).
|
You need to add an [S3 configuration](./configuration#s3-storage-media-uploads) to your project. If you are self-hosting with Docker, you can [add a S3 service to your docker-compose file](./guides/docker#s3-storage).
|
||||||
|
@ -2044,6 +2044,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -6326,6 +6329,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -10182,6 +10188,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -14173,6 +14182,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -18045,6 +18057,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -21971,6 +21986,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -25960,6 +25978,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
@ -38,6 +38,15 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5"
|
||||||
|
],
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -1621,6 +1630,9 @@
|
|||||||
},
|
},
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergeResults": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -3817,6 +3829,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
"version",
|
||||||
"id",
|
"id",
|
||||||
"groups",
|
"groups",
|
||||||
"edges",
|
"edges",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"write-translations": "docusaurus write-translations",
|
"write-translations": "docusaurus write-translations",
|
||||||
"write-heading-ids": "docusaurus write-heading-ids",
|
"write-heading-ids": "docusaurus write-heading-ids",
|
||||||
"update-search": "docker run -it --rm --env-file=.env -e \"CONFIG=$(cat docsearch-scrapper-config.json | jq -r tostring)\" algolia/docsearch-scraper",
|
"update-search": "docker run -it --rm --env-file=.env -e \"CONFIG=$(cat docsearch-scrapper-config.json | jq -r tostring)\" algolia/docsearch-scraper",
|
||||||
"api:generate": "tsx --tsconfig ../builder/tsconfig.json ../builder/src/helpers/server/generateOpenApi.ts && tsx --tsconfig ../viewer/openapi.tsconfig.json ../viewer/src/helpers/server/generateOpenApi.ts"
|
"api:generate": "dotenv -e ./.env -e ../../.env -- tsx --tsconfig ../builder/tsconfig.json ../builder/src/helpers/server/generateOpenApi.ts && dotenv -e ./.env -e ../../.env -- tsx --tsconfig ../viewer/openapi.tsconfig.json ../viewer/src/helpers/server/generateOpenApi.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "2.4.1",
|
"@docusaurus/core": "2.4.1",
|
||||||
@ -46,6 +46,7 @@
|
|||||||
"@algolia/client-search": "4.15.0",
|
"@algolia/client-search": "4.15.0",
|
||||||
"@docusaurus/types": "^2.3.1",
|
"@docusaurus/types": "^2.3.1",
|
||||||
"@types/react": "18.0.28",
|
"@types/react": "18.0.28",
|
||||||
|
"dotenv-cli": "^7.2.1",
|
||||||
"tsx": "3.12.5",
|
"tsx": "3.12.5",
|
||||||
"typescript": "4.9.5",
|
"typescript": "4.9.5",
|
||||||
"webpack": "5.76.1"
|
"webpack": "5.76.1"
|
||||||
|
12
apps/docs/vercel.json
Normal file
12
apps/docs/vercel.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "/self-hosting/configuration",
|
||||||
|
"destination": "/self-hosting/configuration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/self-hosting/configuration/:path*",
|
||||||
|
"destination": "/self-hosting/configuration"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,2 +0,0 @@
|
|||||||
# Don't edit this file
|
|
||||||
NEXT_PUBLIC_VIEWER_URL=
|
|
@ -1,2 +0,0 @@
|
|||||||
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
|
|
||||||
LANDING_PAGE_HOST=http://localhost:3002
|
|
@ -2,10 +2,9 @@
|
|||||||
"name": "landing-page",
|
"name": "landing-page",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next dev -p 3002",
|
"dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3002",
|
||||||
"start": "next start",
|
"start": "dotenv -e ./.env -e ../../.env -- next start",
|
||||||
"build": "next build",
|
"build": "dotenv -e ./.env -e ../../.env -- next build",
|
||||||
"build:env": "cd ../.. && cross-env ENVSH_ENV=./apps/landing-page/.env.docker ENVSH_OUTPUT=./apps/landing-page/public/__env.js bash scripts/inject-runtime-env.sh",
|
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"analyze": "cross-env ANALYZE=true next build"
|
"analyze": "cross-env ANALYZE=true next build"
|
||||||
},
|
},
|
||||||
@ -14,33 +13,33 @@
|
|||||||
"@chakra-ui/react": "2.7.1",
|
"@chakra-ui/react": "2.7.1",
|
||||||
"@emotion/react": "11.11.1",
|
"@emotion/react": "11.11.1",
|
||||||
"@emotion/styled": "11.11.0",
|
"@emotion/styled": "11.11.0",
|
||||||
"@vercel/analytics": "1.0.1",
|
"@typebot.io/lib": "workspace:*",
|
||||||
"@typebot.io/nextjs": "workspace:*",
|
"@typebot.io/nextjs": "workspace:*",
|
||||||
"aos": "2.3.4",
|
|
||||||
"@typebot.io/prisma": "workspace:*",
|
"@typebot.io/prisma": "workspace:*",
|
||||||
|
"@typebot.io/schemas": "workspace:*",
|
||||||
|
"aos": "2.3.4",
|
||||||
"focus-visible": "5.2.0",
|
"focus-visible": "5.2.0",
|
||||||
"framer-motion": "10.12.20",
|
"framer-motion": "10.12.20",
|
||||||
"@typebot.io/schemas": "workspace:*",
|
|
||||||
"next": "13.4.3",
|
"next": "13.4.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0"
|
||||||
"@typebot.io/lib": "workspace:*"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.22.9",
|
"@babel/core": "7.22.9",
|
||||||
"@chakra-ui/styled-system": "2.9.1",
|
"@chakra-ui/styled-system": "2.9.1",
|
||||||
"@next/bundle-analyzer": "13.4.9",
|
"@next/bundle-analyzer": "13.4.9",
|
||||||
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
"@types/aos": "3.0.4",
|
"@types/aos": "3.0.4",
|
||||||
"@types/node": "20.4.2",
|
"@types/node": "20.4.2",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
|
"dotenv-cli": "^7.2.1",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.44.0",
|
||||||
"eslint-config-custom": "workspace:*",
|
"eslint-config-custom": "workspace:*",
|
||||||
"next-transpile-modules": "10.0.0",
|
"next-transpile-modules": "10.0.0",
|
||||||
"postcss": "8.4.26",
|
"postcss": "8.4.26",
|
||||||
"prettier": "3.0.0",
|
"prettier": "3.0.0",
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { ChakraProvider } from '@chakra-ui/react'
|
|||||||
import 'focus-visible/dist/focus-visible'
|
import 'focus-visible/dist/focus-visible'
|
||||||
import { theme } from '../lib/chakraTheme'
|
import { theme } from '../lib/chakraTheme'
|
||||||
import { AppProps } from 'next/app'
|
import { AppProps } from 'next/app'
|
||||||
import { Analytics } from '@vercel/analytics/react'
|
|
||||||
import AOS from 'aos'
|
import AOS from 'aos'
|
||||||
import 'aos/dist/aos.css'
|
import 'aos/dist/aos.css'
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
return (
|
return (
|
||||||
<ChakraProvider theme={theme}>
|
<ChakraProvider theme={theme}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
<Analytics />
|
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,6 @@ class MyDocument extends Document {
|
|||||||
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
{/* eslint-disable-next-line @next/next/no-css-tags */}
|
||||||
<link href="./styles/aos-noscript.css" rel="stylesheet" />
|
<link href="./styles/aos-noscript.css" rel="stylesheet" />
|
||||||
</noscript>
|
</noscript>
|
||||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
|
||||||
<script src="/__env.js" />
|
|
||||||
</Head>
|
</Head>
|
||||||
<body style={{ backgroundColor: '#171923' }}>
|
<body style={{ backgroundColor: '#171923' }}>
|
||||||
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
<ColorModeScript initialColorMode={theme.config.initialColorMode} />
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
# Don't edit this file
|
|
||||||
NEXT_PUBLIC_VIEWER_URL=
|
|
||||||
NEXT_PUBLIC_E2E_TEST=
|
|
@ -1,15 +0,0 @@
|
|||||||
# Make sure to change this to your own random string of 32 characters (https://docs.typebot.io/self-hosting/docker#2-add-the-required-configuration)
|
|
||||||
ENCRYPTION_SECRET=H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S
|
|
||||||
NEXT_PUBLIC_VIEWER_URL=http://localhost:3001
|
|
||||||
DATABASE_URL=postgresql://postgres:typebot@localhost:5432/typebot
|
|
||||||
NEXTAUTH_URL=http://localhost:3000
|
|
||||||
|
|
||||||
S3_ACCESS_KEY=minio
|
|
||||||
S3_SECRET_KEY=minio123
|
|
||||||
S3_BUCKET=typebot
|
|
||||||
S3_PORT=9000
|
|
||||||
S3_ENDPOINT=localhost
|
|
||||||
S3_SSL=false
|
|
||||||
|
|
||||||
# For more configuration options check out:
|
|
||||||
# https://docs.typebot.io/self-hosting/configuration
|
|
@ -1 +0,0 @@
|
|||||||
next.config.js
|
|
@ -1,5 +1,14 @@
|
|||||||
const { withSentryConfig } = require('@sentry/nextjs')
|
import { withSentryConfig } from '@sentry/nextjs'
|
||||||
const path = require('path')
|
import { join, dirname } from 'path'
|
||||||
|
import '@typebot.io/env/dist/env.mjs'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { configureRuntimeEnv } from 'next-runtime-env/build/configure.js'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
configureRuntimeEnv()
|
||||||
|
|
||||||
const landingPagePaths = [
|
const landingPagePaths = [
|
||||||
'/',
|
'/',
|
||||||
@ -20,7 +29,7 @@ const nextConfig = {
|
|||||||
],
|
],
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
experimental: {
|
experimental: {
|
||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: join(__dirname, '../../'),
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return {
|
return {
|
||||||
@ -130,7 +139,7 @@ const sentryWebpackPluginOptions = {
|
|||||||
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-viewer',
|
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-viewer',
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
|
export default process.env.NEXT_PUBLIC_SENTRY_DSN
|
||||||
? withSentryConfig(
|
? withSentryConfig(
|
||||||
{
|
{
|
||||||
...nextConfig,
|
...nextConfig,
|
@ -3,22 +3,20 @@
|
|||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next dev -p 3001",
|
"dev": "dotenv -e ./.env -e ../../.env -- next dev -p 3001",
|
||||||
"build": "cross-env ENVSH_ENV=.env.local bash ../../scripts/inject-runtime-env.sh next build",
|
"build": "dotenv -e ./.env -e ../../.env -- next build",
|
||||||
"build:docker": "next build",
|
"start": "dotenv -e ./.env -e ../../.env -- next start",
|
||||||
"build:env": "cd ../.. && cross-env ENVSH_ENV=./apps/viewer/.env.docker ENVSH_OUTPUT=./apps/viewer/public/__env.js bash scripts/inject-runtime-env.sh",
|
"lint": "dotenv -e ./.env -e ../../.env -- next lint",
|
||||||
"start": "next start -p 3001",
|
"test": "dotenv -e ./.env -e ../../.env -- pnpm playwright test",
|
||||||
"lint": "next lint",
|
|
||||||
"test": "pnpm playwright test",
|
|
||||||
"test:report": "pnpm playwright show-report"
|
"test:report": "pnpm playwright show-report"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@planetscale/database": "^1.8.0",
|
"@planetscale/database": "^1.8.0",
|
||||||
"@sentry/nextjs": "7.58.1",
|
"@sentry/nextjs": "7.58.1",
|
||||||
"@trpc/server": "10.34.0",
|
"@trpc/server": "10.34.0",
|
||||||
"@typebot.io/prisma": "workspace:*",
|
|
||||||
"@typebot.io/nextjs": "workspace:*",
|
"@typebot.io/nextjs": "workspace:*",
|
||||||
"ai": "2.2.8",
|
"@typebot.io/prisma": "workspace:*",
|
||||||
|
"ai": "2.1.32",
|
||||||
"aws-sdk": "2.1415.0",
|
"aws-sdk": "2.1415.0",
|
||||||
"bot-engine": "workspace:*",
|
"bot-engine": "workspace:*",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
@ -43,6 +41,7 @@
|
|||||||
"@paralleldrive/cuid2": "2.2.1",
|
"@paralleldrive/cuid2": "2.2.1",
|
||||||
"@playwright/test": "1.36.0",
|
"@playwright/test": "1.36.0",
|
||||||
"@typebot.io/emails": "workspace:*",
|
"@typebot.io/emails": "workspace:*",
|
||||||
|
"@typebot.io/env": "workspace:*",
|
||||||
"@typebot.io/lib": "workspace:*",
|
"@typebot.io/lib": "workspace:*",
|
||||||
"@typebot.io/schemas": "workspace:*",
|
"@typebot.io/schemas": "workspace:*",
|
||||||
"@typebot.io/tsconfig": "workspace:*",
|
"@typebot.io/tsconfig": "workspace:*",
|
||||||
@ -53,7 +52,8 @@
|
|||||||
"@types/qs": "6.9.7",
|
"@types/qs": "6.9.7",
|
||||||
"@types/react": "18.2.15",
|
"@types/react": "18.2.15",
|
||||||
"@types/sanitize-html": "2.9.0",
|
"@types/sanitize-html": "2.9.0",
|
||||||
"dotenv": "16.3.1",
|
"dotenv-cli": "^7.2.1",
|
||||||
|
"next-runtime-env": "^1.6.2",
|
||||||
"eslint": "8.44.0",
|
"eslint": "8.44.0",
|
||||||
"eslint-config-custom": "workspace:*",
|
"eslint-config-custom": "workspace:*",
|
||||||
"google-auth-library": "8.9.0",
|
"google-auth-library": "8.9.0",
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
'ResizeObserver loop limit exceeded',
|
'ResizeObserver loop limit exceeded',
|
||||||
'ResizeObserver loop completed with undelivered notifications.',
|
'ResizeObserver loop completed with undelivered notifications.',
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import * as Sentry from '@sentry/nextjs'
|
import * as Sentry from '@sentry/nextjs'
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-viewer',
|
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA + '-viewer',
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getViewerUrl, isEmpty } from '@typebot.io/lib'
|
import { getViewerUrl } from '@typebot.io/lib'
|
||||||
|
|
||||||
export const ErrorPage = ({ error }: { error: Error }) => {
|
export const ErrorPage = ({ error }: { error: Error }) => {
|
||||||
return (
|
return (
|
||||||
@ -13,7 +13,7 @@ export const ErrorPage = ({ error }: { error: Error }) => {
|
|||||||
padding: '0 1rem',
|
padding: '0 1rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isEmpty(getViewerUrl()) ? (
|
{!getViewerUrl() ? (
|
||||||
<>
|
<>
|
||||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>
|
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>
|
||||||
NEXT_PUBLIC_VIEWER_URL is missing
|
NEXT_PUBLIC_VIEWER_URL is missing
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { byId, isDefined } from '@typebot.io/lib'
|
import { byId, isDefined } from '@typebot.io/lib'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { generatePresignedUrl } from '@typebot.io/lib/api/storage'
|
import { generatePresignedUrl } from '@typebot.io/lib/api/storage'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
export const getUploadUrl = publicProcedure
|
export const getUploadUrl = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@ -39,11 +40,7 @@ export const getUploadUrl = publicProcedure
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(async ({ input: { typebotId, blockId, filePath, fileType } }) => {
|
.query(async ({ input: { typebotId, blockId, filePath, fileType } }) => {
|
||||||
if (
|
if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY)
|
||||||
!process.env.S3_ENDPOINT ||
|
|
||||||
!process.env.S3_ACCESS_KEY ||
|
|
||||||
!process.env.S3_SECRET_KEY
|
|
||||||
)
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
message:
|
message:
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
} from '@typebot.io/lib/playwright/databaseActions'
|
} from '@typebot.io/lib/playwright/databaseActions'
|
||||||
import { getTestAsset } from '@/test/utils/playwright'
|
import { getTestAsset } from '@/test/utils/playwright'
|
||||||
import { Plan } from '@typebot.io/prisma'
|
import { Plan } from '@typebot.io/prisma'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
|
|
||||||
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
const THREE_GIGABYTES = 3 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
await expect(page.locator(`text="3"`)).toBeVisible()
|
await expect(page.locator(`text="3"`)).toBeVisible()
|
||||||
await page.locator('text="Upload 3 files"').click()
|
await page.locator('text="Upload 3 files"').click()
|
||||||
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
|
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
|
||||||
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
await page.goto(`${env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
||||||
await expect(page.getByRole('link', { name: 'api.json' })).toHaveAttribute(
|
await expect(page.getByRole('link', { name: 'api.json' })).toHaveAttribute(
|
||||||
'href',
|
'href',
|
||||||
/.+\/api\.json/
|
/.+\/api\.json/
|
||||||
@ -52,7 +53,7 @@ test('should work as expected', async ({ page, browser }) => {
|
|||||||
const file = readFileSync(downloadPath as string).toString()
|
const file = readFileSync(downloadPath as string).toString()
|
||||||
const { data } = parse(file)
|
const { data } = parse(file)
|
||||||
expect(data).toHaveLength(2)
|
expect(data).toHaveLength(2)
|
||||||
expect((data[1] as unknown[])[1]).toContain(process.env.S3_ENDPOINT)
|
expect((data[1] as unknown[])[1]).toContain(env.S3_ENDPOINT)
|
||||||
|
|
||||||
const urls = (
|
const urls = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@ -110,7 +111,7 @@ test.describe('Storage limit is reached', () => {
|
|||||||
await page.evaluate(() =>
|
await page.evaluate(() =>
|
||||||
window.localStorage.setItem('workspaceId', 'starterWorkspace')
|
window.localStorage.setItem('workspaceId', 'starterWorkspace')
|
||||||
)
|
)
|
||||||
await page.goto(`${process.env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
await page.goto(`${env.NEXTAUTH_URL}/typebots/${typebotId}/results`)
|
||||||
await expect(page.locator('text="150%"')).toBeVisible()
|
await expect(page.locator('text="150%"')).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { ExecuteIntegrationResponse } from '@/features/chat/types'
|
|||||||
import { extractVariablesFromText } from '@/features/variables/extractVariablesFromText'
|
import { extractVariablesFromText } from '@/features/variables/extractVariablesFromText'
|
||||||
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
import { parseGuessedValueType } from '@/features/variables/parseGuessedValueType'
|
||||||
import { parseVariables } from '@/features/variables/parseVariables'
|
import { parseVariables } from '@/features/variables/parseVariables'
|
||||||
|
import { env } from '@typebot.io/env'
|
||||||
import { isDefined } from '@typebot.io/lib'
|
import { isDefined } from '@typebot.io/lib'
|
||||||
import {
|
import {
|
||||||
ChatwootBlock,
|
ChatwootBlock,
|
||||||
@ -30,7 +31,7 @@ const parseChatwootOpenCode = ({
|
|||||||
const openChatwoot = `${parseSetUserCode(user, resultId)}
|
const openChatwoot = `${parseSetUserCode(user, resultId)}
|
||||||
window.$chatwoot.setCustomAttributes({
|
window.$chatwoot.setCustomAttributes({
|
||||||
typebot_result_url: "${
|
typebot_result_url: "${
|
||||||
process.env.NEXTAUTH_URL
|
env.NEXTAUTH_URL
|
||||||
}/typebots/${typebotId}/results?id=${resultId}",
|
}/typebots/${typebotId}/results?id=${resultId}",
|
||||||
});
|
});
|
||||||
window.$chatwoot.toggle("open");
|
window.$chatwoot.toggle("open");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user