2
0

build: 👷 New compose file and entrypoints

This commit is contained in:
Baptiste Arnaud
2022-05-25 08:13:35 -07:00
parent 4a5a92b973
commit 5d786f59cc
44 changed files with 288 additions and 113 deletions

3
.gitignore vendored
View File

@ -2,7 +2,6 @@ node_modules
.next
.env
.env.local
.env.production
workspace.code-workspace
.DS_Store
.turbo
@ -18,6 +17,8 @@ test-results
build
firebaseServiceAccount.json
.env.production
# Wordpress
.svn
tags

View File

@ -17,6 +17,7 @@ RUN yarn install --frozen-lockfile
FROM base AS builder
COPY --from=installer /app/ .
COPY --from=pruner /app/out/full/ .
COPY ./apps/${SCOPE}/.env.docker ./apps/${SCOPE}/.env.production
RUN apt-get -qy update && apt-get -qy install openssl
RUN yarn turbo run build --scope=${SCOPE} --include-dependencies --no-deps
RUN find . -name node_modules | xargs rm -rf
@ -31,7 +32,14 @@ COPY --from=builder /app/apps/${SCOPE}/public ./public
COPY --from=builder /app/apps/${SCOPE}/package.json ./package.json
COPY --from=builder /app/apps/${SCOPE}/.next/standalone ./
COPY --from=builder /app/apps/${SCOPE}/.next/static ./.next/static
COPY --from=builder /app/apps/${SCOPE}/.env.docker ./.env.production
RUN apt-get -qy update && apt-get -qy install openssl
COPY entrypoint.sh ./
COPY ${SCOPE}-entrypoint.sh ./
RUN chmod +x ./${SCOPE}-entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ./${SCOPE}-entrypoint.sh
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
ENV PORT 3000

13
apps/builder/.env.docker Normal file
View File

@ -0,0 +1,13 @@
# Don't edit this file
NEXT_PUBLIC_VIEWER_URL=DOCKER_PUBLIC_VIEWER_URL
NEXT_PUBLIC_SMTP_FROM=DOCKER_NEXT_PUBLIC_SMTP_FROM
NEXT_PUBLIC_SMTP_AUTH_DISABLED=DOCKER_NEXT_PUBLIC_SMTP_AUTH_DISABLED
NEXT_PUBLIC_GOOGLE_CLIENT_ID=DOCKER_NEXT_PUBLIC_GOOGLE_CLIENT_ID
NEXT_PUBLIC_GOOGLE_API_KEY=DOCKER_NEXT_PUBLIC_GOOGLE_API_KEY
NEXT_PUBLIC_GITHUB_CLIENT_ID=DOCKER_NEXT_PUBLIC_GITHUB_CLIENT_ID
NEXT_PUBLIC_GITLAB_CLIENT_ID=DOCKER_NEXT_PUBLIC_GITLAB_CLIENT_ID
NEXT_PUBLIC_GITLAB_NAME=DOCKER_NEXT_PUBLIC_GITLAB_NAME
NEXT_PUBLIC_FACEBOOK_CLIENT_ID=DOCKER_NEXT_PUBLIC_FACEBOOK_CLIENT_ID
NEXT_PUBLIC_GIPHY_API_KEY=DOCKER_NEXT_PUBLIC_GIPHY_API_KEY
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=DOCKER_NEXT_PUBLIC_STRIPE_PUBLIC_KEY
NEXT_PUBLIC_SENTRY_DSN=DOCKER_NEXT_PUBLIC_SENTRY_DSN

View File

@ -14,14 +14,15 @@ import { DividerWithText } from './DividerWithText'
import { SocialLoginButtons } from './SocialLoginButtons'
import { useRouter } from 'next/router'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { isEmpty } from 'utils'
const hasNoAuthProvider =
(!process.env.NEXT_PUBLIC_SMTP_FROM ||
(isEmpty(process.env.NEXT_PUBLIC_SMTP_FROM) ||
process.env.NEXT_PUBLIC_SMTP_AUTH_DISABLED === 'true') &&
!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID &&
!process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID &&
!process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID &&
!process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID
isEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) &&
isEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID) &&
isEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) &&
isEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID)
type Props = {
defaultEmail?: string
@ -77,7 +78,7 @@ export const SignInForm = ({
return (
<Stack spacing="4" w="330px">
<SocialLoginButtons />
{process.env.NEXT_PUBLIC_SMTP_FROM &&
{!isEmpty(process.env.NEXT_PUBLIC_SMTP_FROM) &&
process.env.NEXT_PUBLIC_SMTP_AUTH_DISABLED !== 'true' && (
<>
<DividerWithText mt="6">Or with your email</DividerWithText>

View File

@ -5,6 +5,7 @@ import { useRouter } from 'next/router'
import React from 'react'
import { stringify } from 'qs'
import { FacebookLogo, GoogleLogo, GitlabLogo } from 'assets/logos'
import { isEmpty } from 'utils'
export const SocialLoginButtons = () => {
const { query } = useRouter()
@ -32,7 +33,7 @@ export const SocialLoginButtons = () => {
return (
<Stack>
{process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID && (
{!isEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID) && (
<Button
leftIcon={<GithubIcon />}
onClick={handleGitHubClick}
@ -43,7 +44,7 @@ export const SocialLoginButtons = () => {
Continue with GitHub
</Button>
)}
{process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID && (
{!isEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) && (
<Button
leftIcon={<GoogleLogo />}
onClick={handleGoogleClick}
@ -54,7 +55,7 @@ export const SocialLoginButtons = () => {
Continue with Google
</Button>
)}
{process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID && (
{!isEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) && (
<Button
leftIcon={<FacebookLogo />}
onClick={handleFacebookClick}
@ -65,7 +66,7 @@ export const SocialLoginButtons = () => {
Continue with Facebook
</Button>
)}
{process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID && (
{!isEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID) && (
<Button
leftIcon={<GitlabLogo />}
onClick={handleGitlabClick}
@ -73,7 +74,10 @@ export const SocialLoginButtons = () => {
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with {process.env.NEXT_PUBLIC_GITLAB_NAME || 'GitLab'}
Continue with{' '}
{isEmpty(process.env.NEXT_PUBLIC_GITLAB_NAME)
? 'GitLab'
: process.env.NEXT_PUBLIC_GITLAB_NAME}
</Button>
)}
</Stack>

View File

@ -5,6 +5,7 @@ import { BubbleParams } from 'typebot-js'
import { parseInitBubbleCode, typebotJsHtml } from '../params'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
import { isEmpty } from 'utils'
type ChatEmbedCodeProps = {
withStarterVariables?: boolean
@ -20,8 +21,9 @@ export const ChatEmbedCode = ({
const snippet = prettier.format(
createSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
button,
proactiveMessage,

View File

@ -5,6 +5,7 @@ import { parseInitContainerCode, typebotJsHtml } from '../params'
import { IframeParams } from 'typebot-js'
import { useTypebot } from 'contexts/TypebotContext'
import { CodeEditor } from 'components/shared/CodeEditor'
import { isEmpty } from 'utils'
type ContainerEmbedCodeProps = {
widthLabel: string
@ -22,8 +23,9 @@ export const ContainerEmbedCode = ({
const snippet = prettier.format(
parseSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
heightLabel,
widthLabel,

View File

@ -1,6 +1,7 @@
import { FlexProps } from '@chakra-ui/react'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import { isEmpty } from 'utils'
type Props = {
widthLabel: string
@ -13,8 +14,9 @@ export const IframeEmbedCode = ({
}: Props & FlexProps) => {
const { typebot } = useTypebot()
const src = `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" />`

View File

@ -4,6 +4,7 @@ import { useTypebot } from 'contexts/TypebotContext'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { PopupParams } from 'typebot-js'
import { isEmpty } from 'utils'
import { parseInitPopupCode, typebotJsHtml } from '../params'
type PopupEmbedCodeProps = {
@ -17,8 +18,9 @@ export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
createSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
delay,
}),

View File

@ -10,6 +10,7 @@ import parserBabel from 'prettier/parser-babel'
import prettier from 'prettier/standalone'
import { CodeEditor } from 'components/shared/CodeEditor'
import { useTypebot } from 'contexts/TypebotContext'
import { isEmpty } from 'utils'
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
export const StandardReactDiv = ({
@ -20,8 +21,9 @@ export const StandardReactDiv = ({
const snippet = prettier.format(
parseContainerSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
heightLabel,
widthLabel,
@ -71,8 +73,9 @@ export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
const snippet = prettier.format(
parsePopupSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
delay,
}),
@ -121,8 +124,9 @@ export const ChatReactCode = ({
const snippet = prettier.format(
parseBubbleSnippet({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${typebot?.publicId}`,
button,
proactiveMessage,

View File

@ -18,6 +18,7 @@ import {
} from '@chakra-ui/react'
import { useEffect, useRef, useState } from 'react'
import { createCustomDomain } from 'services/user'
import { isEmpty } from 'utils'
const hostnameRegex =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/
@ -119,8 +120,11 @@ export const CustomDomainModal = ({
</Stack>
<Stack>
<Text fontWeight="bold">Value</Text>
<Text>{process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL}</Text>
<Text>
{isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL}
</Text>
</Stack>
</HStack>
) : (

View File

@ -11,6 +11,7 @@ import { PopupEmbedSettings } from 'components/share/codeSnippets/Popup/EmbedSet
import { CodeEditor } from 'components/shared/CodeEditor'
import { useState } from 'react'
import { BubbleParams } from 'typebot-js'
import { isEmpty } from 'utils'
import { ModalProps } from '../../EmbedButton'
type GtmInstructionsProps = {
@ -40,8 +41,9 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const jsCode = parseInitContainerCode({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`,
})
const headCode = `${typebotJsHtml}

View File

@ -16,6 +16,7 @@ import {
} from '@chakra-ui/react'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
import { isEmpty } from 'utils'
import { ModalProps } from '../EmbedButton'
export const NotionModal = ({
@ -45,15 +46,17 @@ export const NotionModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`}
/>
</InputRightElement>

View File

@ -14,6 +14,7 @@ import { BubbleParams } from 'typebot-js'
import { ModalProps } from '../../EmbedButton'
import parserHtml from 'prettier/parser-html'
import prettier from 'prettier/standalone'
import { isEmpty } from 'utils'
type ShopifyInstructionsProps = {
type: 'standard' | 'popup' | 'bubble'
@ -45,8 +46,9 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
const jsCode = parseInitContainerCode({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`,
})
const headCode = prettier.format(

View File

@ -17,6 +17,7 @@ import {
import { ExternalLinkIcon } from 'assets/icons'
import { CopyButton } from 'components/shared/buttons/CopyButton'
import { PublishFirstInfo } from 'components/shared/Info'
import { isEmpty } from 'utils'
import { ModalProps } from '../EmbedButton'
export const WordpressModal = ({
@ -54,15 +55,17 @@ export const WordpressModal = ({
pr="4.5rem"
type={'text'}
defaultValue={`${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`}
/>
<InputRightElement width="4.5rem">
<CopyButton
textToCopy={`${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/${publicId}`}
/>
</InputRightElement>

View File

@ -9,6 +9,7 @@ import { useDebouncedCallback } from 'use-debounce'
import { linter } from '@codemirror/lint'
import { VariablesButton } from './buttons/VariablesButton'
import { Variable } from 'models'
import { isEmpty } from 'utils'
const linterExtension = linter(jsonParseLinter())
@ -40,7 +41,7 @@ export const CodeEditor = ({
setPlainTextValue(value)
onChange && onChange(value)
},
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
)
useEffect(

View File

@ -16,6 +16,7 @@ import { useTypebot } from 'contexts/TypebotContext'
import { BaseEmoji, emojiIndex } from 'emoji-mart'
import { emojis } from './emojis'
import { Input } from '../Textbox/Input'
import { isEmpty } from 'utils'
type Props = {
url?: string
@ -182,7 +183,7 @@ const EmojiContent = ({
}
const GiphyContent = ({ onNewUrl }: ContentProps) => {
if (!process.env.NEXT_PUBLIC_GIPHY_API_KEY)
if (isEmpty(process.env.NEXT_PUBLIC_GIPHY_API_KEY))
return <Text>NEXT_PUBLIC_GIPHY_API_KEY is missing in environment</Text>
return (
<SearchContextManager

View File

@ -11,6 +11,7 @@ import {
} from '@chakra-ui/react'
import { useState, useRef, useEffect, ChangeEvent } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { isEmpty } from 'utils'
type Props = {
selectedItem?: string
@ -31,7 +32,7 @@ export const SearchableDropdown = ({
const debounced = useDebouncedCallback(
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValueChange ? onValueChange : () => {},
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
)
const [filteredItems, setFilteredItems] = useState([
...items

View File

@ -8,6 +8,7 @@ import {
} from '@chakra-ui/react'
import { useEffect, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { isEmpty } from 'utils'
export const SmartNumberInput = ({
value,
@ -22,7 +23,7 @@ export const SmartNumberInput = ({
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
const debounced = useDebouncedCallback(
onValueChange,
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
)
useEffect(

View File

@ -5,6 +5,7 @@ import React, { useEffect, useState } from 'react'
import { isCloudProdInstance } from 'services/utils'
import { planToReadable } from 'services/workspace'
import { initBubble } from 'typebot-js'
import { isEmpty } from 'utils'
export const SupportBubble = () => {
const { typebot } = useTypebot()
@ -22,8 +23,9 @@ export const SupportBubble = () => {
setLocalUserId(user?.id)
initBubble({
url: `${
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
process.env.NEXT_PUBLIC_VIEWER_URL
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
? process.env.NEXT_PUBLIC_VIEWER_URL
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
}/typebot-support`,
backgroundColor: '#ffffff',
button: {

View File

@ -7,6 +7,7 @@ import {
import { Variable } from 'models'
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { isEmpty } from 'utils'
import { VariablesButton } from '../buttons/VariablesButton'
export type TextBoxProps = {
@ -35,7 +36,7 @@ export const TextBox = ({
(value) => {
onChange(value)
},
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
)
useEffect(() => {

View File

@ -17,7 +17,7 @@ import cuid from 'cuid'
import { Variable } from 'models'
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { byId, isNotDefined } from 'utils'
import { byId, isEmpty, isNotDefined } from 'utils'
type Props = {
initialVariableId?: string
@ -47,7 +47,7 @@ export const VariableSearchInput = ({
const variable = variables.find((v) => v.name === value)
if (variable) onSelectVariable(variable)
},
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
)
const [filteredItems, setFilteredItems] = useState<Variable[]>(
variables ?? []

View File

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'
import { Text, HStack } from '@chakra-ui/react'
import { SearchableDropdown } from '../../../shared/SearchableDropdown'
import { isEmpty } from 'utils'
type FontSelectorProps = {
activeFont?: string
@ -19,7 +20,7 @@ export const FontSelector = ({
}, [])
const fetchPopularFonts = async () => {
if (!process.env.NEXT_PUBLIC_GOOGLE_API_KEY) return []
if (isEmpty(process.env.NEXT_PUBLIC_GOOGLE_API_KEY)) return []
const response = await fetch(
`https://www.googleapis.com/webfonts/v1/webfonts?key=${process.env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity`
)

View File

@ -30,7 +30,7 @@ import {
} from 'services/typebots/typebots'
import { fetcher, preventUserFromRefreshing } from 'services/utils'
import useSWR from 'swr'
import { isDefined, isNotDefined, omit } from 'utils'
import { isDefined, isEmpty, isNotDefined, omit } from 'utils'
import { BlocksActions, blocksActions } from './actions/blocks'
import { stepsAction, StepsActions } from './actions/steps'
import { variablesAction, VariablesActions } from './actions/variables'
@ -410,7 +410,7 @@ export const useFetchedTypebot = ({
},
Error
>(`/api/typebots/${typebotId}`, fetcher, {
dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? undefined : 0,
})
if (error) onError(error)
return {

View File

@ -10,10 +10,11 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { withSentry } from '@sentry/nextjs'
import { CustomAdapter } from './adapter'
import { User } from 'db'
import { isNotEmpty } from 'utils'
const providers: Provider[] = []
if (process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID)
if (isNotEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID))
providers.push(
GitHubProvider({
clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
@ -22,7 +23,7 @@ if (process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID)
)
if (
process.env.NEXT_PUBLIC_SMTP_FROM &&
isNotEmpty(process.env.NEXT_PUBLIC_SMTP_FROM) &&
process.env.NEXT_PUBLIC_SMTP_AUTH_DISABLED !== 'true'
)
providers.push(
@ -40,8 +41,8 @@ if (
)
if (
process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID &&
process.env.GOOGLE_CLIENT_SECRET
isNotEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) &&
isNotEmpty(process.env.GOOGLE_CLIENT_SECRET)
)
providers.push(
GoogleProvider({
@ -51,8 +52,8 @@ if (
)
if (
process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID &&
process.env.FACEBOOK_CLIENT_SECRET
isNotEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) &&
isNotEmpty(process.env.FACEBOOK_CLIENT_SECRET)
)
providers.push(
FacebookProvider({
@ -62,8 +63,8 @@ if (
)
if (
process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID &&
process.env.GITLAB_CLIENT_SECRET
isNotEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID) &&
isNotEmpty(process.env.GITLAB_CLIENT_SECRET)
) {
const BASE_URL = process.env.GITLAB_BASE_URL || 'https://gitlab.com'
providers.push(

View File

@ -9,6 +9,7 @@ import { getAuthenticatedUser } from 'services/api/utils'
import {
badRequest,
forbidden,
isEmpty,
isNotDefined,
methodNotAllowed,
notAuthenticated,
@ -66,7 +67,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await prisma.invitation.create({
data: { email: email.toLowerCase(), type, typebotId },
})
if (isNotDefined(process.env.NEXT_PUBLIC_E2E_TEST))
if (isEmpty(process.env.NEXT_PUBLIC_E2E_TEST))
await sendEmailNotification({
to: email,
subject: "You've been invited to collaborate 🤝",

View File

@ -1,4 +1,5 @@
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
import { isNotEmpty } from 'utils'
const parseWhereFilter = (
typebotIds: string[] | string,
@ -19,7 +20,7 @@ const parseWhereFilter = (
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
process.env.NEXT_PUBLIC_E2E_TEST
isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
? undefined
: {
members: {

View File

@ -2,7 +2,7 @@ import { DashboardFolder } from 'db'
import useSWR from 'swr'
import { fetcher } from './utils'
import { stringify } from 'qs'
import { sendRequest } from 'utils'
import { isNotEmpty, sendRequest } from 'utils'
export const useFolders = ({
parentId,
@ -17,7 +17,11 @@ export const useFolders = ({
const { data, error, mutate } = useSWR<{ folders: DashboardFolder[] }, Error>(
workspaceId ? `/api/folders?${params}` : null,
fetcher,
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
{
dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
? 0
: undefined,
}
)
if (error) onError(error)
return {

View File

@ -1,6 +1,6 @@
import { User } from 'db'
import { loadStripe } from '@stripe/stripe-js/pure'
import { sendRequest } from 'utils'
import { isEmpty, sendRequest } from 'utils'
type Props = {
user: User
@ -10,7 +10,7 @@ type Props = {
}
export const pay = async ({ user, currency, plan, workspaceId }: Props) => {
if (!process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
if (isEmpty(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY))
throw new Error('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is missing in env')
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
const { data, error } = await sendRequest<{ sessionId: string }>({

View File

@ -1,7 +1,7 @@
import { CollaborationType, Invitation } from 'db'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
import { sendRequest } from 'utils'
import { isNotEmpty, sendRequest } from 'utils'
export const useInvitations = ({
typebotId,
@ -13,7 +13,11 @@ export const useInvitations = ({
const { data, error, mutate } = useSWR<{ invitations: Invitation[] }, Error>(
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
fetcher,
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
{
dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
? 0
: undefined,
}
)
if (error) onError(error)
return {

View File

@ -44,6 +44,7 @@ import useSWR from 'swr'
import { fetcher, toKebabCase } from '../utils'
import {
isBubbleStepType,
isNotEmpty,
isWebhookStep,
omit,
stepHasItems,
@ -80,7 +81,9 @@ export const useTypebots = ({
{ typebots: TypebotInDashboard[] },
Error
>(workspaceId ? `/api/typebots?${params}` : null, fetcher, {
dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
? 0
: undefined,
})
if (error) onError(error)
return {

View File

@ -1,7 +1,7 @@
import { MemberInWorkspace, WorkspaceInvitation } from 'db'
import { fetcher } from 'services/utils'
import useSWR from 'swr'
import { sendRequest } from 'utils'
import { isEmpty, sendRequest } from 'utils'
export type Member = MemberInWorkspace & {
name: string | null
@ -14,7 +14,7 @@ export const useMembers = ({ workspaceId }: { workspaceId?: string }) => {
{ members: Member[]; invitations: WorkspaceInvitation[] },
Error
>(workspaceId ? `/api/workspaces/${workspaceId}/members` : null, fetcher, {
dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? undefined : 0,
})
return {
members: data?.members,

View File

@ -18,17 +18,13 @@ You need a server with Docker installed. If your server doesn't come with Docker
On your server:
1. Clone the repo:
1. Download the latest `docker-compose.yml` file:
```sh
git clone https://github.com/baptistearno/typebot.io.git
wget https://raw.githubusercontent.com/baptisteArno/typebot.io/latest/docker-compose.yml
```
2. Set up environment variables
Copy `apps/builder/.env.production.example` to `apps/builder/.env.production`
Copy `apps/viewer/.env.production.example` to `apps/viewer/.env.production`
2. Open the file and set the environment variables for both `typebot-builder` and `typebot-viewer`
Check out the [Configuration guide](https://docs.typebot.io/self-hosting/configuration) to add your environment variables
@ -45,4 +41,4 @@ On your server:
- Start the builder on port 8080
- Start the viewer on port 8081
You should see the login screen if you navigate to `http://{hostname}:8080`. Login with the `${ADMIN_EMAIL}` to have access to a Pro account automatically.
You should see the login screen if you navigate to `http://{hostname}:8080`. Login with the `${ADMIN_EMAIL}` to have access to a Team plan workspace automatically.

3
apps/viewer/.env.docker Normal file
View File

@ -0,0 +1,3 @@
NEXT_PUBLIC_VIEWER_URL=DOCKER_PUBLIC_VIEWER_URL
NEXT_PUBLIC_SMTP_FROM=DOCKER_NEXT_PUBLIC_SMTP_FROM
NEXT_PUBLIC_GOOGLE_CLIENT_ID=DOCKER_NEXT_PUBLIC_GOOGLE_CLIENT_ID

View File

@ -1,4 +1,3 @@
DATABASE_URL=postgresql://postgres:typebot@db:5432/typebot
NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
ADMIN_EMAIL=contact@baptiste-arnaud.fr
ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6

View File

@ -1,4 +1,5 @@
import React from 'react'
import { isEmpty } from 'utils'
export const ErrorPage = ({ error }: { error: Error }) => {
return (
@ -11,7 +12,7 @@ export const ErrorPage = ({ error }: { error: Error }) => {
flexDirection: 'column',
}}
>
{!process.env.NEXT_PUBLIC_VIEWER_URL ? (
{isEmpty(process.env.NEXT_PUBLIC_VIEWER_URL) ? (
<>
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>
NEXT_PUBLIC_VIEWER_URL is missing

View File

@ -1,4 +1,5 @@
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
import { isNotEmpty } from 'utils'
const parseWhereFilter = (
typebotIds: string[] | string,
@ -19,7 +20,7 @@ const parseWhereFilter = (
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
workspace:
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
process.env.NEXT_PUBLIC_E2E_TEST
isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
? undefined
: {
members: {

12
builder-entrypoint.sh Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
./entrypoint.sh
./node_modules/.bin/prisma generate;
echo 'Waiting 5s for db to be ready...';
sleep 5;
./node_modules/.bin/prisma migrate deploy;
node server.js;

46
docker-compose.build.yml Normal file
View File

@ -0,0 +1,46 @@
version: '3.9'
services:
typebot-db:
image: postgres:13
restart: always
volumes:
- db_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=typebot
- POSTGRES_PASSWORD=typebot
typebot-builder:
build:
context: .
args:
- SCOPE=builder
restart: always
depends_on:
- db
ports:
- '8080:3000'
extra_hosts:
- 'host.docker.internal:host-gateway'
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
environment:
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
- NEXTAUTH_URL=http://localhost:8080
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
- ADMIN_EMAIL=me@email.com
- NEXTAUTH_URL_INTERNAL=http://host.docker.internal:8080
- NEXT_PUBLIC_GOOGLE_CLIENT_ID=fnejwkn
typebot-viewer:
build:
context: .
args:
- SCOPE=viewer
restart: always
ports:
- '8081:3000'
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
environment:
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
volumes:
db_data:

View File

@ -1,6 +1,6 @@
version: '3.9'
services:
db:
typebot-db:
image: postgres:13
restart: always
volumes:
@ -8,42 +8,35 @@ services:
environment:
- POSTGRES_DB=typebot
- POSTGRES_PASSWORD=typebot
builder:
depends_on:
- db
typebot-builder:
image: baptistearno/typebot-builder:latest
build:
context: .
args:
- SCOPE=builder
restart: always
depends_on:
- db
ports:
- '8080:3000'
extra_hosts:
- 'host.docker.internal:host-gateway'
env_file:
- './apps/builder/.env.production'
entrypoint: >
/bin/sh -c "
./node_modules/.bin/prisma generate;
echo 'Waiting 5s for db to be ready...';
sleep 5;
./node_modules/.bin/prisma migrate deploy;
node server.js;"
viewer:
depends_on:
- db
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
environment:
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
- NEXTAUTH_URL=http://localhost:8080
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
- ADMIN_EMAIL=me@email.com
typebot-viewer:
image: baptistearno/typebot-viewer:latest
restart: always
build:
context: .
args:
- SCOPE=viewer
ports:
- '8081:3000'
env_file:
- './apps/viewer/.env.production'
entrypoint: >
/bin/sh -c "
./node_modules/.bin/prisma generate;
node server.js;"
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
environment:
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
volumes:
db_data:
s3_data:

35
entrypoint.sh Normal file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# This entrypoint inject variables at runtime.
# See https://raphaelpralat.medium.com/system-environment-variables-in-next-js-with-docker-1f0754e04cde
# no verbose
set +x
# config
envFilename='.env.production'
nextFolder='./.next/'
function apply_path {
# read all config file
while read line; do
# no comment or not empty
if [ "${line:0:1}" == "#" ] || [ "${line}" == "" ]; then
continue
fi
# split
configName="$(cut -d'=' -f1 <<<"$line")"
configValue="$(cut -d'=' -f2 <<<"$line")" # get system env
envValue=$(env | grep "^$configName=" | grep -oe '[^=]*$');
if [ -n "$configValue" ] && [ -n "$envValue" ]; then
echo "Injecting ${configName}..."
fi
# replace all
find $nextFolder \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#$configValue#$envValue#g"
done < $envFilename
}
apply_path
exec "$@"

View File

@ -21,6 +21,7 @@ import {
} from 'models'
import { Log } from 'db'
import { LiteBadge } from './LiteBadge'
import { isEmpty } from 'utils'
export type TypebotViewerProps = {
typebot: PublicTypebot
@ -66,7 +67,7 @@ export const TypebotViewer = ({
const handleCompleted = () => onCompleted && onCompleted()
if (!apiHost)
if (isEmpty(apiHost))
return <p>process.env.NEXT_PUBLIC_VIEWER_URL is missing in env</p>
return (
<Frame

View File

@ -61,6 +61,12 @@ export const isNotDefined = <T>(
value: T | undefined | null
): value is undefined | null => value === undefined || value === null
export const isEmpty = (value: string | undefined | null): value is undefined =>
value === undefined || value === null || value === ''
export const isNotEmpty = (value: string | undefined | null): value is string =>
value !== undefined && value !== null && value !== ''
export const isInputStep = (step: Step): step is InputStep =>
(Object.values(InputStepType) as string[]).includes(step.type)

7
viewer-entrypoint.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
./entrypoint.sh
./node_modules/.bin/prisma generate;
node server.js;