refactor(♻️ Add defaults everywhere (+ settings page)):
This commit is contained in:
34
packages/bot-engine/.eslintrc.js
Normal file
34
packages/bot-engine/.eslintrc.js
Normal file
@@ -0,0 +1,34 @@
|
||||
module.exports = {
|
||||
ignorePatterns: ['node_modules'],
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react/recommended',
|
||||
'next/core-web-vitals',
|
||||
'prettier',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
|
||||
sourceType: 'module', // Allows for the use of imports
|
||||
ecmaFeatures: {
|
||||
jsx: true, // Allows for the parsing of JSX
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use
|
||||
},
|
||||
},
|
||||
plugins: ['prettier', 'react', 'cypress', '@typescript-eslint'],
|
||||
ignorePatterns: 'dist',
|
||||
rules: {
|
||||
'react/no-unescaped-entities': [0],
|
||||
'prettier/prettier': 'error',
|
||||
'react/display-name': [0],
|
||||
'@next/next/no-img-element': [0],
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GoogleAnalyticsOptions } from 'models'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
declare const gtag: any
|
||||
|
||||
const initGoogleAnalytics = (id: string): Promise<void> =>
|
||||
|
||||
@@ -34,13 +34,20 @@
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"tailwindcss": "^3.0.11",
|
||||
"typescript": "^4.5.4"
|
||||
"typescript": "^4.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
||||
"eslint": "<8.0.0",
|
||||
"eslint-config-next": "12.0.7",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-prettier": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn rollup -c",
|
||||
"dev": "yarn rollup -c --watch"
|
||||
"dev": "yarn rollup -c --watch",
|
||||
"lint": "eslint --fix -c ./.eslintrc.js \"./src/**/*.ts*\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTypebot } from '../../contexts/TypebotContext'
|
||||
import { HostAvatar } from '../avatars/HostAvatar'
|
||||
import { useFrame } from 'react-frame-component'
|
||||
@@ -22,6 +22,7 @@ export const AvatarSideContainer = () => {
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,11 +35,13 @@ export const ChatBlock = ({
|
||||
const nextStep =
|
||||
typebot.steps.byId[startStepId ?? stepIds[displayedSteps.length]]
|
||||
if (nextStep) setDisplayedSteps([...displayedSteps, nextStep])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
autoScrollToBottom()
|
||||
onNewStepDisplayed()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [displayedSteps])
|
||||
|
||||
const onNewStepDisplayed = async () => {
|
||||
|
||||
@@ -42,6 +42,7 @@ const InputChatStep = ({
|
||||
|
||||
useEffect(() => {
|
||||
addNewAvatarOffset()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const handleSubmit = (value: string) => {
|
||||
|
||||
@@ -24,11 +24,12 @@ export const ImageBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
const url = useMemo(
|
||||
() =>
|
||||
parseVariables({ text: step.content?.url, variables: typebot.variables }),
|
||||
[typebot.variables]
|
||||
[step.content?.url, typebot.variables]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
showContentAfterMediaLoad()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const showContentAfterMediaLoad = () => {
|
||||
@@ -83,6 +84,7 @@ export const ImageBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
height: isTyping ? '2rem' : 'auto',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
alt="Bubble image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,7 @@ export const TextBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
const content = useMemo(
|
||||
() =>
|
||||
parseVariables({ text: step.content.html, variables: typebot.variables }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[typebot.variables]
|
||||
)
|
||||
|
||||
@@ -40,6 +41,7 @@ export const TextBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
setTimeout(() => {
|
||||
onTypingEnd()
|
||||
}, typingTimeout)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const onTypingEnd = () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ export const VideoBubble = ({ step, onTransitionEnd }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
showContentAfterMediaLoad()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const showContentAfterMediaLoad = () => {
|
||||
@@ -86,6 +87,7 @@ const VideoContent = ({
|
||||
}) => {
|
||||
const url = useMemo(
|
||||
() => parseVariables({ text: content?.url, variables: variables }),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[variables]
|
||||
)
|
||||
if (!content?.type) return <></>
|
||||
|
||||
@@ -13,6 +13,7 @@ export const ChoiceForm = ({ options, onSubmit }: ChoiceFormProps) => {
|
||||
const { typebot } = useTypebot()
|
||||
const items = useMemo(
|
||||
() => filterTable(options?.itemIds ?? [], typebot.choiceItems),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
)
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
@@ -41,6 +42,7 @@ export const ChoiceForm = ({ options, onSubmit }: ChoiceFormProps) => {
|
||||
<div className="flex flex-wrap">
|
||||
{options?.itemIds.map((itemId) => (
|
||||
<button
|
||||
key={itemId}
|
||||
role={options?.isMultipleChoice ? 'checkbox' : 'button'}
|
||||
onClick={handleClick(itemId)}
|
||||
className={
|
||||
|
||||
@@ -26,7 +26,7 @@ type TextInputProps = {
|
||||
}
|
||||
|
||||
export const TextInput = ({ step, onChange }: TextInputProps) => {
|
||||
const inputRef = useRef<any>(null)
|
||||
const inputRef = useRef<HTMLInputElement & HTMLTextAreaElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) return
|
||||
@@ -102,7 +102,8 @@ export const TextInput = ({ step, onChange }: TextInputProps) => {
|
||||
case InputStepType.PHONE: {
|
||||
return (
|
||||
<PhoneInput
|
||||
ref={inputRef}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ref={inputRef as any}
|
||||
onChange={handlePhoneNumberChange}
|
||||
placeholder={
|
||||
step.options?.labels?.placeholder ?? 'Your phone number...'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useFrame } from 'react-frame-component'
|
||||
import { setCssVariablesValue } from '../services/theme'
|
||||
import { useAnswers } from '../contexts/AnswersContext'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Answer, Block, Edge, PublicTypebot } from 'models'
|
||||
import { Answer, Block, PublicTypebot } from 'models'
|
||||
|
||||
type Props = {
|
||||
typebot: PublicTypebot
|
||||
@@ -45,6 +45,7 @@ export const ConversationContainer = ({
|
||||
typebot.steps.byId[blocks.byId[blocks.allIds[0]].stepIds[0]].edgeId
|
||||
if (!firstEdgeId) return
|
||||
displayNextBlock(firstEdgeId)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -56,6 +57,7 @@ export const ConversationContainer = ({
|
||||
if (!answer || deepEqual(localAnswer, answer)) return
|
||||
setLocalAnswer(answer)
|
||||
onNewAnswer(answer)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [answers])
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import React, { useMemo } from 'react'
|
||||
import { TypebotContext } from '../contexts/TypebotContext'
|
||||
import Frame from 'react-frame-component'
|
||||
@@ -61,7 +62,7 @@ export const TypebotViewer = ({
|
||||
}}
|
||||
/>
|
||||
<TypebotContext typebot={typebot}>
|
||||
<AnswersContext typebotId={typebot.id}>
|
||||
<AnswersContext>
|
||||
<div
|
||||
className="flex text-base overflow-hidden bg-cover h-screen w-screen flex-col items-center typebot-container"
|
||||
style={{
|
||||
@@ -78,6 +79,17 @@ export const TypebotViewer = ({
|
||||
onCompleted={handleCompleted}
|
||||
/>
|
||||
</div>
|
||||
{typebot.settings.general.isBrandingEnabled && (
|
||||
<a
|
||||
href={'https://www.typebot.io/?utm_source=litebadge'}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="fixed py-1 px-2 bg-white z-50 rounded shadow-md"
|
||||
style={{ bottom: '20px' }}
|
||||
>
|
||||
Made with <span className="text-blue-500">Typebot</span>.
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</AnswersContext>
|
||||
</TypebotContext>
|
||||
|
||||
@@ -4,16 +4,11 @@ import React, { createContext, ReactNode, useContext, useState } from 'react'
|
||||
const answersContext = createContext<{
|
||||
answers: Answer[]
|
||||
addAnswer: (answer: Answer) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
export const AnswersContext = ({
|
||||
children,
|
||||
typebotId,
|
||||
}: {
|
||||
children: ReactNode
|
||||
typebotId: string
|
||||
}) => {
|
||||
export const AnswersContext = ({ children }: { children: ReactNode }) => {
|
||||
const [answers, setAnswers] = useState<Answer[]>([])
|
||||
|
||||
const addAnswer = (answer: Answer) =>
|
||||
|
||||
@@ -5,6 +5,7 @@ const hostAvatarsContext = createContext<{
|
||||
lastBubblesTopOffset: number[]
|
||||
addNewAvatarOffset: () => void
|
||||
updateLastAvatarOffset: (newOffset: number) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { createContext, ReactNode, useContext, useState } from 'react'
|
||||
const typebotContext = createContext<{
|
||||
typebot: PublicTypebot
|
||||
updateVariableValue: (variableId: string, value: string) => void
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
}>({})
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TypingEmulationSettings } from 'models'
|
||||
import { TypingEmulation } from 'models'
|
||||
|
||||
export const computeTypingTimeout = (
|
||||
bubbleContent: string,
|
||||
typingSettings: TypingEmulationSettings
|
||||
typingSettings: TypingEmulation
|
||||
) => {
|
||||
const wordCount = bubbleContent.match(/(\w+)/g)?.length ?? 0
|
||||
const typedWordsPerMinute = typingSettings.speed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChoiceInputStep, ChoiceItem, Table, Target } from 'models'
|
||||
import { ChoiceInputStep, ChoiceItem, Table } from 'models'
|
||||
|
||||
export const getSingleChoiceTargetId = (
|
||||
currentStep: ChoiceInputStep,
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
Cell,
|
||||
GoogleSheetsGetOptions,
|
||||
GoogleAnalyticsStep,
|
||||
Webhook,
|
||||
WebhookStep,
|
||||
} from 'models'
|
||||
import { stringify } from 'qs'
|
||||
|
||||
@@ -28,7 +28,7 @@ export const isMathFormula = (str?: string) =>
|
||||
['*', '/', '+', '-'].some((val) => str && str.includes(val))
|
||||
|
||||
export const evaluateExpression = (str: string) => {
|
||||
let result = replaceCommasWithDots(str)
|
||||
const result = replaceCommasWithDots(str)
|
||||
try {
|
||||
const evaluatedNumber = safeEval(result) as number
|
||||
if (countDecimals(evaluatedNumber) > 2) {
|
||||
|
||||
@@ -138,7 +138,7 @@ model Result {
|
||||
typebotId String
|
||||
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
|
||||
answers Answer[]
|
||||
isCompleted Boolean?
|
||||
isCompleted Boolean
|
||||
}
|
||||
|
||||
model Answer {
|
||||
|
||||
@@ -18,6 +18,6 @@ export type PublicTypebot = Omit<
|
||||
choiceItems: Table<ChoiceItem>
|
||||
variables: Table<Variable>
|
||||
edges: Table<Edge>
|
||||
theme?: Theme
|
||||
settings?: Settings
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
export type Settings = {
|
||||
typingEmulation?: TypingEmulationSettings
|
||||
general: GeneralSettings
|
||||
typingEmulation: TypingEmulation
|
||||
metadata: Metadata
|
||||
}
|
||||
|
||||
export type TypingEmulationSettings = {
|
||||
enabled?: boolean
|
||||
speed?: number
|
||||
maxDelay?: number
|
||||
export type GeneralSettings = {
|
||||
isBrandingEnabled: boolean
|
||||
}
|
||||
|
||||
export type TypingEmulation = {
|
||||
enabled: boolean
|
||||
speed: number
|
||||
maxDelay: number
|
||||
}
|
||||
|
||||
export type Metadata = {
|
||||
title?: string
|
||||
description: string
|
||||
imageUrl?: string
|
||||
favIconUrl?: string
|
||||
}
|
||||
|
||||
export const defaultSettings: Settings = {
|
||||
general: { isBrandingEnabled: true },
|
||||
typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 },
|
||||
metadata: {
|
||||
description:
|
||||
'Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form.',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ export type TextBubbleStep = StepBase & {
|
||||
|
||||
export type ImageBubbleStep = StepBase & {
|
||||
type: BubbleStepType.IMAGE
|
||||
content?: ImageBubbleContent
|
||||
content: ImageBubbleContent
|
||||
}
|
||||
|
||||
export type VideoBubbleStep = StepBase & {
|
||||
type: BubbleStepType.VIDEO
|
||||
content?: VideoBubbleContent
|
||||
content: VideoBubbleContent
|
||||
}
|
||||
|
||||
export type TextBubbleContent = {
|
||||
@@ -49,3 +49,13 @@ export type VideoBubbleContent = {
|
||||
url?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
export const defaultTextBubbleContent: TextBubbleContent = {
|
||||
html: '',
|
||||
richText: [],
|
||||
plainText: '',
|
||||
}
|
||||
|
||||
export const defaultImageBubbleContent: ImageBubbleContent = {}
|
||||
|
||||
export const defaultVideoBubbleContent: VideoBubbleContent = {}
|
||||
|
||||
@@ -25,37 +25,41 @@ export type InputStepOptions =
|
||||
| EmailInputOptions
|
||||
| DateInputOptions
|
||||
| UrlInputOptions
|
||||
| PhoneNumberInputOptions
|
||||
| ChoiceInputOptions
|
||||
|
||||
export type TextInputStep = StepBase & {
|
||||
type: InputStepType.TEXT
|
||||
options?: TextInputOptions
|
||||
options: TextInputOptions
|
||||
}
|
||||
|
||||
export type NumberInputStep = StepBase & {
|
||||
type: InputStepType.NUMBER
|
||||
options?: NumberInputOptions
|
||||
options: NumberInputOptions
|
||||
}
|
||||
|
||||
export type EmailInputStep = StepBase & {
|
||||
type: InputStepType.EMAIL
|
||||
options?: EmailInputOptions
|
||||
options: EmailInputOptions
|
||||
}
|
||||
|
||||
export type UrlInputStep = StepBase & {
|
||||
type: InputStepType.URL
|
||||
options?: UrlInputOptions
|
||||
options: UrlInputOptions
|
||||
}
|
||||
|
||||
export type DateInputStep = StepBase & {
|
||||
type: InputStepType.DATE
|
||||
options?: DateInputOptions
|
||||
options: DateInputOptions
|
||||
}
|
||||
|
||||
export type PhoneNumberInputStep = StepBase & {
|
||||
type: InputStepType.PHONE
|
||||
options?: OptionBase & InputTextOptionsBase
|
||||
options: OptionBase & InputTextOptionsBase
|
||||
}
|
||||
|
||||
export type PhoneNumberInputOptions = OptionBase & InputTextOptionsBase
|
||||
|
||||
export type ChoiceInputStep = StepBase & {
|
||||
type: InputStepType.CHOICE
|
||||
options: ChoiceInputOptions
|
||||
@@ -70,19 +74,19 @@ export type ChoiceItem = {
|
||||
|
||||
type OptionBase = { variableId?: string }
|
||||
type InputTextOptionsBase = {
|
||||
labels?: { placeholder?: string; button?: string }
|
||||
labels: { placeholder: string; button: string }
|
||||
}
|
||||
|
||||
export type ChoiceInputOptions = OptionBase & {
|
||||
itemIds: string[]
|
||||
isMultipleChoice?: boolean
|
||||
buttonLabel?: string
|
||||
isMultipleChoice: boolean
|
||||
buttonLabel: string
|
||||
}
|
||||
|
||||
export type DateInputOptions = OptionBase & {
|
||||
labels?: { button?: string; from?: string; to?: string }
|
||||
hasTime?: boolean
|
||||
isRange?: boolean
|
||||
labels: { button: string; from: string; to: string }
|
||||
hasTime: boolean
|
||||
isRange: boolean
|
||||
}
|
||||
|
||||
export type EmailInputOptions = OptionBase & InputTextOptionsBase
|
||||
@@ -91,7 +95,7 @@ export type UrlInputOptions = OptionBase & InputTextOptionsBase
|
||||
|
||||
export type TextInputOptions = OptionBase &
|
||||
InputTextOptionsBase & {
|
||||
isLong?: boolean
|
||||
isLong: boolean
|
||||
}
|
||||
|
||||
export type NumberInputOptions = OptionBase &
|
||||
@@ -100,3 +104,41 @@ export type NumberInputOptions = OptionBase &
|
||||
max?: number
|
||||
step?: number
|
||||
}
|
||||
|
||||
const defaultButtonLabel = 'Send'
|
||||
|
||||
export const defaultTextInputOptions: TextInputOptions = {
|
||||
isLong: false,
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type your answer...' },
|
||||
}
|
||||
|
||||
export const defaultNumberInputOptions: NumberInputOptions = {
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type a number...' },
|
||||
}
|
||||
|
||||
export const defaultEmailInputOptions: EmailInputOptions = {
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type your email...' },
|
||||
}
|
||||
|
||||
export const defaultUrlInputOptions: UrlInputOptions = {
|
||||
labels: { button: defaultButtonLabel, placeholder: 'Type a URL...' },
|
||||
}
|
||||
|
||||
export const defaultDateInputOptions: DateInputOptions = {
|
||||
hasTime: false,
|
||||
isRange: false,
|
||||
labels: { button: defaultButtonLabel, from: 'From:', to: 'To:' },
|
||||
}
|
||||
|
||||
export const defaultPhoneInputOptions: PhoneNumberInputOptions = {
|
||||
labels: {
|
||||
button: defaultButtonLabel,
|
||||
placeholder: 'Type your phone number...',
|
||||
},
|
||||
}
|
||||
|
||||
export const defaultChoiceInputOptions: ChoiceInputOptions = {
|
||||
buttonLabel: defaultButtonLabel,
|
||||
isMultipleChoice: false,
|
||||
itemIds: [],
|
||||
}
|
||||
|
||||
@@ -19,17 +19,17 @@ export enum IntegrationStepType {
|
||||
|
||||
export type GoogleSheetsStep = StepBase & {
|
||||
type: IntegrationStepType.GOOGLE_SHEETS
|
||||
options?: GoogleSheetsOptions
|
||||
options: GoogleSheetsOptions
|
||||
}
|
||||
|
||||
export type GoogleAnalyticsStep = StepBase & {
|
||||
type: IntegrationStepType.GOOGLE_ANALYTICS
|
||||
options?: GoogleAnalyticsOptions
|
||||
options: GoogleAnalyticsOptions
|
||||
}
|
||||
|
||||
export type WebhookStep = StepBase & {
|
||||
type: IntegrationStepType.WEBHOOK
|
||||
options?: WebhookOptions
|
||||
options: WebhookOptions
|
||||
}
|
||||
|
||||
export type GoogleAnalyticsOptions = {
|
||||
@@ -113,3 +113,9 @@ export type WebhookResponse = {
|
||||
statusCode: number
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
export const defaultGoogleSheetsOptions: GoogleSheetsOptions = {}
|
||||
|
||||
export const defaultGoogleAnalyticsOptions: GoogleAnalyticsOptions = {}
|
||||
|
||||
export const defaultWebhookOptions: WebhookOptions = {}
|
||||
|
||||
@@ -16,7 +16,7 @@ export type LogicStepOptions =
|
||||
|
||||
export type SetVariableStep = StepBase & {
|
||||
type: LogicStepType.SET_VARIABLE
|
||||
options?: SetVariableOptions
|
||||
options: SetVariableOptions
|
||||
}
|
||||
|
||||
export type ConditionStep = StepBase & {
|
||||
@@ -28,7 +28,7 @@ export type ConditionStep = StepBase & {
|
||||
|
||||
export type RedirectStep = StepBase & {
|
||||
type: LogicStepType.REDIRECT
|
||||
options?: RedirectOptions
|
||||
options: RedirectOptions
|
||||
}
|
||||
|
||||
export enum LogicalOperator {
|
||||
@@ -47,13 +47,13 @@ export enum ComparisonOperators {
|
||||
|
||||
export type ConditionOptions = {
|
||||
comparisons: Table<Comparison>
|
||||
logicalOperator?: LogicalOperator
|
||||
logicalOperator: LogicalOperator
|
||||
}
|
||||
|
||||
export type Comparison = {
|
||||
id: string
|
||||
variableId?: string
|
||||
comparisonOperator: ComparisonOperators
|
||||
comparisonOperator?: ComparisonOperators
|
||||
value?: string
|
||||
}
|
||||
|
||||
@@ -64,5 +64,14 @@ export type SetVariableOptions = {
|
||||
|
||||
export type RedirectOptions = {
|
||||
url?: string
|
||||
isNewTab?: boolean
|
||||
isNewTab: boolean
|
||||
}
|
||||
|
||||
export const defaultSetVariablesOptions: SetVariableOptions = {}
|
||||
|
||||
export const defaultConditionOptions: ConditionOptions = {
|
||||
comparisons: { byId: {}, allIds: [] },
|
||||
logicalOperator: LogicalOperator.AND,
|
||||
}
|
||||
|
||||
export const defaultRedirectOptions: RedirectOptions = { isNewTab: false }
|
||||
|
||||
@@ -31,6 +31,13 @@ export type DraggableStepType =
|
||||
| LogicStepType
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepWithOptions = InputStep | LogicStep | IntegrationStep
|
||||
|
||||
export type StepWithOptionsType =
|
||||
| InputStepType
|
||||
| LogicStepType
|
||||
| IntegrationStepType
|
||||
|
||||
export type StepOptions =
|
||||
| InputStepOptions
|
||||
| LogicStepOptions
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
export type Theme = {
|
||||
general?: GeneralTheme
|
||||
chat?: ChatTheme
|
||||
general: GeneralTheme
|
||||
chat: ChatTheme
|
||||
customCss?: string
|
||||
}
|
||||
|
||||
export type GeneralTheme = {
|
||||
font?: string
|
||||
background?: Background
|
||||
font: string
|
||||
background: Background
|
||||
}
|
||||
|
||||
export type ChatTheme = {
|
||||
hostBubbles?: ContainerColors
|
||||
guestBubbles?: ContainerColors
|
||||
buttons?: ContainerColors
|
||||
inputs?: InputColors
|
||||
hostBubbles: ContainerColors
|
||||
guestBubbles: ContainerColors
|
||||
buttons: ContainerColors
|
||||
inputs: InputColors
|
||||
}
|
||||
|
||||
export type ContainerColors = {
|
||||
backgroundColor?: string
|
||||
color?: string
|
||||
backgroundColor: string
|
||||
color: string
|
||||
}
|
||||
|
||||
export type InputColors = ContainerColors & {
|
||||
placeholderColor?: string
|
||||
placeholderColor: string
|
||||
}
|
||||
|
||||
export enum BackgroundType {
|
||||
@@ -35,3 +35,17 @@ export type Background = {
|
||||
type: BackgroundType
|
||||
content?: string
|
||||
}
|
||||
|
||||
export const defaultTheme: Theme = {
|
||||
chat: {
|
||||
hostBubbles: { backgroundColor: '#F7F8FF', color: '#303235' },
|
||||
guestBubbles: { backgroundColor: '#FF8E21', color: '#FFFFFF' },
|
||||
buttons: { backgroundColor: '#0042DA', color: '#FFFFFF' },
|
||||
inputs: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
color: '#303235',
|
||||
placeholderColor: '#9095A0',
|
||||
},
|
||||
},
|
||||
general: { font: 'Open Sans', background: { type: BackgroundType.NONE } },
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ export type Typebot = Omit<
|
||||
variables: Table<Variable>
|
||||
edges: Table<Edge>
|
||||
webhooks: Table<Webhook>
|
||||
theme?: Theme
|
||||
settings?: Settings
|
||||
theme: Theme
|
||||
settings: Settings
|
||||
}
|
||||
|
||||
export type Block = {
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
TextInputStep,
|
||||
TextBubbleStep,
|
||||
WebhookStep,
|
||||
StepType,
|
||||
StepWithOptionsType,
|
||||
} from 'models'
|
||||
|
||||
export const sendRequest = async <ResponseData>({
|
||||
@@ -78,3 +80,14 @@ export const isIntegrationStep = (step: Step): step is IntegrationStep =>
|
||||
|
||||
export const isWebhookStep = (step: Step): step is WebhookStep =>
|
||||
step.type === IntegrationStepType.WEBHOOK
|
||||
|
||||
export const isBubbleStepType = (type: StepType): type is BubbleStepType =>
|
||||
(Object.values(BubbleStepType) as string[]).includes(type)
|
||||
|
||||
export const stepTypeHasOption = (
|
||||
type: StepType
|
||||
): type is StepWithOptionsType =>
|
||||
(Object.values(InputStepType) as string[])
|
||||
.concat(Object.values(LogicStepType))
|
||||
.concat(Object.values(IntegrationStepType))
|
||||
.includes(type)
|
||||
|
||||
Reference in New Issue
Block a user