Skip to content

Commit

Permalink
feat(engine): 🚸 Improve input variable behaviour (for loops)
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Mar 10, 2022
1 parent 2c1f694 commit 9123977
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 17 deletions.
8 changes: 8 additions & 0 deletions apps/builder/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,11 @@ export const BoxIcon = (props: IconProps) => (
<line x1="12" y1="22.08" x2="12" y2="12"></line>
</Icon>
)

export const HelpCircleIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</Icon>
)
13 changes: 13 additions & 0 deletions apps/builder/components/settings/GeneralSettingsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export const GeneralSettingsForm = ({
isNewResultOnRefreshEnabled,
})

const handleInputPrefillChange = (isInputPrefillEnabled: boolean) =>
onGeneralSettingsChange({
...generalSettings,
isInputPrefillEnabled,
})

return (
<Stack spacing={6}>
<UpgradeModal isOpen={isOpen} onClose={onClose} />
Expand All @@ -59,6 +65,13 @@ export const GeneralSettingsForm = ({
onChange={handleSwitchChange}
/>
</Flex>
<SwitchWithLabel
id="prefill"
label="Prefill input"
initialValue={generalSettings.isInputPrefillEnabled ?? true}
onCheckChange={handleInputPrefillChange}
moreInfoContent="Inputs are automatically pre-filled whenever its saving variable has a value"
/>
<SwitchWithLabel
id="new-result"
label="Create new session on page refresh"
Expand Down
27 changes: 23 additions & 4 deletions apps/builder/components/shared/SwitchWithLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { FormLabel, HStack, Switch, SwitchProps } from '@chakra-ui/react'
import {
chakra,
FormLabel,
HStack,
Switch,
SwitchProps,
Tooltip,
} from '@chakra-ui/react'
import { HelpCircleIcon } from 'assets/icons'
import React, { useState } from 'react'

type SwitchWithLabelProps = {
id: string
label: string
initialValue: boolean
moreInfoContent?: string
onCheckChange: (isChecked: boolean) => void
} & SwitchProps

export const SwitchWithLabel = ({
id,
label,
initialValue,
moreInfoContent,
onCheckChange,
...props
}: SwitchWithLabelProps) => {
Expand All @@ -23,9 +33,18 @@ export const SwitchWithLabel = ({
}
return (
<HStack justifyContent="space-between">
<FormLabel htmlFor={id} mb="0">
{label}
</FormLabel>
<HStack>
<FormLabel htmlFor={id} mb="0" mr="0">
{label}
</FormLabel>
{moreInfoContent && (
<Tooltip label={moreInfoContent}>
<chakra.span cursor="pointer">
<HelpCircleIcon />
</chakra.span>
</Tooltip>
)}
</HStack>
<Switch
isChecked={isChecked}
onChange={handleChange}
Expand Down
119 changes: 119 additions & 0 deletions apps/builder/playwright/fixtures/typebots/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"id": "cl0kvckdk2754en1avz0i71k0",
"createdAt": "2022-03-10T10:50:23.912Z",
"updatedAt": "2022-03-10T10:50:23.912Z",
"name": "My typebot",
"ownerId": "cl0cfi60r0000381a2bft9yis",
"publishedTypebotId": null,
"folderId": null,
"blocks": [
{
"id": "cAvp3oQUNYcANvcEQEVSpD",
"steps": [
{
"id": "bAkhPioPM1uAda6K2aJzHD",
"type": "start",
"label": "Start",
"blockId": "cAvp3oQUNYcANvcEQEVSpD",
"outgoingEdgeId": "2V3HtAH5fSAm6fyYzCyotq"
}
],
"title": "Start",
"graphCoordinates": { "x": 0, "y": 0 }
},
{
"id": "8KLYVvRVGVHRQGJHHe2YPv",
"graphCoordinates": { "x": 362, "y": 96 },
"title": "Block #1",
"steps": [
{
"id": "s7QbNUSgojnka9v9LX7Tp7L",
"blockId": "8KLYVvRVGVHRQGJHHe2YPv",
"type": "Set variable",
"options": {
"variableId": "htYvG7crtdjpsZ6XKTh1PM",
"expressionToEvaluate": "Baptiste"
},
"outgoingEdgeId": "7kKfQWo6xFy97cTwV7B2w7"
}
]
},
{
"id": "4H8ucvLjTiQ7sAyB23Huka",
"graphCoordinates": { "x": 723, "y": 203 },
"title": "Block #2",
"steps": [
{
"id": "s4xoCc33mHyKv6hbVWd8MLo",
"blockId": "4H8ucvLjTiQ7sAyB23Huka",
"type": "text",
"content": {
"html": "<div>What&#x27;s your name?</div>",
"richText": [
{ "type": "p", "children": [{ "text": "What's your name?" }] }
],
"plainText": "What's your name?"
}
},
{
"id": "s5AjAPTMbUbhYxVTjSNwQuJ",
"blockId": "4H8ucvLjTiQ7sAyB23Huka",
"type": "text input",
"options": {
"isLong": false,
"labels": {
"button": "Send",
"placeholder": "Type your answer..."
},
"variableId": "htYvG7crtdjpsZ6XKTh1PM"
}
}
]
}
],
"variables": [{ "id": "htYvG7crtdjpsZ6XKTh1PM", "name": "Name" }],
"edges": [
{
"from": {
"blockId": "cAvp3oQUNYcANvcEQEVSpD",
"stepId": "bAkhPioPM1uAda6K2aJzHD"
},
"to": { "blockId": "8KLYVvRVGVHRQGJHHe2YPv" },
"id": "2V3HtAH5fSAm6fyYzCyotq"
},
{
"from": {
"blockId": "8KLYVvRVGVHRQGJHHe2YPv",
"stepId": "s7QbNUSgojnka9v9LX7Tp7L"
},
"to": { "blockId": "4H8ucvLjTiQ7sAyB23Huka" },
"id": "7kKfQWo6xFy97cTwV7B2w7"
}
],
"theme": {
"chat": {
"inputs": {
"color": "#303235",
"backgroundColor": "#FFFFFF",
"placeholderColor": "#9095A0"
},
"buttons": { "color": "#FFFFFF", "backgroundColor": "#0042DA" },
"hostBubbles": { "color": "#303235", "backgroundColor": "#F7F8FF" },
"guestBubbles": { "color": "#FFFFFF", "backgroundColor": "#FF8E21" }
},
"general": { "font": "Open Sans", "background": { "type": "None" } }
},
"settings": {
"general": {
"isBrandingEnabled": true,
"isInputPrefillEnabled": 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
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 20 additions & 4 deletions apps/builder/playwright/tests/settings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import test, { expect } from '@playwright/test'
import { defaultTextInputOptions } from 'models'
import path from 'path'
import { generate } from 'short-uuid'
import { importTypebotInDatabase } from '../services/database'
Expand All @@ -9,11 +10,12 @@ test.describe.parallel('Settings page', () => {
test('should reflect change in real-time', async ({ page }) => {
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
)

await page.goto(`/typebots/${typebotId}/settings`)
await expect(
typebotViewer(page).locator('a:has-text("Made with Typebot")')
Expand All @@ -23,18 +25,32 @@ test.describe.parallel('Settings page', () => {
await expect(
typebotViewer(page).locator('a:has-text("Made with Typebot")')
).toBeHidden()

await page.click('text=Create new session on page refresh')
await expect(
page.locator('input[type="checkbox"] >> nth=-1')
).toHaveAttribute('checked', '')

await expect(
typebotViewer(page).locator('input[value="Baptiste"]')
).toBeVisible()
await page.click('text=Prefill input')
await page.click('text=Theme')
await page.waitForTimeout(1000)
await page.click('text=Settings')
await expect(
typebotViewer(page).locator(
`input[placeholder="${defaultTextInputOptions.labels.placeholder}"]`
)
).toHaveValue('')
})
})

test.describe('Typing emulation', () => {
test('should be fillable', async ({ page }) => {
const typebotId = generate()
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
Expand All @@ -57,7 +73,7 @@ test.describe.parallel('Settings page', () => {
const imageUrl = 'https://www.baptistearno.com/images/site-preview.png'
const typebotId = 'metadata-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
Expand Down Expand Up @@ -101,7 +117,7 @@ test.describe.parallel('Settings page', () => {
test("can't remove branding", async ({ page }) => {
const typebotId = 'free-branding-typebot'
await importTypebotInDatabase(
path.join(__dirname, '../fixtures/typebots/theme.json'),
path.join(__dirname, '../fixtures/typebots/settings.json'),
{
id: typebotId,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const InputChatStep = ({

const { variableId } = step.options
const defaultValue =
variableId && typebot.variables.find(byId(variableId))?.value
typebot.settings.general.isInputPrefillEnabled ?? true
? variableId && typebot.variables.find(byId(variableId))?.value
: undefined

const handleSubmit = (content: string) => {
setAnswer(content)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Avatar } from 'components/avatars/Avatar'
import React from 'react'
import React, { useState } from 'react'
import { CSSTransition } from 'react-transition-group'

interface Props {
Expand All @@ -15,14 +15,16 @@ export const GuestBubble = ({
avatarSrc,
onClick,
}: Props): JSX.Element => {
const [content] = useState(message)

return (
<CSSTransition classNames="bubble" timeout={1000}>
<div className="flex justify-end mb-2 items-end" onClick={onClick}>
<span
className="px-4 py-2 rounded-lg mr-2 whitespace-pre-wrap max-w-full typebot-guest-bubble cursor-pointer hover:brightness-90 active:brightness-75"
data-testid="guest-bubble"
>
{message}
{content}
</span>
{showAvatar && <Avatar avatarSrc={avatarSrc} />}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { useTypebot } from 'contexts/TypebotContext'
import { BubbleStepType, TextBubbleStep } from 'models'
import { computeTypingTimeout } from 'services/chat'
Expand All @@ -23,10 +23,8 @@ export const TextBubble = ({ step, onTransitionEnd }: Props) => {
const messageContainer = useRef<HTMLDivElement | null>(null)
const [isTyping, setIsTyping] = useState(true)

const content = useMemo(
() => parseVariables(typebot.variables)(step.content.html),
// eslint-disable-next-line react-hooks/exhaustive-deps
[typebot.variables]
const [content] = useState(
parseVariables(typebot.variables)(step.content.html)
)

useEffect(() => {
Expand Down
7 changes: 6 additions & 1 deletion packages/models/src/typebot/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type Settings = {
export type GeneralSettings = {
isBrandingEnabled: boolean
isNewResultOnRefreshEnabled?: boolean
isInputPrefillEnabled?: boolean
}

export type TypingEmulation = {
Expand All @@ -23,7 +24,11 @@ export type Metadata = {
}

export const defaultSettings: Settings = {
general: { isBrandingEnabled: true, isNewResultOnRefreshEnabled: false },
general: {
isBrandingEnabled: true,
isNewResultOnRefreshEnabled: false,
isInputPrefillEnabled: true,
},
typingEmulation: { enabled: true, speed: 300, maxDelay: 1.5 },
metadata: {
description:
Expand Down

2 comments on commit 9123977

@vercel
Copy link

@vercel vercel bot commented on 9123977 Mar 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io

Please sign in to comment.