build: 👷 New compose file and entrypoints
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,7 +2,6 @@ node_modules
|
|||||||
.next
|
.next
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.production
|
|
||||||
workspace.code-workspace
|
workspace.code-workspace
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.turbo
|
.turbo
|
||||||
@ -18,6 +17,8 @@ test-results
|
|||||||
build
|
build
|
||||||
firebaseServiceAccount.json
|
firebaseServiceAccount.json
|
||||||
|
|
||||||
|
.env.production
|
||||||
|
|
||||||
# Wordpress
|
# Wordpress
|
||||||
.svn
|
.svn
|
||||||
tags
|
tags
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -17,6 +17,7 @@ RUN yarn install --frozen-lockfile
|
|||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
COPY --from=installer /app/ .
|
COPY --from=installer /app/ .
|
||||||
COPY --from=pruner /app/out/full/ .
|
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 apt-get -qy update && apt-get -qy install openssl
|
||||||
RUN yarn turbo run build --scope=${SCOPE} --include-dependencies --no-deps
|
RUN yarn turbo run build --scope=${SCOPE} --include-dependencies --no-deps
|
||||||
RUN find . -name node_modules | xargs rm -rf
|
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}/package.json ./package.json
|
||||||
COPY --from=builder /app/apps/${SCOPE}/.next/standalone ./
|
COPY --from=builder /app/apps/${SCOPE}/.next/standalone ./
|
||||||
COPY --from=builder /app/apps/${SCOPE}/.next/static ./.next/static
|
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
|
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
|
EXPOSE 3000
|
||||||
ENV PORT 3000
|
ENV PORT 3000
|
||||||
CMD ["node", "server.js"]
|
|
13
apps/builder/.env.docker
Normal file
13
apps/builder/.env.docker
Normal 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
|
@ -14,14 +14,15 @@ import { DividerWithText } from './DividerWithText'
|
|||||||
import { SocialLoginButtons } from './SocialLoginButtons'
|
import { SocialLoginButtons } from './SocialLoginButtons'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
const hasNoAuthProvider =
|
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_SMTP_AUTH_DISABLED === 'true') &&
|
||||||
!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID &&
|
isEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) &&
|
||||||
!process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID &&
|
isEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID) &&
|
||||||
!process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID &&
|
isEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) &&
|
||||||
!process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID
|
isEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID)
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
defaultEmail?: string
|
defaultEmail?: string
|
||||||
@ -77,7 +78,7 @@ export const SignInForm = ({
|
|||||||
return (
|
return (
|
||||||
<Stack spacing="4" w="330px">
|
<Stack spacing="4" w="330px">
|
||||||
<SocialLoginButtons />
|
<SocialLoginButtons />
|
||||||
{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_SMTP_AUTH_DISABLED !== 'true' && (
|
||||||
<>
|
<>
|
||||||
<DividerWithText mt="6">Or with your email</DividerWithText>
|
<DividerWithText mt="6">Or with your email</DividerWithText>
|
||||||
|
@ -5,6 +5,7 @@ import { useRouter } from 'next/router'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { FacebookLogo, GoogleLogo, GitlabLogo } from 'assets/logos'
|
import { FacebookLogo, GoogleLogo, GitlabLogo } from 'assets/logos'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
export const SocialLoginButtons = () => {
|
export const SocialLoginButtons = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
@ -32,7 +33,7 @@ export const SocialLoginButtons = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
{process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID && (
|
{!isEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID) && (
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<GithubIcon />}
|
leftIcon={<GithubIcon />}
|
||||||
onClick={handleGitHubClick}
|
onClick={handleGitHubClick}
|
||||||
@ -43,7 +44,7 @@ export const SocialLoginButtons = () => {
|
|||||||
Continue with GitHub
|
Continue with GitHub
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID && (
|
{!isEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) && (
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<GoogleLogo />}
|
leftIcon={<GoogleLogo />}
|
||||||
onClick={handleGoogleClick}
|
onClick={handleGoogleClick}
|
||||||
@ -54,7 +55,7 @@ export const SocialLoginButtons = () => {
|
|||||||
Continue with Google
|
Continue with Google
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID && (
|
{!isEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) && (
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<FacebookLogo />}
|
leftIcon={<FacebookLogo />}
|
||||||
onClick={handleFacebookClick}
|
onClick={handleFacebookClick}
|
||||||
@ -65,7 +66,7 @@ export const SocialLoginButtons = () => {
|
|||||||
Continue with Facebook
|
Continue with Facebook
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID && (
|
{!isEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID) && (
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<GitlabLogo />}
|
leftIcon={<GitlabLogo />}
|
||||||
onClick={handleGitlabClick}
|
onClick={handleGitlabClick}
|
||||||
@ -73,7 +74,10 @@ export const SocialLoginButtons = () => {
|
|||||||
isLoading={['loading', 'authenticated'].includes(status)}
|
isLoading={['loading', 'authenticated'].includes(status)}
|
||||||
variant="outline"
|
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>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -5,6 +5,7 @@ import { BubbleParams } from 'typebot-js'
|
|||||||
import { parseInitBubbleCode, typebotJsHtml } from '../params'
|
import { parseInitBubbleCode, typebotJsHtml } from '../params'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type ChatEmbedCodeProps = {
|
type ChatEmbedCodeProps = {
|
||||||
withStarterVariables?: boolean
|
withStarterVariables?: boolean
|
||||||
@ -20,8 +21,9 @@ export const ChatEmbedCode = ({
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
createSnippet({
|
createSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
button,
|
button,
|
||||||
proactiveMessage,
|
proactiveMessage,
|
||||||
|
@ -5,6 +5,7 @@ import { parseInitContainerCode, typebotJsHtml } from '../params'
|
|||||||
import { IframeParams } from 'typebot-js'
|
import { IframeParams } from 'typebot-js'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type ContainerEmbedCodeProps = {
|
type ContainerEmbedCodeProps = {
|
||||||
widthLabel: string
|
widthLabel: string
|
||||||
@ -22,8 +23,9 @@ export const ContainerEmbedCode = ({
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
parseSnippet({
|
parseSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
heightLabel,
|
heightLabel,
|
||||||
widthLabel,
|
widthLabel,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FlexProps } from '@chakra-ui/react'
|
import { FlexProps } from '@chakra-ui/react'
|
||||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
widthLabel: string
|
widthLabel: string
|
||||||
@ -13,8 +14,9 @@ export const IframeEmbedCode = ({
|
|||||||
}: Props & FlexProps) => {
|
}: Props & FlexProps) => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
const src = `${
|
const src = `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`
|
}/${typebot?.publicId}`
|
||||||
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" />`
|
const code = `<iframe src="${src}" width="${widthLabel}" height="${heightLabel}" />`
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import { useTypebot } from 'contexts/TypebotContext'
|
|||||||
import parserHtml from 'prettier/parser-html'
|
import parserHtml from 'prettier/parser-html'
|
||||||
import prettier from 'prettier/standalone'
|
import prettier from 'prettier/standalone'
|
||||||
import { PopupParams } from 'typebot-js'
|
import { PopupParams } from 'typebot-js'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
import { parseInitPopupCode, typebotJsHtml } from '../params'
|
import { parseInitPopupCode, typebotJsHtml } from '../params'
|
||||||
|
|
||||||
type PopupEmbedCodeProps = {
|
type PopupEmbedCodeProps = {
|
||||||
@ -17,8 +18,9 @@ export const PopupEmbedCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
createSnippet({
|
createSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
delay,
|
delay,
|
||||||
}),
|
}),
|
||||||
|
@ -10,6 +10,7 @@ import parserBabel from 'prettier/parser-babel'
|
|||||||
import prettier from 'prettier/standalone'
|
import prettier from 'prettier/standalone'
|
||||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
|
type StandardReactDivProps = { widthLabel: string; heightLabel: string }
|
||||||
export const StandardReactDiv = ({
|
export const StandardReactDiv = ({
|
||||||
@ -20,8 +21,9 @@ export const StandardReactDiv = ({
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
parseContainerSnippet({
|
parseContainerSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
heightLabel,
|
heightLabel,
|
||||||
widthLabel,
|
widthLabel,
|
||||||
@ -71,8 +73,9 @@ export const PopupReactCode = ({ delay }: PopupEmbedCodeProps & FlexProps) => {
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
parsePopupSnippet({
|
parsePopupSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
delay,
|
delay,
|
||||||
}),
|
}),
|
||||||
@ -121,8 +124,9 @@ export const ChatReactCode = ({
|
|||||||
const snippet = prettier.format(
|
const snippet = prettier.format(
|
||||||
parseBubbleSnippet({
|
parseBubbleSnippet({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${typebot?.publicId}`,
|
}/${typebot?.publicId}`,
|
||||||
button,
|
button,
|
||||||
proactiveMessage,
|
proactiveMessage,
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { createCustomDomain } from 'services/user'
|
import { createCustomDomain } from 'services/user'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
const hostnameRegex =
|
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])$/
|
/^(([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>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text fontWeight="bold">Value</Text>
|
<Text fontWeight="bold">Value</Text>
|
||||||
<Text>{process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
<Text>
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL}</Text>
|
{isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL}
|
||||||
|
</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</HStack>
|
</HStack>
|
||||||
) : (
|
) : (
|
||||||
|
@ -11,6 +11,7 @@ import { PopupEmbedSettings } from 'components/share/codeSnippets/Popup/EmbedSet
|
|||||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { BubbleParams } from 'typebot-js'
|
import { BubbleParams } from 'typebot-js'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
import { ModalProps } from '../../EmbedButton'
|
import { ModalProps } from '../../EmbedButton'
|
||||||
|
|
||||||
type GtmInstructionsProps = {
|
type GtmInstructionsProps = {
|
||||||
@ -40,8 +41,9 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
|
|||||||
|
|
||||||
const jsCode = parseInitContainerCode({
|
const jsCode = parseInitContainerCode({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`,
|
}/${publicId}`,
|
||||||
})
|
})
|
||||||
const headCode = `${typebotJsHtml}
|
const headCode = `${typebotJsHtml}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { CopyButton } from 'components/shared/buttons/CopyButton'
|
import { CopyButton } from 'components/shared/buttons/CopyButton'
|
||||||
import { PublishFirstInfo } from 'components/shared/Info'
|
import { PublishFirstInfo } from 'components/shared/Info'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { ModalProps } from '../EmbedButton'
|
||||||
|
|
||||||
export const NotionModal = ({
|
export const NotionModal = ({
|
||||||
@ -45,15 +46,17 @@ export const NotionModal = ({
|
|||||||
pr="4.5rem"
|
pr="4.5rem"
|
||||||
type={'text'}
|
type={'text'}
|
||||||
defaultValue={`${
|
defaultValue={`${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`}
|
}/${publicId}`}
|
||||||
/>
|
/>
|
||||||
<InputRightElement width="4.5rem">
|
<InputRightElement width="4.5rem">
|
||||||
<CopyButton
|
<CopyButton
|
||||||
textToCopy={`${
|
textToCopy={`${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`}
|
}/${publicId}`}
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
|
@ -14,6 +14,7 @@ import { BubbleParams } from 'typebot-js'
|
|||||||
import { ModalProps } from '../../EmbedButton'
|
import { ModalProps } from '../../EmbedButton'
|
||||||
import parserHtml from 'prettier/parser-html'
|
import parserHtml from 'prettier/parser-html'
|
||||||
import prettier from 'prettier/standalone'
|
import prettier from 'prettier/standalone'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type ShopifyInstructionsProps = {
|
type ShopifyInstructionsProps = {
|
||||||
type: 'standard' | 'popup' | 'bubble'
|
type: 'standard' | 'popup' | 'bubble'
|
||||||
@ -45,8 +46,9 @@ const StandardInstructions = ({ publicId }: Pick<ModalProps, 'publicId'>) => {
|
|||||||
|
|
||||||
const jsCode = parseInitContainerCode({
|
const jsCode = parseInitContainerCode({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`,
|
}/${publicId}`,
|
||||||
})
|
})
|
||||||
const headCode = prettier.format(
|
const headCode = prettier.format(
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
import { ExternalLinkIcon } from 'assets/icons'
|
import { ExternalLinkIcon } from 'assets/icons'
|
||||||
import { CopyButton } from 'components/shared/buttons/CopyButton'
|
import { CopyButton } from 'components/shared/buttons/CopyButton'
|
||||||
import { PublishFirstInfo } from 'components/shared/Info'
|
import { PublishFirstInfo } from 'components/shared/Info'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
import { ModalProps } from '../EmbedButton'
|
import { ModalProps } from '../EmbedButton'
|
||||||
|
|
||||||
export const WordpressModal = ({
|
export const WordpressModal = ({
|
||||||
@ -54,15 +55,17 @@ export const WordpressModal = ({
|
|||||||
pr="4.5rem"
|
pr="4.5rem"
|
||||||
type={'text'}
|
type={'text'}
|
||||||
defaultValue={`${
|
defaultValue={`${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`}
|
}/${publicId}`}
|
||||||
/>
|
/>
|
||||||
<InputRightElement width="4.5rem">
|
<InputRightElement width="4.5rem">
|
||||||
<CopyButton
|
<CopyButton
|
||||||
textToCopy={`${
|
textToCopy={`${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/${publicId}`}
|
}/${publicId}`}
|
||||||
/>
|
/>
|
||||||
</InputRightElement>
|
</InputRightElement>
|
||||||
|
@ -9,6 +9,7 @@ import { useDebouncedCallback } from 'use-debounce'
|
|||||||
import { linter } from '@codemirror/lint'
|
import { linter } from '@codemirror/lint'
|
||||||
import { VariablesButton } from './buttons/VariablesButton'
|
import { VariablesButton } from './buttons/VariablesButton'
|
||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
const linterExtension = linter(jsonParseLinter())
|
const linterExtension = linter(jsonParseLinter())
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ export const CodeEditor = ({
|
|||||||
setPlainTextValue(value)
|
setPlainTextValue(value)
|
||||||
onChange && onChange(value)
|
onChange && onChange(value)
|
||||||
},
|
},
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
@ -16,6 +16,7 @@ import { useTypebot } from 'contexts/TypebotContext'
|
|||||||
import { BaseEmoji, emojiIndex } from 'emoji-mart'
|
import { BaseEmoji, emojiIndex } from 'emoji-mart'
|
||||||
import { emojis } from './emojis'
|
import { emojis } from './emojis'
|
||||||
import { Input } from '../Textbox/Input'
|
import { Input } from '../Textbox/Input'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
@ -182,7 +183,7 @@ const EmojiContent = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GiphyContent = ({ onNewUrl }: ContentProps) => {
|
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 <Text>NEXT_PUBLIC_GIPHY_API_KEY is missing in environment</Text>
|
||||||
return (
|
return (
|
||||||
<SearchContextManager
|
<SearchContextManager
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useState, useRef, useEffect, ChangeEvent } from 'react'
|
import { useState, useRef, useEffect, ChangeEvent } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedItem?: string
|
selectedItem?: string
|
||||||
@ -31,7 +32,7 @@ export const SearchableDropdown = ({
|
|||||||
const debounced = useDebouncedCallback(
|
const debounced = useDebouncedCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
onValueChange ? onValueChange : () => {},
|
onValueChange ? onValueChange : () => {},
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
|
||||||
)
|
)
|
||||||
const [filteredItems, setFilteredItems] = useState([
|
const [filteredItems, setFilteredItems] = useState([
|
||||||
...items
|
...items
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
export const SmartNumberInput = ({
|
export const SmartNumberInput = ({
|
||||||
value,
|
value,
|
||||||
@ -22,7 +23,7 @@ export const SmartNumberInput = ({
|
|||||||
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
|
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
|
||||||
const debounced = useDebouncedCallback(
|
const debounced = useDebouncedCallback(
|
||||||
onValueChange,
|
onValueChange,
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
|
@ -5,6 +5,7 @@ import React, { useEffect, useState } from 'react'
|
|||||||
import { isCloudProdInstance } from 'services/utils'
|
import { isCloudProdInstance } from 'services/utils'
|
||||||
import { planToReadable } from 'services/workspace'
|
import { planToReadable } from 'services/workspace'
|
||||||
import { initBubble } from 'typebot-js'
|
import { initBubble } from 'typebot-js'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
export const SupportBubble = () => {
|
export const SupportBubble = () => {
|
||||||
const { typebot } = useTypebot()
|
const { typebot } = useTypebot()
|
||||||
@ -22,8 +23,9 @@ export const SupportBubble = () => {
|
|||||||
setLocalUserId(user?.id)
|
setLocalUserId(user?.id)
|
||||||
initBubble({
|
initBubble({
|
||||||
url: `${
|
url: `${
|
||||||
process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL ??
|
isEmpty(process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL)
|
||||||
process.env.NEXT_PUBLIC_VIEWER_URL
|
? process.env.NEXT_PUBLIC_VIEWER_URL
|
||||||
|
: process.env.NEXT_PUBLIC_VIEWER_INTERNAL_URL
|
||||||
}/typebot-support`,
|
}/typebot-support`,
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
button: {
|
button: {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
|
import React, { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
import { VariablesButton } from '../buttons/VariablesButton'
|
import { VariablesButton } from '../buttons/VariablesButton'
|
||||||
|
|
||||||
export type TextBoxProps = {
|
export type TextBoxProps = {
|
||||||
@ -35,7 +36,7 @@ export const TextBox = ({
|
|||||||
(value) => {
|
(value) => {
|
||||||
onChange(value)
|
onChange(value)
|
||||||
},
|
},
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? debounceTimeout : 0
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -17,7 +17,7 @@ import cuid from 'cuid'
|
|||||||
import { Variable } from 'models'
|
import { Variable } from 'models'
|
||||||
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
import React, { useState, useRef, ChangeEvent, useEffect } from 'react'
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import { byId, isNotDefined } from 'utils'
|
import { byId, isEmpty, isNotDefined } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialVariableId?: string
|
initialVariableId?: string
|
||||||
@ -47,7 +47,7 @@ export const VariableSearchInput = ({
|
|||||||
const variable = variables.find((v) => v.name === value)
|
const variable = variables.find((v) => v.name === value)
|
||||||
if (variable) onSelectVariable(variable)
|
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[]>(
|
const [filteredItems, setFilteredItems] = useState<Variable[]>(
|
||||||
variables ?? []
|
variables ?? []
|
||||||
|
@ -1,6 +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 { SearchableDropdown } from '../../../shared/SearchableDropdown'
|
import { SearchableDropdown } from '../../../shared/SearchableDropdown'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
type FontSelectorProps = {
|
type FontSelectorProps = {
|
||||||
activeFont?: string
|
activeFont?: string
|
||||||
@ -19,7 +20,7 @@ export const FontSelector = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const fetchPopularFonts = async () => {
|
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(
|
const response = await fetch(
|
||||||
`https://www.googleapis.com/webfonts/v1/webfonts?key=${process.env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity`
|
`https://www.googleapis.com/webfonts/v1/webfonts?key=${process.env.NEXT_PUBLIC_GOOGLE_API_KEY}&sort=popularity`
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
} from 'services/typebots/typebots'
|
} from 'services/typebots/typebots'
|
||||||
import { fetcher, preventUserFromRefreshing } from 'services/utils'
|
import { fetcher, preventUserFromRefreshing } from 'services/utils'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { isDefined, isNotDefined, omit } from 'utils'
|
import { isDefined, isEmpty, isNotDefined, omit } from 'utils'
|
||||||
import { BlocksActions, blocksActions } from './actions/blocks'
|
import { BlocksActions, blocksActions } from './actions/blocks'
|
||||||
import { stepsAction, StepsActions } from './actions/steps'
|
import { stepsAction, StepsActions } from './actions/steps'
|
||||||
import { variablesAction, VariablesActions } from './actions/variables'
|
import { variablesAction, VariablesActions } from './actions/variables'
|
||||||
@ -410,7 +410,7 @@ export const useFetchedTypebot = ({
|
|||||||
},
|
},
|
||||||
Error
|
Error
|
||||||
>(`/api/typebots/${typebotId}`, fetcher, {
|
>(`/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)
|
if (error) onError(error)
|
||||||
return {
|
return {
|
||||||
|
@ -10,10 +10,11 @@ import { NextApiRequest, NextApiResponse } from 'next'
|
|||||||
import { withSentry } from '@sentry/nextjs'
|
import { withSentry } from '@sentry/nextjs'
|
||||||
import { CustomAdapter } from './adapter'
|
import { CustomAdapter } from './adapter'
|
||||||
import { User } from 'db'
|
import { User } from 'db'
|
||||||
|
import { isNotEmpty } from 'utils'
|
||||||
|
|
||||||
const providers: Provider[] = []
|
const providers: Provider[] = []
|
||||||
|
|
||||||
if (process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID)
|
if (isNotEmpty(process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID))
|
||||||
providers.push(
|
providers.push(
|
||||||
GitHubProvider({
|
GitHubProvider({
|
||||||
clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
|
clientId: process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID,
|
||||||
@ -22,7 +23,7 @@ if (process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID)
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NEXT_PUBLIC_SMTP_FROM &&
|
isNotEmpty(process.env.NEXT_PUBLIC_SMTP_FROM) &&
|
||||||
process.env.NEXT_PUBLIC_SMTP_AUTH_DISABLED !== 'true'
|
process.env.NEXT_PUBLIC_SMTP_AUTH_DISABLED !== 'true'
|
||||||
)
|
)
|
||||||
providers.push(
|
providers.push(
|
||||||
@ -40,8 +41,8 @@ if (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID &&
|
isNotEmpty(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) &&
|
||||||
process.env.GOOGLE_CLIENT_SECRET
|
isNotEmpty(process.env.GOOGLE_CLIENT_SECRET)
|
||||||
)
|
)
|
||||||
providers.push(
|
providers.push(
|
||||||
GoogleProvider({
|
GoogleProvider({
|
||||||
@ -51,8 +52,8 @@ if (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID &&
|
isNotEmpty(process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID) &&
|
||||||
process.env.FACEBOOK_CLIENT_SECRET
|
isNotEmpty(process.env.FACEBOOK_CLIENT_SECRET)
|
||||||
)
|
)
|
||||||
providers.push(
|
providers.push(
|
||||||
FacebookProvider({
|
FacebookProvider({
|
||||||
@ -62,8 +63,8 @@ if (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID &&
|
isNotEmpty(process.env.NEXT_PUBLIC_GITLAB_CLIENT_ID) &&
|
||||||
process.env.GITLAB_CLIENT_SECRET
|
isNotEmpty(process.env.GITLAB_CLIENT_SECRET)
|
||||||
) {
|
) {
|
||||||
const BASE_URL = process.env.GITLAB_BASE_URL || 'https://gitlab.com'
|
const BASE_URL = process.env.GITLAB_BASE_URL || 'https://gitlab.com'
|
||||||
providers.push(
|
providers.push(
|
||||||
|
@ -9,6 +9,7 @@ import { getAuthenticatedUser } from 'services/api/utils'
|
|||||||
import {
|
import {
|
||||||
badRequest,
|
badRequest,
|
||||||
forbidden,
|
forbidden,
|
||||||
|
isEmpty,
|
||||||
isNotDefined,
|
isNotDefined,
|
||||||
methodNotAllowed,
|
methodNotAllowed,
|
||||||
notAuthenticated,
|
notAuthenticated,
|
||||||
@ -66,7 +67,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
await prisma.invitation.create({
|
await prisma.invitation.create({
|
||||||
data: { email: email.toLowerCase(), type, typebotId },
|
data: { email: email.toLowerCase(), type, typebotId },
|
||||||
})
|
})
|
||||||
if (isNotDefined(process.env.NEXT_PUBLIC_E2E_TEST))
|
if (isEmpty(process.env.NEXT_PUBLIC_E2E_TEST))
|
||||||
await sendEmailNotification({
|
await sendEmailNotification({
|
||||||
to: email,
|
to: email,
|
||||||
subject: "You've been invited to collaborate 🤝",
|
subject: "You've been invited to collaborate 🤝",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
|
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
|
||||||
|
import { isNotEmpty } from 'utils'
|
||||||
|
|
||||||
const parseWhereFilter = (
|
const parseWhereFilter = (
|
||||||
typebotIds: string[] | string,
|
typebotIds: string[] | string,
|
||||||
@ -19,7 +20,7 @@ const parseWhereFilter = (
|
|||||||
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
||||||
workspace:
|
workspace:
|
||||||
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
|
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST
|
isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
members: {
|
members: {
|
||||||
|
@ -2,7 +2,7 @@ import { DashboardFolder } from 'db'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fetcher } from './utils'
|
import { fetcher } from './utils'
|
||||||
import { stringify } from 'qs'
|
import { stringify } from 'qs'
|
||||||
import { sendRequest } from 'utils'
|
import { isNotEmpty, sendRequest } from 'utils'
|
||||||
|
|
||||||
export const useFolders = ({
|
export const useFolders = ({
|
||||||
parentId,
|
parentId,
|
||||||
@ -17,7 +17,11 @@ export const useFolders = ({
|
|||||||
const { data, error, mutate } = useSWR<{ folders: DashboardFolder[] }, Error>(
|
const { data, error, mutate } = useSWR<{ folders: DashboardFolder[] }, Error>(
|
||||||
workspaceId ? `/api/folders?${params}` : null,
|
workspaceId ? `/api/folders?${params}` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
|
{
|
||||||
|
dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
|
||||||
|
? 0
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
return {
|
return {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { User } from 'db'
|
import { User } from 'db'
|
||||||
import { loadStripe } from '@stripe/stripe-js/pure'
|
import { loadStripe } from '@stripe/stripe-js/pure'
|
||||||
import { sendRequest } from 'utils'
|
import { isEmpty, sendRequest } from 'utils'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: User
|
user: User
|
||||||
@ -10,7 +10,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const pay = async ({ user, currency, plan, workspaceId }: 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')
|
throw new Error('NEXT_PUBLIC_STRIPE_PUBLIC_KEY is missing in env')
|
||||||
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
|
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY)
|
||||||
const { data, error } = await sendRequest<{ sessionId: string }>({
|
const { data, error } = await sendRequest<{ sessionId: string }>({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { CollaborationType, Invitation } from 'db'
|
import { CollaborationType, Invitation } from 'db'
|
||||||
import { fetcher } from 'services/utils'
|
import { fetcher } from 'services/utils'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { sendRequest } from 'utils'
|
import { isNotEmpty, sendRequest } from 'utils'
|
||||||
|
|
||||||
export const useInvitations = ({
|
export const useInvitations = ({
|
||||||
typebotId,
|
typebotId,
|
||||||
@ -13,7 +13,11 @@ export const useInvitations = ({
|
|||||||
const { data, error, mutate } = useSWR<{ invitations: Invitation[] }, Error>(
|
const { data, error, mutate } = useSWR<{ invitations: Invitation[] }, Error>(
|
||||||
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
|
typebotId ? `/api/typebots/${typebotId}/invitations` : null,
|
||||||
fetcher,
|
fetcher,
|
||||||
{ dedupingInterval: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined }
|
{
|
||||||
|
dedupingInterval: isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
|
||||||
|
? 0
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if (error) onError(error)
|
if (error) onError(error)
|
||||||
return {
|
return {
|
||||||
|
@ -44,6 +44,7 @@ import useSWR from 'swr'
|
|||||||
import { fetcher, toKebabCase } from '../utils'
|
import { fetcher, toKebabCase } from '../utils'
|
||||||
import {
|
import {
|
||||||
isBubbleStepType,
|
isBubbleStepType,
|
||||||
|
isNotEmpty,
|
||||||
isWebhookStep,
|
isWebhookStep,
|
||||||
omit,
|
omit,
|
||||||
stepHasItems,
|
stepHasItems,
|
||||||
@ -80,7 +81,9 @@ export const useTypebots = ({
|
|||||||
{ typebots: TypebotInDashboard[] },
|
{ typebots: TypebotInDashboard[] },
|
||||||
Error
|
Error
|
||||||
>(workspaceId ? `/api/typebots?${params}` : null, fetcher, {
|
>(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)
|
if (error) onError(error)
|
||||||
return {
|
return {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { MemberInWorkspace, WorkspaceInvitation } from 'db'
|
import { MemberInWorkspace, WorkspaceInvitation } from 'db'
|
||||||
import { fetcher } from 'services/utils'
|
import { fetcher } from 'services/utils'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { sendRequest } from 'utils'
|
import { isEmpty, sendRequest } from 'utils'
|
||||||
|
|
||||||
export type Member = MemberInWorkspace & {
|
export type Member = MemberInWorkspace & {
|
||||||
name: string | null
|
name: string | null
|
||||||
@ -14,7 +14,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: process.env.NEXT_PUBLIC_E2E_TEST ? 0 : undefined,
|
dedupingInterval: isEmpty(process.env.NEXT_PUBLIC_E2E_TEST) ? undefined : 0,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
members: data?.members,
|
members: data?.members,
|
||||||
|
@ -18,17 +18,13 @@ You need a server with Docker installed. If your server doesn't come with Docker
|
|||||||
|
|
||||||
On your server:
|
On your server:
|
||||||
|
|
||||||
1. Clone the repo:
|
1. Download the latest `docker-compose.yml` file:
|
||||||
|
|
||||||
```sh
|
```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
|
2. Open the file and set the environment variables for both `typebot-builder` and `typebot-viewer`
|
||||||
|
|
||||||
Copy `apps/builder/.env.production.example` to `apps/builder/.env.production`
|
|
||||||
|
|
||||||
Copy `apps/viewer/.env.production.example` to `apps/viewer/.env.production`
|
|
||||||
|
|
||||||
Check out the [Configuration guide](https://docs.typebot.io/self-hosting/configuration) to add your environment variables
|
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 builder on port 8080
|
||||||
- Start the viewer on port 8081
|
- 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
3
apps/viewer/.env.docker
Normal 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
|
@ -1,4 +1,3 @@
|
|||||||
DATABASE_URL=postgresql://postgres:typebot@db:5432/typebot
|
DATABASE_URL=postgresql://postgres:typebot@db:5432/typebot
|
||||||
NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
|
NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
|
||||||
ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
|
ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
|
||||||
ADMIN_EMAIL=contact@baptiste-arnaud.fr
|
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
export const ErrorPage = ({ error }: { error: Error }) => {
|
export const ErrorPage = ({ error }: { error: Error }) => {
|
||||||
return (
|
return (
|
||||||
@ -11,7 +12,7 @@ export const ErrorPage = ({ error }: { error: Error }) => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!process.env.NEXT_PUBLIC_VIEWER_URL ? (
|
{isEmpty(process.env.NEXT_PUBLIC_VIEWER_URL) ? (
|
||||||
<>
|
<>
|
||||||
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>
|
<h1 style={{ fontWeight: 'bold', fontSize: '30px' }}>
|
||||||
NEXT_PUBLIC_VIEWER_URL is missing
|
NEXT_PUBLIC_VIEWER_URL is missing
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
|
import { CollaborationType, Prisma, User, WorkspaceRole } from 'db'
|
||||||
|
import { isNotEmpty } from 'utils'
|
||||||
|
|
||||||
const parseWhereFilter = (
|
const parseWhereFilter = (
|
||||||
typebotIds: string[] | string,
|
typebotIds: string[] | string,
|
||||||
@ -19,7 +20,7 @@ const parseWhereFilter = (
|
|||||||
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
id: typeof typebotIds === 'string' ? typebotIds : { in: typebotIds },
|
||||||
workspace:
|
workspace:
|
||||||
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
|
(type === 'read' && user.email === process.env.ADMIN_EMAIL) ||
|
||||||
process.env.NEXT_PUBLIC_E2E_TEST
|
isNotEmpty(process.env.NEXT_PUBLIC_E2E_TEST)
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
members: {
|
members: {
|
||||||
|
12
builder-entrypoint.sh
Normal file
12
builder-entrypoint.sh
Normal 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
46
docker-compose.build.yml
Normal 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:
|
@ -1,6 +1,6 @@
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
services:
|
services:
|
||||||
db:
|
typebot-db:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
@ -8,42 +8,35 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=typebot
|
- POSTGRES_DB=typebot
|
||||||
- POSTGRES_PASSWORD=typebot
|
- POSTGRES_PASSWORD=typebot
|
||||||
builder:
|
typebot-builder:
|
||||||
depends_on:
|
image: baptistearno/typebot-builder:latest
|
||||||
- db
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
- SCOPE=builder
|
- SCOPE=builder
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
ports:
|
ports:
|
||||||
- '8080:3000'
|
- '8080:3000'
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- 'host.docker.internal:host-gateway'
|
- 'host.docker.internal:host-gateway'
|
||||||
env_file:
|
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
||||||
- './apps/builder/.env.production'
|
environment:
|
||||||
entrypoint: >
|
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
|
||||||
/bin/sh -c "
|
- NEXTAUTH_URL=http://localhost:8080
|
||||||
./node_modules/.bin/prisma generate;
|
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
|
||||||
echo 'Waiting 5s for db to be ready...';
|
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
|
||||||
sleep 5;
|
- ADMIN_EMAIL=me@email.com
|
||||||
./node_modules/.bin/prisma migrate deploy;
|
typebot-viewer:
|
||||||
node server.js;"
|
image: baptistearno/typebot-viewer:latest
|
||||||
viewer:
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
restart: always
|
restart: always
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
args:
|
|
||||||
- SCOPE=viewer
|
|
||||||
ports:
|
ports:
|
||||||
- '8081:3000'
|
- '8081:3000'
|
||||||
env_file:
|
# See https://docs.typebot.io/self-hosting/configuration for more configuration options
|
||||||
- './apps/viewer/.env.production'
|
environment:
|
||||||
entrypoint: >
|
- DATABASE_URL=postgresql://postgres:typebot@typebot-db:5432/typebot
|
||||||
/bin/sh -c "
|
- NEXT_PUBLIC_VIEWER_URL=http://localhost:8081
|
||||||
./node_modules/.bin/prisma generate;
|
- ENCRYPTION_SECRET=SgVkYp2s5v8y/B?E(H+MbQeThWmZq4t6
|
||||||
node server.js;"
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data:
|
db_data:
|
||||||
s3_data:
|
|
||||||
|
35
entrypoint.sh
Normal file
35
entrypoint.sh
Normal 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 "$@"
|
@ -21,6 +21,7 @@ import {
|
|||||||
} from 'models'
|
} from 'models'
|
||||||
import { Log } from 'db'
|
import { Log } from 'db'
|
||||||
import { LiteBadge } from './LiteBadge'
|
import { LiteBadge } from './LiteBadge'
|
||||||
|
import { isEmpty } from 'utils'
|
||||||
|
|
||||||
export type TypebotViewerProps = {
|
export type TypebotViewerProps = {
|
||||||
typebot: PublicTypebot
|
typebot: PublicTypebot
|
||||||
@ -66,7 +67,7 @@ export const TypebotViewer = ({
|
|||||||
|
|
||||||
const handleCompleted = () => onCompleted && onCompleted()
|
const handleCompleted = () => onCompleted && onCompleted()
|
||||||
|
|
||||||
if (!apiHost)
|
if (isEmpty(apiHost))
|
||||||
return <p>process.env.NEXT_PUBLIC_VIEWER_URL is missing in env</p>
|
return <p>process.env.NEXT_PUBLIC_VIEWER_URL is missing in env</p>
|
||||||
return (
|
return (
|
||||||
<Frame
|
<Frame
|
||||||
|
@ -61,6 +61,12 @@ export const isNotDefined = <T>(
|
|||||||
value: T | undefined | null
|
value: T | undefined | null
|
||||||
): value is undefined | null => value === undefined || value === 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 =>
|
export const isInputStep = (step: Step): step is InputStep =>
|
||||||
(Object.values(InputStepType) as string[]).includes(step.type)
|
(Object.values(InputStepType) as string[]).includes(step.type)
|
||||||
|
|
||||||
|
7
viewer-entrypoint.sh
Normal file
7
viewer-entrypoint.sh
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
./entrypoint.sh
|
||||||
|
|
||||||
|
./node_modules/.bin/prisma generate;
|
||||||
|
|
||||||
|
node server.js;
|
Reference in New Issue
Block a user