@ -25,9 +25,7 @@ import { useParentModal } from '@/features/graph/providers/ParentModalProvider'
|
||||
type Props = {
|
||||
initialVariableId?: string
|
||||
autoFocus?: boolean
|
||||
onSelectVariable: (
|
||||
variable: Pick<Variable, 'id' | 'name'> | undefined
|
||||
) => void
|
||||
onSelectVariable: (variable: Pick<Variable, 'id' | 'name'>) => void
|
||||
} & InputProps
|
||||
|
||||
export const VariableSearchInput = ({
|
||||
@ -70,7 +68,6 @@ export const VariableSearchInput = ({
|
||||
const onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setInputValue(e.target.value)
|
||||
if (e.target.value === '') {
|
||||
onSelectVariable(undefined)
|
||||
setFilteredItems([...variables.slice(0, 50)])
|
||||
return
|
||||
}
|
||||
|
@ -10,16 +10,19 @@ import { useEffect, useState } from 'react'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { env } from 'utils'
|
||||
|
||||
type Props = {
|
||||
value?: number
|
||||
debounceTimeout?: number
|
||||
withVariableButton?: boolean
|
||||
onValueChange: (value?: number) => void
|
||||
} & NumberInputProps
|
||||
|
||||
export const SmartNumberInput = ({
|
||||
value,
|
||||
onValueChange,
|
||||
debounceTimeout = 1000,
|
||||
...props
|
||||
}: {
|
||||
value?: number
|
||||
debounceTimeout?: number
|
||||
onValueChange: (value?: number) => void
|
||||
} & NumberInputProps) => {
|
||||
}: Props) => {
|
||||
const [currentValue, setCurrentValue] = useState(value?.toString() ?? '')
|
||||
const debounced = useDebouncedCallback(
|
||||
onValueChange,
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { featherIconsBaseProps } from '@/components/icons'
|
||||
import { Icon, IconProps, useColorModeValue } from '@chakra-ui/react'
|
||||
|
||||
export const WaitIcon = (props: IconProps) => (
|
||||
<Icon
|
||||
viewBox="0 0 24 24"
|
||||
color={useColorModeValue('purple.500', 'purple.300')}
|
||||
{...featherIconsBaseProps}
|
||||
{...props}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<polyline points="12 6 12 12 16 14"></polyline>
|
||||
</Icon>
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { WaitOptions } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options: WaitOptions
|
||||
}
|
||||
|
||||
export const WaitNodeContent = ({ options: { secondsToWaitFor } }: Props) => (
|
||||
<Text color={secondsToWaitFor ? 'currentcolor' : 'gray.500'} noOfLines={1}>
|
||||
{secondsToWaitFor ? `Wait for ${secondsToWaitFor}s` : 'Configure...'}
|
||||
</Text>
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { WaitOptions } from 'models'
|
||||
import React from 'react'
|
||||
import { Input } from '@/components/inputs'
|
||||
|
||||
type Props = {
|
||||
options: WaitOptions
|
||||
onOptionsChange: (options: WaitOptions) => void
|
||||
}
|
||||
|
||||
export const WaitSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const handleSecondsChange = (secondsToWaitFor: string | undefined) => {
|
||||
onOptionsChange({ ...options, secondsToWaitFor })
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Input
|
||||
label="Seconds to wait for:"
|
||||
defaultValue={options.secondsToWaitFor}
|
||||
onChange={handleSecondsChange}
|
||||
placeholder="0"
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
26
apps/builder/src/features/blocks/logic/wait/wait.spec.ts
Normal file
26
apps/builder/src/features/blocks/logic/wait/wait.spec.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import test, { expect } from '@playwright/test'
|
||||
import { typebotViewer } from 'utils/playwright/testHelpers'
|
||||
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
|
||||
import cuid from 'cuid'
|
||||
import { getTestAsset } from '@/test/utils/playwright'
|
||||
|
||||
const typebotId = cuid()
|
||||
|
||||
test.describe('Wait block', () => {
|
||||
test('wait should trigger', async ({ page }) => {
|
||||
await importTypebotInDatabase(getTestAsset('typebots/logic/wait.json'), {
|
||||
id: typebotId,
|
||||
})
|
||||
|
||||
await page.goto(`/typebots/${typebotId}/edit`)
|
||||
await page.click('text=Configure...')
|
||||
await page.getByRole('textbox', { name: 'Seconds to wait for:' }).fill('3')
|
||||
|
||||
await page.click('text=Preview')
|
||||
await typebotViewer(page).locator('text=Wait now').click()
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeHidden()
|
||||
await page.waitForTimeout(3000)
|
||||
await expect(typebotViewer(page).locator('text="Hi there!"')).toBeVisible()
|
||||
})
|
||||
})
|
@ -36,6 +36,7 @@ import { TextInputIcon } from '@/features/blocks/inputs/textInput'
|
||||
import { EmbedBubbleIcon } from '@/features/blocks/bubbles/embed'
|
||||
import { GoogleAnalyticsLogo } from '@/features/blocks/integrations/googleAnalytics'
|
||||
import { AudioBubbleIcon } from '@/features/blocks/bubbles/audio'
|
||||
import { WaitIcon } from '@/features/blocks/logic/wait/components/WaitIcon'
|
||||
|
||||
type BlockIconProps = { type: BlockType } & IconProps
|
||||
|
||||
@ -82,6 +83,8 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps) => {
|
||||
return <RedirectIcon color={purple} {...props} />
|
||||
case LogicBlockType.CODE:
|
||||
return <CodeIcon color={purple} {...props} />
|
||||
case LogicBlockType.WAIT:
|
||||
return <WaitIcon color={purple} {...props} />
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return <TypebotLinkIcon color={purple} {...props} />
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
|
@ -77,6 +77,8 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
|
||||
<Text>Typebot</Text>
|
||||
</Tooltip>
|
||||
)
|
||||
case LogicBlockType.WAIT:
|
||||
return <Text>Wait</Text>
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
return (
|
||||
<Tooltip label="Google Sheets">
|
||||
|
@ -37,6 +37,7 @@ import { SendEmailContent } from '@/features/blocks/integrations/sendEmail'
|
||||
import { isInputBlock, isChoiceInput, blockHasItems } from 'utils'
|
||||
import { MakeComContent } from '@/features/blocks/integrations/makeCom'
|
||||
import { AudioBubbleNode } from '@/features/blocks/bubbles/audio'
|
||||
import { WaitNodeContent } from '@/features/blocks/logic/wait/components/WaitNodeContent'
|
||||
|
||||
type Props = {
|
||||
block: Block | StartBlock
|
||||
@ -120,6 +121,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
case LogicBlockType.WAIT: {
|
||||
return <WaitNodeContent options={block.options} />
|
||||
}
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return <TypebotLinkNode block={block} />
|
||||
|
||||
|
@ -40,6 +40,8 @@ const getHelpDocUrl = (blockType: BlockWithOptions['type']): string | null => {
|
||||
return 'https://docs.typebot.io/editor/blocks/logic/redirect'
|
||||
case LogicBlockType.CODE:
|
||||
return 'https://docs.typebot.io/editor/blocks/logic/code'
|
||||
case LogicBlockType.WAIT:
|
||||
return 'https://docs.typebot.io/editor/blocks/logic/wait'
|
||||
case InputBlockType.TEXT:
|
||||
return 'https://docs.typebot.io/editor/blocks/inputs/text'
|
||||
case InputBlockType.NUMBER:
|
||||
|
@ -41,6 +41,7 @@ import { ButtonsOptionsForm } from '@/features/blocks/inputs/buttons'
|
||||
import { ChatwootSettingsForm } from '@/features/blocks/integrations/chatwoot'
|
||||
import { MakeComSettings } from '@/features/blocks/integrations/makeCom'
|
||||
import { HelpDocButton } from './HelpDocButton'
|
||||
import { WaitSettings } from '@/features/blocks/logic/wait/components/WaitSettings'
|
||||
|
||||
type Props = {
|
||||
block: BlockWithOptions
|
||||
@ -212,6 +213,14 @@ export const BlockSettings = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case LogicBlockType.WAIT: {
|
||||
return (
|
||||
<WaitSettings
|
||||
options={block.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case IntegrationBlockType.GOOGLE_SHEETS: {
|
||||
return (
|
||||
<GoogleSheetsSettingsBody
|
||||
|
@ -37,6 +37,7 @@ import {
|
||||
Item,
|
||||
ItemType,
|
||||
LogicBlockType,
|
||||
defaultWaitOptions,
|
||||
} from 'models'
|
||||
import {
|
||||
stubLength,
|
||||
@ -434,6 +435,8 @@ const parseDefaultBlockOptions = (type: BlockWithOptionsType): BlockOptions => {
|
||||
return defaultRedirectOptions
|
||||
case LogicBlockType.CODE:
|
||||
return defaultCodeOptions
|
||||
case LogicBlockType.WAIT:
|
||||
return defaultWaitOptions
|
||||
case LogicBlockType.TYPEBOT_LINK:
|
||||
return {}
|
||||
case IntegrationBlockType.GOOGLE_SHEETS:
|
||||
|
@ -13,9 +13,7 @@ import React from 'react'
|
||||
import { VariableSearchInput } from '@/components/VariableSearchInput'
|
||||
|
||||
type Props = {
|
||||
onSelectVariable: (
|
||||
variable: Pick<Variable, 'name' | 'id'> | undefined
|
||||
) => void
|
||||
onSelectVariable: (variable: Pick<Variable, 'name' | 'id'>) => void
|
||||
} & Omit<IconButtonProps, 'aria-label'>
|
||||
|
||||
export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
|
||||
|
1
apps/builder/src/test/assets/typebots/logic/wait.json
Normal file
1
apps/builder/src/test/assets/typebots/logic/wait.json
Normal file
@ -0,0 +1 @@
|
||||
{"id":"clddbpdus00011asqclyl0vlw","createdAt":"2023-01-26T16:42:16.803Z","updatedAt":"2023-01-26T16:48:53.712Z","icon":null,"name":"My typebot","publishedTypebotId":null,"folderId":null,"groups":[{"id":"clddbpduq002ssq1a2h622bw0","title":"Start","blocks":[{"id":"clddbpduq002tsq1aghl4dh31","type":"start","label":"Start","groupId":"clddbpduq002ssq1a2h622bw0","outgoingEdgeId":"clddbq3yp000l3b6ss15lu48v"}],"graphCoordinates":{"x":0,"y":0}},{"id":"clddbpito000e3b6spz6qqipa","title":"Group #1","blocks":[{"id":"clddbpito000f3b6srs0cizys","type":"choice input","items":[{"id":"clddbpito000g3b6snvioor95","type":0,"blockId":"clddbpito000f3b6srs0cizys","content":"Wait now"}],"groupId":"clddbpito000e3b6spz6qqipa","options":{"buttonLabel":"Send","isMultipleChoice":false}},{"id":"clddbw55x000m3b6svz34enqq","groupId":"clddbpito000e3b6spz6qqipa","type":"Wait","options":{"secondsToWaitFor":""},"outgoingEdgeId":"clddbw6l4000n3b6st6le0hu9"}],"graphCoordinates":{"x":357.49609375,"y":148.40625}},{"id":"clddbpwh9000i3b6s9e1vdjrd","title":"Group #2","blocks":[{"id":"clddbpwh9000j3b6srnzdpbcx","type":"text","content":{"html":"<div>Hi there!</div>","richText":[{"type":"p","children":[{"text":"Hi there!"}]}],"plainText":"Hi there!"},"groupId":"clddbpwh9000i3b6s9e1vdjrd"}],"graphCoordinates":{"x":545.25,"y":447.36328125}}],"variables":[],"edges":[{"id":"clddbq3yp000l3b6ss15lu48v","to":{"groupId":"clddbpito000e3b6spz6qqipa"},"from":{"blockId":"clddbpduq002tsq1aghl4dh31","groupId":"clddbpduq002ssq1a2h622bw0"}},{"from":{"groupId":"clddbpito000e3b6spz6qqipa","blockId":"clddbw55x000m3b6svz34enqq"},"to":{"groupId":"clddbpwh9000i3b6s9e1vdjrd"},"id":"clddbw6l4000n3b6st6le0hu9"}],"theme":{"chat":{"inputs":{"color":"#303235","backgroundColor":"#FFFFFF","placeholderColor":"#9095A0"},"buttons":{"color":"#FFFFFF","backgroundColor":"#0042DA"},"hostAvatar":{"url":"https://avatars.githubusercontent.com/u/16015833?v=4","isEnabled":true},"hostBubbles":{"color":"#303235","backgroundColor":"#F7F8FF"},"guestBubbles":{"color":"#FFFFFF","backgroundColor":"#FF8E21"}},"general":{"font":"Open Sans","background":{"type":"Color","content":"#ffffff"}}},"settings":{"general":{"isBrandingEnabled":false,"isInputPrefillEnabled":true,"isResultSavingEnabled":true,"isHideQueryParamsEnabled":true,"isNewResultOnRefreshEnabled":false},"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."},"typingEmulation":{"speed":300,"enabled":true,"maxDelay":1.5}},"publicId":null,"customDomain":null,"workspaceId":"proWorkspace","resultsTablePreferences":null,"isArchived":false,"isClosed":false}
|
Reference in New Issue
Block a user