diff --git a/apps/builder/components/board/StepTypesList/StepIcon.tsx b/apps/builder/components/board/StepTypesList/StepIcon.tsx index 9c4eb6b81..d4f5a7c1f 100644 --- a/apps/builder/components/board/StepTypesList/StepIcon.tsx +++ b/apps/builder/components/board/StepTypesList/StepIcon.tsx @@ -1,4 +1,5 @@ import { + CalendarIcon, ChatIcon, EmailIcon, FlagIcon, @@ -28,6 +29,9 @@ export const StepIcon = ({ type }: StepIconProps) => { case InputStepType.URL: { return } + case InputStepType.DATE: { + return + } case 'start': { return } diff --git a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx index ccb617819..caf448add 100644 --- a/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx +++ b/apps/builder/components/board/StepTypesList/StepTypeLabel.tsx @@ -19,6 +19,9 @@ export const StepTypeLabel = ({ type }: Props) => { case InputStepType.URL: { return Website } + case InputStepType.DATE: { + return Date + } 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 cc2befbde..1dae6c277 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx +++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/SettingsPopoverContent.tsx @@ -1,10 +1,13 @@ import { PopoverContent, PopoverArrow, PopoverBody } from '@chakra-ui/react' import { useTypebot } from 'contexts/TypebotContext/TypebotContext' import { InputStepType, Step, TextInputOptions } from 'models' -import { EmailInputSettingsBody } from './EmailInputSettingsBody' -import { NumberInputSettingsBody } from './NumberInputSettingsBody' -import { TextInputSettingsBody } from './TextInputSettingsBody' -import { UrlInputSettingsBody } from './UrlInputSettingsBody' +import { + TextInputSettingsBody, + NumberInputSettingsBody, + EmailInputSettingsBody, + UrlInputSettingsBody, + DateInputSettingsBody, +} from './bodies' type Props = { step: Step @@ -60,6 +63,14 @@ const SettingsPopoverBodyContent = ({ step }: Props) => { /> ) } + case InputStepType.DATE: { + return ( + + ) + } default: { return <>> } diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx new file mode 100644 index 000000000..223253ef6 --- /dev/null +++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/DateInputSettingsBody.tsx @@ -0,0 +1,80 @@ +import { FormLabel, Stack } from '@chakra-ui/react' +import { DebouncedInput } from 'components/shared/DebouncedInput' +import { SwitchWithLabel } from 'components/shared/SwitchWithLabel' +import { DateInputOptions } from 'models' +import React from 'react' + +type DateInputSettingsBodyProps = { + options?: DateInputOptions + onOptionsChange: (options: DateInputOptions) => void +} + +export const DateInputSettingsBody = ({ + options, + onOptionsChange, +}: DateInputSettingsBodyProps) => { + const handleFromChange = (from: string) => + onOptionsChange({ ...options, labels: { ...options?.labels, from } }) + const handleToChange = (to: string) => + onOptionsChange({ ...options, labels: { ...options?.labels, to } }) + const handleButtonLabelChange = (button: string) => + onOptionsChange({ ...options, labels: { ...options?.labels, button } }) + const handleIsRangeChange = (isRange: boolean) => + onOptionsChange({ ...options, isRange }) + const handleHasTimeChange = (hasTime: boolean) => + onOptionsChange({ ...options, hasTime }) + + return ( + + + + {options?.isRange && ( + + + From label: + + + + )} + {options?.isRange && ( + + + To label: + + + + )} + + + Button label: + + + + + ) +} diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/EmailInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/EmailInputSettingsBody.tsx rename to apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/EmailInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/NumberInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/NumberInputSettingsBody.tsx rename to apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/NumberInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/TextInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/TextInputSettingsBody.tsx rename to apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/TextInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/UrlInputSettingsBody.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx similarity index 100% rename from apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/UrlInputSettingsBody.tsx rename to apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/UrlInputSettingsBody.tsx diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/index.ts b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/index.ts new file mode 100644 index 000000000..050b5d376 --- /dev/null +++ b/apps/builder/components/board/graph/BlockNode/StepNode/SettingsPopoverContent/bodies/index.ts @@ -0,0 +1,5 @@ +export * from './DateInputSettingsBody' +export * from './EmailInputSettingsBody' +export * from './NumberInputSettingsBody' +export * from './TextInputSettingsBody' +export * from './UrlInputSettingsBody' diff --git a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx index 5d19fff83..28bb950f0 100644 --- a/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx +++ b/apps/builder/components/board/graph/BlockNode/StepNode/StepNodeLabel.tsx @@ -46,6 +46,13 @@ export const StepNodeLabel = (props: Step | StartStep) => { ) } + case InputStepType.DATE: { + return ( + + {props.options?.labels?.from ?? 'Pick a date...'} + + ) + } case 'start': { return {props.label} } diff --git a/apps/builder/components/shared/SwitchWithLabel.tsx b/apps/builder/components/shared/SwitchWithLabel.tsx index b31c49c2c..dd4f90021 100644 --- a/apps/builder/components/shared/SwitchWithLabel.tsx +++ b/apps/builder/components/shared/SwitchWithLabel.tsx @@ -2,12 +2,14 @@ import { FormLabel, HStack, Switch, SwitchProps } from '@chakra-ui/react' import React, { useState } from 'react' type SwitchWithLabelProps = { + id: string label: string initialValue: boolean onCheckChange: (isChecked: boolean) => void } & SwitchProps export const SwitchWithLabel = ({ + id, label, initialValue, onCheckChange, @@ -21,10 +23,15 @@ export const SwitchWithLabel = ({ } return ( - + {label} - + ) } diff --git a/apps/builder/cypress/tests/inputs.ts b/apps/builder/cypress/tests/inputs.ts index 70812f6b5..1160b0091 100644 --- a/apps/builder/cypress/tests/inputs.ts +++ b/apps/builder/cypress/tests/inputs.ts @@ -137,6 +137,41 @@ describe('URL input', () => { }) }) +describe('Date input', () => { + beforeEach(() => { + cy.task('seed') + createTypebotWithStep({ type: InputStepType.DATE }) + cy.signOut() + }) + + it.only('options should work', () => { + cy.signIn('test2@gmail.com') + cy.visit('/typebots/typebot3/edit') + cy.findByRole('button', { name: 'Preview' }).click() + getIframeBody() + .findByTestId('from-date') + .should('have.attr', 'type') + .should('eq', 'date') + getIframeBody().findByRole('button', { name: 'Send' }).should('be.disabled') + cy.findByTestId('step-step1').click({ force: true }) + cy.findByRole('checkbox', { name: 'Is range?' }).check({ force: true }) + cy.findByRole('textbox', { name: 'From label:' }).clear().type('Previous:') + cy.findByRole('textbox', { name: 'To label:' }).clear().type('After:') + cy.findByRole('checkbox', { name: 'With time?' }).check({ force: true }) + cy.findByRole('textbox', { name: 'Button label:' }).clear().type('Go') + cy.findByRole('button', { name: 'Restart' }).click() + getIframeBody() + .findByTestId('from-date') + .should('have.attr', 'type') + .should('eq', 'datetime-local') + getIframeBody() + .findByTestId('to-date') + .should('have.attr', 'type') + .should('eq', 'datetime-local') + getIframeBody().findByRole('button', { name: 'Go' }) + }) +}) + const createTypebotWithStep = (step: Omit) => { cy.task( 'createTypebot', diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx index a54ba3667..22ce71dde 100644 --- a/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/ChatStep.tsx @@ -6,6 +6,7 @@ import { GuestBubble } from './bubbles/GuestBubble' import { HostMessageBubble } from './bubbles/HostMessageBubble' import { TextForm } from './inputs/TextForm' import { isInputStep, isTextBubbleStep } from 'utils' +import { DateForm } from './inputs/DateForm' export const ChatStep = ({ step, @@ -56,5 +57,7 @@ const InputChatStep = ({ case InputStepType.EMAIL: case InputStepType.URL: return + case InputStepType.DATE: + return } } diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/DateForm.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/DateForm.tsx new file mode 100644 index 000000000..f13c96f7f --- /dev/null +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/DateForm.tsx @@ -0,0 +1,70 @@ +import { DateInputOptions } from 'models' +import React, { useState } from 'react' +import { SendButton } from './SendButton' + +type DateInputProps = { + onSubmit: (inputValue: `${string} to ${string}` | string) => void + options?: DateInputOptions +} + +export const DateForm = ({ + onSubmit, + options, +}: DateInputProps): JSX.Element => { + const { hasTime, isRange, labels } = options ?? {} + const [inputValues, setInputValues] = useState({ from: '', to: '' }) + return ( + + + { + if (inputValues.from === '' && inputValues.to === '') return + e.preventDefault() + onSubmit( + `${inputValues.from}${isRange ? ` to ${inputValues.to}` : ''}` + ) + }} + > + + + {isRange && ( + {labels?.from ?? 'From:'} + )} + + setInputValues({ ...inputValues, from: e.target.value }) + } + data-testid="from-date" + /> + + {isRange && ( + + {isRange && ( + {labels?.to ?? 'To:'} + )} + + setInputValues({ ...inputValues, to: e.target.value }) + } + data-testid="to-date" + /> + + )} + + + + + + + ) +} diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/NumberInput.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/NumberInput.tsx deleted file mode 100644 index 10f5a5d26..000000000 --- a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/NumberInput.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { NumberInputStep, TextInputStep } from 'models' -import React, { FormEvent, useRef, useState } from 'react' -import { SendIcon } from '../../../../assets/icons' - -type NumberInputProps = { - step: NumberInputStep - onSubmit: (value: string) => void -} - -export const NumberInput = ({ step, onSubmit }: NumberInputProps) => { - const inputRef = useRef(null) - const [inputValue, setInputValue] = useState('') - - const handleSubmit = (e: FormEvent) => { - e.preventDefault() - if (inputValue === '') return - onSubmit(inputValue) - } - - return ( - - - - setInputValue(e.target.value)} - style={{ appearance: 'auto' }} - min={step.options?.min} - max={step.options?.max} - step={step.options?.step} - required - /> - - - {step.options?.labels?.button ?? 'Send'} - - - - - - - ) -} diff --git a/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/SendButton.tsx b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/SendButton.tsx new file mode 100644 index 000000000..bba18d566 --- /dev/null +++ b/packages/bot-engine/src/components/ChatBlock/ChatStep/inputs/SendButton.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { SendIcon } from '../../../../assets/icons' + +type SendButtonProps = { + label: string + isDisabled: boolean +} & React.ButtonHTMLAttributes + +export const SendButton = ({ + label, + isDisabled, + ...props +}: SendButtonProps) => { + return ( + + {label} + + + ) +} 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 714d25253..c2e021b61 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 @@ -5,7 +5,7 @@ import { UrlInputStep, } from 'models' import React, { FormEvent, useState } from 'react' -import { SendIcon } from '../../../../../assets/icons' +import { SendButton } from '../SendButton' import { TextInput } from './TextInputContent' type TextFormProps = { @@ -32,18 +32,10 @@ export const TextForm = ({ step, onSubmit }: TextFormProps) => { onSubmit={handleSubmit} > - - - {step.options?.labels?.button ?? 'Send'} - - - + diff --git a/packages/models/src/typebot/steps/index.ts b/packages/models/src/typebot/steps/index.ts new file mode 100644 index 000000000..c9ce17275 --- /dev/null +++ b/packages/models/src/typebot/steps/index.ts @@ -0,0 +1,2 @@ +export * from './steps' +export * from './inputs' diff --git a/packages/models/src/typebot/steps.ts b/packages/models/src/typebot/steps/inputs.ts similarity index 64% rename from packages/models/src/typebot/steps.ts rename to packages/models/src/typebot/steps/inputs.ts index cc46d0dce..2bacc4791 100644 --- a/packages/models/src/typebot/steps.ts +++ b/packages/models/src/typebot/steps/inputs.ts @@ -1,36 +1,18 @@ -export type Step = StartStep | BubbleStep | InputStep - -export type BubbleStep = TextStep +import { StepBase } from './steps' export type InputStep = | TextInputStep | NumberInputStep | EmailInputStep | UrlInputStep - -export type StepType = 'start' | BubbleStepType | InputStepType - -export enum BubbleStepType { - TEXT = 'text', -} + | DateInputStep export enum InputStepType { TEXT = 'text input', NUMBER = 'number input', EMAIL = 'email input', URL = 'url input', -} - -export type StepBase = { id: string; blockId: string; target?: Target } - -export type StartStep = StepBase & { - type: 'start' - label: string -} - -export type TextStep = StepBase & { - type: BubbleStepType.TEXT - content: { html: string; richText: unknown[]; plainText: string } + DATE = 'date input', } export type TextInputStep = StepBase & { @@ -53,6 +35,17 @@ export type UrlInputStep = StepBase & { options?: UrlInputOptions } +export type DateInputStep = StepBase & { + type: InputStepType.DATE + options?: DateInputOptions +} + +export type DateInputOptions = { + labels?: { button?: string; from?: string; to?: string } + hasTime?: boolean + isRange?: boolean +} + export type EmailInputOptions = InputOptionsBase export type UrlInputOptions = InputOptionsBase @@ -70,5 +63,3 @@ export type NumberInputOptions = InputOptionsBase & { max?: number step?: number } - -export type Target = { blockId: string; stepId?: string } diff --git a/packages/models/src/typebot/steps/steps.ts b/packages/models/src/typebot/steps/steps.ts new file mode 100644 index 000000000..41b3866c3 --- /dev/null +++ b/packages/models/src/typebot/steps/steps.ts @@ -0,0 +1,25 @@ +import { InputStep, InputStepType } from './inputs' + +export type Step = StartStep | BubbleStep | InputStep + +export type BubbleStep = TextStep + +export type StepType = 'start' | BubbleStepType | InputStepType + +export enum BubbleStepType { + TEXT = 'text', +} + +export type StepBase = { id: string; blockId: string; target?: Target } + +export type StartStep = StepBase & { + type: 'start' + label: string +} + +export type TextStep = StepBase & { + type: BubbleStepType.TEXT + content: { html: string; richText: unknown[]; plainText: string } +} + +export type Target = { blockId: string; stepId?: string } diff --git a/packages/models/src/typebot/typebot.ts b/packages/models/src/typebot/typebot.ts index b6ab30bc7..7a1b64fab 100644 --- a/packages/models/src/typebot/typebot.ts +++ b/packages/models/src/typebot/typebot.ts @@ -1,7 +1,7 @@ import { Typebot as TypebotFromPrisma } from 'db' import { Table } from '../utils' import { Settings } from './settings' -import { Step } from './steps' +import { Step } from './steps/steps' import { Theme } from './theme' export type Typebot = Omit<
{labels?.from ?? 'From:'}
{labels?.to ?? 'To:'}