2
0

feat(engine): Add retry bubbles

This commit is contained in:
Baptiste Arnaud
2022-02-10 10:25:38 +01:00
parent 276f1c1e90
commit 8c8d77e052
15 changed files with 217 additions and 29 deletions

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ yarn-error.log
authenticatedState.json authenticatedState.json
playwright-report playwright-report
dist dist
test-results

View File

@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput' import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput' import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { EmailInputOptions, Variable } from 'models' import { EmailInputOptions, Variable } from 'models'
import React from 'react' import React from 'react'
@@ -19,6 +20,8 @@ export const EmailInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } }) onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) => const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id }) onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
@@ -42,6 +45,16 @@ export const EmailInputSettingsBody = ({
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
</Stack> </Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: Save answer in a variable:

View File

@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput' import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput' import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { EmailInputOptions, Variable } from 'models' import { EmailInputOptions, Variable } from 'models'
import React from 'react' import React from 'react'
@@ -19,6 +20,8 @@ export const PhoneNumberSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } }) onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) => const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id }) onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
@@ -42,6 +45,16 @@ export const PhoneNumberSettingsBody = ({
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
</Stack> </Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: Save answer in a variable:

View File

@@ -1,5 +1,6 @@
import { FormLabel, Stack } from '@chakra-ui/react' import { FormLabel, Stack } from '@chakra-ui/react'
import { DebouncedInput } from 'components/shared/DebouncedInput' import { DebouncedInput } from 'components/shared/DebouncedInput'
import { InputWithVariableButton } from 'components/shared/TextboxWithVariableButton'
import { VariableSearchInput } from 'components/shared/VariableSearchInput' import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { UrlInputOptions, Variable } from 'models' import { UrlInputOptions, Variable } from 'models'
import React from 'react' import React from 'react'
@@ -19,6 +20,8 @@ export const UrlInputSettingsBody = ({
onOptionsChange({ ...options, labels: { ...options.labels, button } }) onOptionsChange({ ...options, labels: { ...options.labels, button } })
const handleVariableChange = (variable?: Variable) => const handleVariableChange = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id }) onOptionsChange({ ...options, variableId: variable?.id })
const handleRetryMessageChange = (retryMessageContent: string) =>
onOptionsChange({ ...options, retryMessageContent })
return ( return (
<Stack spacing={4}> <Stack spacing={4}>
@@ -42,6 +45,16 @@ export const UrlInputSettingsBody = ({
onChange={handleButtonLabelChange} onChange={handleButtonLabelChange}
/> />
</Stack> </Stack>
<Stack>
<FormLabel mb="0" htmlFor="retry">
Retry message:
</FormLabel>
<InputWithVariableButton
id="retry"
initialValue={options.retryMessageContent}
onChange={handleRetryMessageChange}
/>
</Stack>
<Stack> <Stack>
<FormLabel mb="0" htmlFor="variable"> <FormLabel mb="0" htmlFor="variable">
Save answer in a variable: Save answer in a variable:

View File

@@ -57,7 +57,7 @@ const createCredentials = () => {
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod', 'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzqqC47fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
// This token is linked to a mock Google account (typebot.test.user@gmail.com) // This token is linked to a mock Google account (typebot.test.user@gmail.com)
refresh_token: refresh_token:
'1//0379tIHBxszeXCgYIARAAGAMSNwF-L9Ir0zhkzhblwXqn3_jYqRP3pajcUpqkjRU3fKZZ_eQakOa28amUHSQ-Q9fMzk89MpRTvkc', '1//03NRE9V8T-aayCgYIARAAGAMSNwF-L9Ir6zVzF-wm30psz0lbDJj5Y9OgqTO0cvBISODMW4QTR0VK40BLnOQgcHCHkb9c769TAhQ',
}) })
return prisma.credentials.createMany({ return prisma.credentials.createMany({
data: [ data: [

View File

@@ -34,10 +34,25 @@ test.describe('Email input step', () => {
await page.fill('#placeholder', 'Your email...') await page.fill('#placeholder', 'Your email...')
await expect(page.locator('text=Your email...')).toBeVisible() await expect(page.locator('text=Your email...')).toBeVisible()
await page.fill('#button', 'Go') await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultEmailInputOptions.retryMessageContent}"]`,
'Try again bro'
)
await page.click('text=Restart') await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="Your email..."]`)
.fill('test@test')
await typebotViewer(page).locator('text=Go').click()
await expect( await expect(
typebotViewer(page).locator(`input[placeholder="Your email..."]`) typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="Your email..."]`)
.fill('test@test.com')
await typebotViewer(page).locator('text=Go').click()
await expect(
typebotViewer(page).locator('text=test@test.com')
).toBeVisible() ).toBeVisible()
}) })
}) })

View File

@@ -33,16 +33,27 @@ test.describe('Phone input step', () => {
await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`) await page.click(`text=${defaultPhoneInputOptions.labels.placeholder}`)
await page.fill('#placeholder', '+33 XX XX XX XX') await page.fill('#placeholder', '+33 XX XX XX XX')
await page.fill('#button', 'Go') await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultPhoneInputOptions.retryMessageContent}"]`,
'Try again bro'
)
await page.click('text=Restart') await page.click('text=Restart')
await typebotViewer(page) await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`) .locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 18 45 36') .fill('+33 6 73')
await expect(typebotViewer(page).locator(`img`)).toHaveAttribute( await expect(typebotViewer(page).locator(`img`)).toHaveAttribute(
'alt', 'alt',
'France' 'France'
) )
await typebotViewer(page).locator('text="Go"').click() await typebotViewer(page).locator('button >> text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673184536')).toBeVisible() await expect(
typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="+33 XX XX XX XX"]`)
.fill('+33 6 73 54 45 67')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(typebotViewer(page).locator('text=+33673544567')).toBeVisible()
}) })
}) })

View File

@@ -34,10 +34,25 @@ test.describe('Url input step', () => {
await page.fill('#placeholder', 'Your URL...') await page.fill('#placeholder', 'Your URL...')
await expect(page.locator('text=Your URL...')).toBeVisible() await expect(page.locator('text=Your URL...')).toBeVisible()
await page.fill('#button', 'Go') await page.fill('#button', 'Go')
await page.fill(
`input[value="${defaultUrlInputOptions.retryMessageContent}"]`,
'Try again bro'
)
await page.click('text=Restart') await page.click('text=Restart')
await typebotViewer(page)
.locator(`input[placeholder="Your URL..."]`)
.fill('gg://test.com')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect( await expect(
typebotViewer(page).locator(`input[placeholder="Your URL..."]`) typebotViewer(page).locator('text=Try again bro')
).toBeVisible()
await typebotViewer(page)
.locator(`input[placeholder="Your URL..."]`)
.fill('https://website.com')
await typebotViewer(page).locator('button >> text="Go"').click()
await expect(
typebotViewer(page).locator('text=https://website.com')
).toBeVisible() ).toBeVisible()
}) })
}) })

View File

@@ -14,6 +14,7 @@ import {
} from 'utils' } from 'utils'
import { executeLogic } from 'services/logic' import { executeLogic } from 'services/logic'
import { executeIntegration } from 'services/integration' import { executeIntegration } from 'services/integration'
import { parseRetryStep, stepCanBeRetried } from 'services/inputs'
type ChatBlockProps = { type ChatBlockProps = {
steps: PublicStep[] steps: PublicStep[]
@@ -30,7 +31,7 @@ export const ChatBlock = ({
onScroll, onScroll,
onBlockEnd, onBlockEnd,
}: ChatBlockProps) => { }: ChatBlockProps) => {
const { typebot, updateVariableValue } = useTypebot() const { typebot, updateVariableValue, createEdge } = useTypebot()
const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([]) const [displayedSteps, setDisplayedSteps] = useState<PublicStep[]>([])
const currentStepIndex = displayedSteps.length - 1 const currentStepIndex = displayedSteps.length - 1
@@ -70,9 +71,15 @@ export const ChatBlock = ({
} }
} }
const displayNextStep = (answerContent?: string) => { const displayNextStep = (answerContent?: string, isRetry?: boolean) => {
const currentStep = [...displayedSteps].pop() const currentStep = [...displayedSteps].pop()
console.log(currentStep)
if (currentStep) { if (currentStep) {
if (isRetry && stepCanBeRetried(currentStep))
return setDisplayedSteps([
...displayedSteps,
parseRetryStep(currentStep, typebot.variables, createEdge),
])
if ( if (
isInputStep(currentStep) && isInputStep(currentStep) &&
currentStep.options?.variableId && currentStep.options?.variableId &&

View File

@@ -1,26 +1,27 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useAnswers } from '../../../contexts/AnswersContext' import { useAnswers } from '../../../contexts/AnswersContext'
import { useHostAvatars } from '../../../contexts/HostAvatarsContext' import { useHostAvatars } from '../../../contexts/HostAvatarsContext'
import { InputStep, InputStepType, PublicStep, Step } from 'models' import { InputStep, InputStepType, PublicStep } from 'models'
import { GuestBubble } from './bubbles/GuestBubble' import { GuestBubble } from './bubbles/GuestBubble'
import { TextForm } from './inputs/TextForm' import { TextForm } from './inputs/TextForm'
import { isBubbleStep, isInputStep } from 'utils' import { isBubbleStep, isInputStep } from 'utils'
import { DateForm } from './inputs/DateForm' import { DateForm } from './inputs/DateForm'
import { ChoiceForm } from './inputs/ChoiceForm' import { ChoiceForm } from './inputs/ChoiceForm'
import { HostBubble } from './bubbles/HostBubble' import { HostBubble } from './bubbles/HostBubble'
import { isInputValid } from 'services/inputs'
export const ChatStep = ({ export const ChatStep = ({
step, step,
onTransitionEnd, onTransitionEnd,
}: { }: {
step: PublicStep step: PublicStep
onTransitionEnd: (answerContent?: string) => void onTransitionEnd: (answerContent?: string, isRetry?: boolean) => void
}) => { }) => {
const { addAnswer } = useAnswers() const { addAnswer } = useAnswers()
const handleInputSubmit = (content: string) => { const handleInputSubmit = (content: string, isRetry: boolean) => {
addAnswer({ stepId: step.id, blockId: step.blockId, content }) if (!isRetry) addAnswer({ stepId: step.id, blockId: step.blockId, content })
onTransitionEnd(content) onTransitionEnd(content, isRetry)
} }
if (isBubbleStep(step)) if (isBubbleStep(step))
@@ -35,7 +36,7 @@ const InputChatStep = ({
onSubmit, onSubmit,
}: { }: {
step: InputStep step: InputStep
onSubmit: (value: string) => void onSubmit: (value: string, isRetry: boolean) => void
}) => { }) => {
const { addNewAvatarOffset } = useHostAvatars() const { addNewAvatarOffset } = useHostAvatars()
const [answer, setAnswer] = useState<string>() const [answer, setAnswer] = useState<string>()
@@ -47,7 +48,7 @@ const InputChatStep = ({
const handleSubmit = (value: string) => { const handleSubmit = (value: string) => {
setAnswer(value) setAnswer(value)
onSubmit(value) onSubmit(value, !isInputValid(value, step.type))
} }
if (answer) { if (answer) {

View File

@@ -5,22 +5,24 @@ import { useFrame } from 'react-frame-component'
import { setCssVariablesValue } from '../services/theme' import { setCssVariablesValue } from '../services/theme'
import { useAnswers } from '../contexts/AnswersContext' import { useAnswers } from '../contexts/AnswersContext'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import { Answer, Edge, PublicBlock, PublicTypebot } from 'models' import { Answer, Edge, PublicBlock, Theme } from 'models'
import { byId } from 'utils' import { byId } from 'utils'
import { animateScroll as scroll } from 'react-scroll' import { animateScroll as scroll } from 'react-scroll'
import { useTypebot } from 'contexts/TypebotContext'
type Props = { type Props = {
typebot: PublicTypebot theme: Theme
onNewBlockVisible: (edge: Edge) => void onNewBlockVisible: (edge: Edge) => void
onNewAnswer: (answer: Answer) => void onNewAnswer: (answer: Answer) => void
onCompleted: () => void onCompleted: () => void
} }
export const ConversationContainer = ({ export const ConversationContainer = ({
typebot, theme,
onNewBlockVisible, onNewBlockVisible,
onNewAnswer, onNewAnswer,
onCompleted, onCompleted,
}: Props) => { }: Props) => {
const { typebot } = useTypebot()
const { document: frameDocument } = useFrame() const { document: frameDocument } = useFrame()
const [displayedBlocks, setDisplayedBlocks] = useState< const [displayedBlocks, setDisplayedBlocks] = useState<
{ block: PublicBlock; startStepIndex: number }[] { block: PublicBlock; startStepIndex: number }[]
@@ -51,8 +53,8 @@ export const ConversationContainer = ({
}, []) }, [])
useEffect(() => { useEffect(() => {
setCssVariablesValue(typebot.theme, frameDocument.body.style) setCssVariablesValue(theme, frameDocument.body.style)
}, [typebot.theme, frameDocument]) }, [theme, frameDocument])
useEffect(() => { useEffect(() => {
const answer = [...answers].pop() const answer = [...answers].pop()

View File

@@ -74,7 +74,7 @@ export const TypebotViewer = ({
> >
<div className="flex w-full h-full justify-center"> <div className="flex w-full h-full justify-center">
<ConversationContainer <ConversationContainer
typebot={typebot} theme={typebot.theme}
onNewBlockVisible={handleNewBlockVisible} onNewBlockVisible={handleNewBlockVisible}
onNewAnswer={handleNewAnswer} onNewAnswer={handleNewAnswer}
onCompleted={handleCompleted} onCompleted={handleCompleted}

View File

@@ -1,9 +1,10 @@
import { PublicTypebot } from 'models' import { Edge, PublicTypebot } from 'models'
import React, { createContext, ReactNode, useContext, useState } from 'react' import React, { createContext, ReactNode, useContext, useState } from 'react'
const typebotContext = createContext<{ const typebotContext = createContext<{
typebot: PublicTypebot typebot: PublicTypebot
updateVariableValue: (variableId: string, value: string) => void updateVariableValue: (variableId: string, value: string) => void
createEdge: (edge: Edge) => void
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore //@ts-ignore
}>({}) }>({})
@@ -25,11 +26,20 @@ export const TypebotContext = ({
), ),
})) }))
} }
const createEdge = (edge: Edge) => {
setLocalTypebot((typebot) => ({
...typebot,
edges: [...typebot.edges, edge],
}))
}
return ( return (
<typebotContext.Provider <typebotContext.Provider
value={{ value={{
typebot: localTypebot, typebot: localTypebot,
updateVariableValue, updateVariableValue,
createEdge,
}} }}
> >
{children} {children}

View File

@@ -0,0 +1,65 @@
import {
BubbleStep,
BubbleStepType,
Edge,
EmailInputStep,
InputStepType,
PhoneNumberInputStep,
PublicStep,
UrlInputStep,
Variable,
} from 'models'
import { isPossiblePhoneNumber } from 'react-phone-number-input'
import { isInputStep } from 'utils'
import { parseVariables } from './variable'
const emailRegex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const urlRegex =
/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/
export const isInputValid = (
inputValue: string,
type: InputStepType
): boolean => {
switch (type) {
case InputStepType.EMAIL:
return emailRegex.test(inputValue)
case InputStepType.PHONE:
return isPossiblePhoneNumber(inputValue)
case InputStepType.URL:
return urlRegex.test(inputValue)
}
return true
}
export const stepCanBeRetried = (
step: PublicStep
): step is EmailInputStep | UrlInputStep | PhoneNumberInputStep =>
isInputStep(step) && 'retryMessageContent' in step.options
export const parseRetryStep = (
step: EmailInputStep | UrlInputStep | PhoneNumberInputStep,
variables: Variable[],
createEdge: (edge: Edge) => void
): BubbleStep => {
const content = parseVariables(variables)(step.options.retryMessageContent)
const newStepId = step.id + Math.random() * 1000
const newEdge: Edge = {
id: (Math.random() * 1000).toString(),
from: { stepId: newStepId, blockId: step.blockId },
to: { blockId: step.blockId, stepId: step.id },
}
createEdge(newEdge)
return {
blockId: step.blockId,
id: newStepId,
type: BubbleStepType.TEXT,
content: {
html: `<div>${content}</div>`,
richText: [],
plainText: content,
},
outgoingEdgeId: newEdge.id,
}
}

View File

@@ -56,11 +56,9 @@ export type DateInputStep = StepBase & {
export type PhoneNumberInputStep = StepBase & { export type PhoneNumberInputStep = StepBase & {
type: InputStepType.PHONE type: InputStepType.PHONE
options: OptionBase & InputTextOptionsBase options: PhoneNumberInputOptions
} }
export type PhoneNumberInputOptions = OptionBase & InputTextOptionsBase
export type ChoiceInputStep = StepBase & { export type ChoiceInputStep = StepBase & {
type: InputStepType.CHOICE type: InputStepType.CHOICE
items: ButtonItem[] items: ButtonItem[]
@@ -73,6 +71,7 @@ export type ButtonItem = ItemBase & {
} }
type OptionBase = { variableId?: string } type OptionBase = { variableId?: string }
type InputTextOptionsBase = { type InputTextOptionsBase = {
labels: { placeholder: string; button: string } labels: { placeholder: string; button: string }
} }
@@ -88,9 +87,20 @@ export type DateInputOptions = OptionBase & {
isRange: boolean isRange: boolean
} }
export type EmailInputOptions = OptionBase & InputTextOptionsBase export type EmailInputOptions = OptionBase & {
labels: { placeholder: string; button: string }
retryMessageContent: string
}
export type UrlInputOptions = OptionBase & InputTextOptionsBase export type UrlInputOptions = OptionBase & {
labels: { placeholder: string; button: string }
retryMessageContent: string
}
export type PhoneNumberInputOptions = OptionBase & {
labels: { placeholder: string; button: string }
retryMessageContent: string
}
export type TextInputOptions = OptionBase & export type TextInputOptions = OptionBase &
InputTextOptionsBase & { InputTextOptionsBase & {
@@ -116,11 +126,21 @@ export const defaultNumberInputOptions: NumberInputOptions = {
} }
export const defaultEmailInputOptions: EmailInputOptions = { export const defaultEmailInputOptions: EmailInputOptions = {
labels: { button: defaultButtonLabel, placeholder: 'Type your email...' }, labels: {
button: defaultButtonLabel,
placeholder: 'Type your email...',
},
retryMessageContent:
"This email doesn't seem to be valid. Can you type it again?",
} }
export const defaultUrlInputOptions: UrlInputOptions = { export const defaultUrlInputOptions: UrlInputOptions = {
labels: { button: defaultButtonLabel, placeholder: 'Type a URL...' }, labels: {
button: defaultButtonLabel,
placeholder: 'Type a URL...',
},
retryMessageContent:
"This email doesn't seem to be valid. Can you type it again?",
} }
export const defaultDateInputOptions: DateInputOptions = { export const defaultDateInputOptions: DateInputOptions = {
@@ -134,6 +154,8 @@ export const defaultPhoneInputOptions: PhoneNumberInputOptions = {
button: defaultButtonLabel, button: defaultButtonLabel,
placeholder: 'Type your phone number...', placeholder: 'Type your phone number...',
}, },
retryMessageContent:
"This email doesn't seem to be valid. Can you type it again?",
} }
export const defaultChoiceInputOptions: ChoiceInputOptions = { export const defaultChoiceInputOptions: ChoiceInputOptions = {