diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx index 7ca909ee6d..40514726e0 100644 --- a/apps/builder/assets/icons.tsx +++ b/apps/builder/assets/icons.tsx @@ -224,3 +224,9 @@ export const EmailIcon = (props: IconProps) => ( ) + +export const PhoneIcon = (props: IconProps) => ( + + + +) diff --git a/apps/builder/components/board/StepTypesList/StepIcon.tsx b/apps/builder/components/board/StepTypesList/StepIcon.tsx index d4f5a7c1fc..e08e3ca42a 100644 --- a/apps/builder/components/board/StepTypesList/StepIcon.tsx +++ b/apps/builder/components/board/StepTypesList/StepIcon.tsx @@ -5,6 +5,7 @@ import { FlagIcon, GlobeIcon, NumberIcon, + PhoneIcon, TextIcon, } from 'assets/icons' import { BubbleStepType, InputStepType, StepType } from 'models' @@ -32,6 +33,9 @@ export const StepIcon = ({ type }: StepIconProps) => { case InputStepType.DATE: { return } + case InputStepType.PHONE: { + return + } case 'start': { return } diff --git a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx index caf448add6..ce53045945 100644 --- a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx +++ b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx @@ -22,6 +22,9 @@ export const StepTypeLabel = ({ type }: Props) => { case InputStepType.DATE: { return Date } + case InputStepType.PHONE: { + return Phone + } default: { return <> } diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx index 1dae6c2770..c2582fe47d 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx +++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx @@ -8,6 +8,7 @@ import { UrlInputSettingsBody, DateInputSettingsBody, } from './bodies' +import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody' type Props = { step: Step @@ -71,6 +72,14 @@ const SettingsPopoverBodyContent = ({ step }: Props) => { /> ) } + case InputStepType.PHONE: { + return ( + + ) + } default: { return <> } diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx new file mode 100644 index 0000000000..8a352b885a --- /dev/null +++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/PhoneNumberSettingsBody.tsx @@ -0,0 +1,46 @@ +import { FormLabel, Stack } from '@chakra-ui/react' +import { DebouncedInput } from 'components/shared/DebouncedInput' +import { EmailInputOptions } from 'models' +import React from 'react' + +type PhoneNumberSettingsBodyProps = { + options?: EmailInputOptions + onOptionsChange: (options: EmailInputOptions) => void +} + +export const PhoneNumberSettingsBody = ({ + options, + onOptionsChange, +}: PhoneNumberSettingsBodyProps) => { + const handlePlaceholderChange = (placeholder: string) => + onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } }) + const handleButtonLabelChange = (button: string) => + onOptionsChange({ ...options, labels: { ...options?.labels, button } }) + + return ( + + + + Placeholder: + + + + + + Button label: + + + + + ) +} diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx index 28bb950f0a..01cd99c8c8 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx +++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx @@ -53,6 +53,13 @@ export const StepNodeLabel = (props: Step | StartStep) => { ) } + case InputStepType.PHONE: { + return ( + + {props.options?.labels?.placeholder ?? 'Your phone number...'} + + ) + } case 'start': { return {props.label} } diff --git a/apps/builder/cypress/tests/inputs.ts b/apps/builder/cypress/tests/inputs.ts index 1160b00911..8df832956e 100644 --- a/apps/builder/cypress/tests/inputs.ts +++ b/apps/builder/cypress/tests/inputs.ts @@ -144,7 +144,7 @@ describe('Date input', () => { cy.signOut() }) - it.only('options should work', () => { + it('options should work', () => { cy.signIn('test2@gmail.com') cy.visit('/typebots/typebot3/edit') cy.findByRole('button', { name: 'Preview' }).click() @@ -172,6 +172,41 @@ describe('Date input', () => { }) }) +describe('Phone number input', () => { + beforeEach(() => { + cy.task('seed') + cy.log(JSON.stringify({ type: InputStepType.PHONE })) + createTypebotWithStep({ type: InputStepType.PHONE }) + cy.signOut() + }) + + it('options should work', () => { + cy.signIn('test2@gmail.com') + cy.visit('/typebots/typebot3/edit') + cy.findByRole('button', { name: 'Preview' }).click() + getIframeBody() + .findByPlaceholderText('Your phone number...') + .should('have.attr', 'type') + .should('eq', 'tel') + getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled') + cy.findByTestId('step-step1').click({ force: true }) + cy.findByRole('textbox', { name: 'Placeholder:' }) + .clear() + .type('+33 XX XX XX XX') + cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go') + cy.findByRole('button', { name: 'Restart' }).click() + getIframeBody() + .findByPlaceholderText('+33 XX XX XX XX') + .type('+33 6 73 18 45 36') + getIframeBody() + .findByRole('img') + .should('have.attr', 'alt') + .should('eq', 'France') + getIframeBody().findByRole('button', { name: 'Go' }).click() + getIframeBody().findByText('+33673184536').should('exist') + }) +}) + const createTypebotWithStep = (step: Omit) => { cy.task( 'createTypebot', diff --git a/packages/bot-engine/package.json b/packages/bot-engine/package.json index 91a31128c1..0d10501377 100644 --- a/packages/bot-engine/package.json +++ b/packages/bot-engine/package.json @@ -10,15 +10,18 @@ "fast-equals": "^2.0.4", "models": "*", "react-frame-component": "^5.2.1", + "react-phone-number-input": "^3.1.44", "react-scroll": "^1.8.4", "react-transition-group": "^4.4.2", "utils": "*" }, "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.1.3", "@rollup/plugin-typescript": "^8.3.0", "@types/react": "^17.0.38", + "@types/react-phone-number-input": "^3.0.13", "@types/react-scroll": "^1.8.3", "@types/react-transition-group": "^4.4.4", "autoprefixer": "^10.4.1", diff --git a/packages/bot-engine/rollup.config.js b/packages/bot-engine/rollup.config.js index af871fe3d2..2bf4cdd3f9 100644 --- a/packages/bot-engine/rollup.config.js +++ b/packages/bot-engine/rollup.config.js @@ -1,6 +1,7 @@ import resolve from '@rollup/plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import typescript from '@rollup/plugin-typescript' +import json from '@rollup/plugin-json' import dts from 'rollup-plugin-dts' import postcss from 'rollup-plugin-postcss' import { terser } from 'rollup-plugin-terser' @@ -28,6 +29,7 @@ export default [ plugins: [ peerDepsExternal(), resolve(), + json(), commonjs(), typescript({ tsconfig: './tsconfig.json' }), postcss({ diff --git a/packages/bot-engine/src/assets/style.css b/packages/bot-engine/src/assets/style.css index 600aa5d133..333c3c99f6 100644 --- a/packages/bot-engine/src/assets/style.css +++ b/packages/bot-engine/src/assets/style.css @@ -38,6 +38,10 @@ --typebot-header-border: none; --typebot-header-shadow: none; --typebot-header-max-width: 1000px; + + /* Phone input */ + --PhoneInputCountryFlag-borderColor: transparent; + --PhoneInput-color--focus: transparent; } /* Hide scrollbar for Chrome, Safari and Opera */ diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx index 22ce71dde5..39c937ac98 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx @@ -56,6 +56,7 @@ const InputChatStep = ({ case InputStepType.NUMBER: case InputStepType.EMAIL: case InputStepType.URL: + case InputStepType.PHONE: return case InputStepType.DATE: return diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextForm.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextForm.tsx index c2e021b61f..137a4578c5 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextForm.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextForm.tsx @@ -1,6 +1,7 @@ import { EmailInputStep, NumberInputStep, + PhoneNumberInputStep, TextInputStep, UrlInputStep, } from 'models' @@ -9,7 +10,12 @@ import { SendButton } from '../SendButton' import { TextInput } from './TextInputContent' type TextFormProps = { - step: TextInputStep | EmailInputStep | NumberInputStep | UrlInputStep + step: + | TextInputStep + | EmailInputStep + | NumberInputStep + | UrlInputStep + | PhoneNumberInputStep onSubmit: (value: string) => void } diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextInputContent.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextInputContent.tsx index 544e4f3aae..12b48a3705 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextInputContent.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/TextForm/TextInputContent.tsx @@ -4,6 +4,7 @@ import { NumberInputStep, InputStepType, UrlInputStep, + PhoneNumberInputStep, } from 'models' import React, { ChangeEvent, @@ -12,14 +13,20 @@ import React, { useEffect, useRef, } from 'react' +import PhoneInput, { Value } from 'react-phone-number-input' type TextInputProps = { - step: TextInputStep | EmailInputStep | NumberInputStep | UrlInputStep + step: + | TextInputStep + | EmailInputStep + | NumberInputStep + | UrlInputStep + | PhoneNumberInputStep onChange: (value: string) => void } export const TextInput = ({ step, onChange }: TextInputProps) => { - const inputRef = useRef(null) + const inputRef = useRef(null) useEffect(() => { if (!inputRef.current) return @@ -30,6 +37,10 @@ export const TextInput = ({ step, onChange }: TextInputProps) => { e: ChangeEvent ) => onChange(e.target.value) + const handlePhoneNumberChange = (value?: Value | undefined) => { + onChange(value as string) + } + switch (step.type) { case InputStepType.TEXT: { return step.options?.isLong ? ( @@ -88,6 +99,17 @@ export const TextInput = ({ step, onChange }: TextInputProps) => { /> ) } + case InputStepType.PHONE: { + return ( + + ) + } } } diff --git a/packages/bot-engine/src/components/TypebotViewer.tsx b/packages/bot-engine/src/components/TypebotViewer.tsx index 7982589aa6..83885f5bb1 100644 --- a/packages/bot-engine/src/components/TypebotViewer.tsx +++ b/packages/bot-engine/src/components/TypebotViewer.tsx @@ -3,6 +3,8 @@ import { TypebotContext } from '../contexts/TypebotContext' import Frame from 'react-frame-component' //@ts-ignore import style from '../assets/style.css' +//@ts-ignore +import phoneNumberInputStyle from 'react-phone-number-input/style.css' import { ConversationContainer } from './ConversationContainer' import { AnswersContext } from '../contexts/AnswersContext' import { Answer, BackgroundType, PublicTypebot } from 'models' @@ -39,7 +41,12 @@ export const TypebotViewer = ({ return ( {style}} + head={ + + } style={{ width: '100%', height: '100%', border: 'none' }} >