Skip to content

Commit

Permalink
✨ Add attachments option to text input (#1608)
Browse files Browse the repository at this point in the history
Closes #854
  • Loading branch information
baptisteArno authored Jun 26, 2024
1 parent 80da7af commit 6db0464
Show file tree
Hide file tree
Showing 88 changed files with 2,958 additions and 734 deletions.
2 changes: 1 addition & 1 deletion apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
"@uiw/codemirror-theme-tokyo-night": "4.21.24",
"@uiw/react-codemirror": "4.21.24",
"@upstash/ratelimit": "0.4.3",
"@upstash/redis": "1.22.0",
"@use-gesture/react": "10.2.27",
"browser-image-compression": "2.0.2",
"canvas-confetti": "1.6.0",
Expand All @@ -69,6 +68,7 @@
"google-auth-library": "8.9.0",
"google-spreadsheet": "4.1.1",
"immer": "10.0.2",
"ioredis": "^5.4.1",
"isolated-vm": "4.7.2",
"jsonwebtoken": "9.0.1",
"ky": "1.2.4",
Expand Down
7 changes: 5 additions & 2 deletions apps/builder/src/components/EditableEmojiOrImageIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import {
useColorModeValue,
Portal,
} from '@chakra-ui/react'
import React from 'react'
import React, { RefObject } from 'react'
import { EmojiOrImageIcon } from './EmojiOrImageIcon'
import { ImageUploadContent } from './ImageUploadContent'
import { FilePathUploadProps } from '@/features/upload/api/generateUploadUrl'
import { useTranslate } from '@tolgee/react'
import { useParentModal } from '@/features/graph/providers/ParentModalProvider'

type Props = {
uploadFileProps: FilePathUploadProps
icon?: string | null
parentModalRef?: RefObject<HTMLElement | null> | undefined
onChangeIcon: (icon: string) => void
boxSize?: string
}
Expand All @@ -28,6 +30,7 @@ export const EditableEmojiOrImageIcon = ({
boxSize,
}: Props) => {
const { t } = useTranslate()
const { ref: parentModalRef } = useParentModal()
const bg = useColorModeValue('gray.100', 'gray.700')

return (
Expand Down Expand Up @@ -56,7 +59,7 @@ export const EditableEmojiOrImageIcon = ({
</PopoverTrigger>
</Flex>
</Tooltip>
<Portal>
<Portal containerRef={parentModalRef}>
<PopoverContent p="2">
<ImageUploadContent
uploadFileProps={uploadFileProps}
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/src/features/blocks/inputs/date/date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.describe('Date input block', () => {
'date'
)
await page.locator('[data-testid="from-date"]').fill('2021-01-01')
await page.locator('form').getByRole('button').click()
await page.getByLabel('Send').click()
await expect(page.locator('text="01/01/2021"')).toBeVisible()

await page.click(`text=Pick a date`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ test('options should work', async ({ page }) => {
await page
.locator(`input[type="file"]`)
.setInputFiles([getTestAsset('avatar.jpg')])
await expect(page.locator(`text=File uploaded`)).toBeVisible()
await expect(
page.getByRole('img', { name: 'Attached image 1' })
).toBeVisible()
await page.click('text="Collect file"')
await page.click('text="Required?"')
await page.click('text="Allow multiple files?"')
Expand All @@ -46,9 +48,11 @@ test('options should work', async ({ page }) => {
getTestAsset('avatar.jpg'),
getTestAsset('avatar.jpg'),
])
await expect(page.locator(`text="3"`)).toBeVisible()
await expect(page.getByRole('img', { name: 'avatar.jpg' })).toHaveCount(3)
await page.locator('text="Go"').click()
await expect(page.locator(`text="3 files uploaded"`)).toBeVisible()
await expect(
page.getByRole('img', { name: 'Attached image 1' })
).toBeVisible()
})

test.describe('Free workspace', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import React from 'react'
import { Text } from '@chakra-ui/react'
import { Stack, Text } from '@chakra-ui/react'
import { WithVariableContent } from '@/features/graph/components/nodes/block/WithVariableContent'
import { TextInputBlock } from '@typebot.io/schemas'
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { SetVariableLabel } from '@/components/SetVariableLabel'

type Props = {
options: TextInputBlock['options']
}

export const TextInputNodeContent = ({ options }: Props) => {
const { typebot } = useTypebot()
const attachmentVariableId =
typebot &&
options?.attachments?.isEnabled &&
options?.attachments.saveVariableId
if (options?.variableId)
return (
<WithVariableContent
variableId={options?.variableId}
h={options.isLong ? '100px' : 'auto'}
/>
<Stack w="calc(100% - 25px)">
<WithVariableContent
variableId={options?.variableId}
h={options.isLong ? '100px' : 'auto'}
/>
{attachmentVariableId && (
<SetVariableLabel
variables={typebot.variables}
variableId={attachmentVariableId}
/>
)}
</Stack>
)
return (
<Text color={'gray.500'} h={options?.isLong ? '100px' : 'auto'}>
{options?.labels?.placeholder ??
defaultTextInputOptions.labels.placeholder}
</Text>
<Stack>
<Text color={'gray.500'} h={options?.isLong ? '100px' : 'auto'}>
{options?.labels?.placeholder ??
defaultTextInputOptions.labels.placeholder}
</Text>
{attachmentVariableId && (
<SetVariableLabel
variables={typebot.variables}
variableId={attachmentVariableId}
/>
)}
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { DropdownList } from '@/components/DropdownList'
import { SwitchWithRelatedSettings } from '@/components/SwitchWithRelatedSettings'
import { TextInput } from '@/components/inputs'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { FormLabel, Stack } from '@chakra-ui/react'
import { useTranslate } from '@tolgee/react'
import { TextInputBlock, Variable } from '@typebot.io/schemas'
import { fileVisibilityOptions } from '@typebot.io/schemas/features/blocks/inputs/file/constants'
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
import React from 'react'

Expand All @@ -14,44 +17,95 @@ type Props = {

export const TextInputSettings = ({ options, onOptionsChange }: Props) => {
const { t } = useTranslate()
const handlePlaceholderChange = (placeholder: string) =>
const updatePlaceholder = (placeholder: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, placeholder } })
const handleButtonLabelChange = (button: string) =>

const updateButtonLabel = (button: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, button } })
const handleLongChange = (isLong: boolean) =>

const updateIsLong = (isLong: boolean) =>
onOptionsChange({ ...options, isLong })
const handleVariableChange = (variable?: Variable) =>

const updateVariableId = (variable?: Variable) =>
onOptionsChange({ ...options, variableId: variable?.id })

const updateAttachmentsEnabled = (isEnabled: boolean) =>
onOptionsChange({
...options,
attachments: { ...options?.attachments, isEnabled },
})

const updateAttachmentsSaveVariableId = (variable?: Pick<Variable, 'id'>) =>
onOptionsChange({
...options,
attachments: { ...options?.attachments, saveVariableId: variable?.id },
})

const updateVisibility = (
visibility: (typeof fileVisibilityOptions)[number]
) =>
onOptionsChange({
...options,
attachments: { ...options?.attachments, visibility },
})

return (
<Stack spacing={4}>
<SwitchWithLabel
label={t('blocks.inputs.text.settings.longText.label')}
initialValue={options?.isLong ?? defaultTextInputOptions.isLong}
onCheckChange={handleLongChange}
onCheckChange={updateIsLong}
/>
<TextInput
label={t('blocks.inputs.settings.placeholder.label')}
defaultValue={
options?.labels?.placeholder ??
defaultTextInputOptions.labels.placeholder
}
onChange={handlePlaceholderChange}
onChange={updatePlaceholder}
/>
<TextInput
label={t('blocks.inputs.settings.button.label')}
defaultValue={
options?.labels?.button ?? defaultTextInputOptions.labels.button
}
onChange={handleButtonLabelChange}
onChange={updateButtonLabel}
/>
<SwitchWithRelatedSettings
label={'Allow attachments'}
initialValue={
options?.attachments?.isEnabled ??
defaultTextInputOptions.attachments.isEnabled
}
onCheckChange={updateAttachmentsEnabled}
>
<Stack>
<FormLabel mb="0" htmlFor="variable">
Save the URLs in a variable:
</FormLabel>
<VariableSearchInput
initialVariableId={options?.attachments?.saveVariableId}
onSelectVariable={updateAttachmentsSaveVariableId}
/>
</Stack>
<DropdownList
label="Visibility:"
moreInfoTooltip='This setting determines who can see the uploaded files. "Public" means that anyone who has the link can see the files. "Private" means that only a members of this workspace can see the files.'
currentItem={
options?.attachments?.visibility ??
defaultTextInputOptions.attachments.visibility
}
onItemSelect={updateVisibility}
items={fileVisibilityOptions}
/>
</SwitchWithRelatedSettings>
<Stack>
<FormLabel mb="0" htmlFor="variable">
{t('blocks.inputs.settings.saveAnswer.label')}
</FormLabel>
<VariableSearchInput
initialVariableId={options?.variableId}
onSelectVariable={handleVariableChange}
onSelectVariable={updateVariableId}
/>
</Stack>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseDefaultGroupWithBlock } from '@typebot.io/playwright/databaseHelpe
import { createId } from '@paralleldrive/cuid2'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { defaultTextInputOptions } from '@typebot.io/schemas/features/blocks/inputs/text/constants'
import { getTestAsset } from '@/test/utils/playwright'

test.describe.parallel('Text input block', () => {
test('options should work', async ({ page }) => {
Expand Down Expand Up @@ -37,4 +38,48 @@ test.describe.parallel('Text input block', () => {
).toBeVisible()
await expect(page.getByRole('button', { name: 'Go' })).toBeVisible()
})

test('hey boy', async ({ page }) => {
const typebotId = createId()
await createTypebots([
{
id: typebotId,
...parseDefaultGroupWithBlock({
type: InputBlockType.TEXT,
}),
},
])

await page.goto(`/typebots/${typebotId}/edit`)

await page.click(`text=${defaultTextInputOptions.labels.placeholder}`)
await page.getByText('Allow attachments').click()
await page.locator('[data-testid="variables-input"]').first().click()
await page.getByText('var1').click()
await page.getByRole('button', { name: 'Test' }).click()
await page
.getByPlaceholder('Type your answer...')
.fill('Help me with these')
await page.getByLabel('Add attachments').click()
await expect(page.getByRole('menuitem', { name: 'Document' })).toBeVisible()
await expect(
page.getByRole('menuitem', { name: 'Photos & videos' })
).toBeVisible()
await page
.locator('#document-upload')
.setInputFiles(getTestAsset('typebots/theme.json'))
await expect(page.getByText('theme.json')).toBeVisible()
await page
.locator('#photos-upload')
.setInputFiles([getTestAsset('avatar.jpg'), getTestAsset('avatar.jpg')])
await expect(page.getByRole('img', { name: 'avatar.jpg' })).toHaveCount(2)
await page.getByRole('img', { name: 'avatar.jpg' }).first().hover()
await page.getByLabel('Remove attachment').first().click()
await expect(page.getByRole('img', { name: 'avatar.jpg' })).toHaveCount(1)
await page.getByLabel('Send').click()
await expect(
page.getByRole('img', { name: 'Attached image 1' })
).toBeVisible()
await expect(page.getByText('Help me with these')).toBeVisible()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ test.describe.parallel('Google sheets integration', () => {

await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('button >> text="Email"')
await page.getByRole('menuitem', { name: 'Email' }).click()
await page.click('[aria-label="Insert a variable"]')
await page.click('button >> text="Email" >> nth=1')
await page.getByRole('menuitem', { name: 'Email' }).last().click()

await page.click('text=Add a value')
await page.click('text=Select a column')
Expand Down Expand Up @@ -61,11 +61,11 @@ test.describe.parallel('Google sheets integration', () => {
await page.getByRole('button', { name: 'Row(s) to update' }).click()
await page.getByRole('button', { name: 'Add filter rule' }).click()
await page.click('text=Select a column')
await page.click('button >> text="Email"')
await page.getByRole('menuitem', { name: 'Email' }).click()
await page.getByRole('button', { name: 'Select an operator' }).click()
await page.getByRole('menuitem', { name: 'Equal to' }).click()
await page.click('[aria-label="Insert a variable"]')
await page.click('button >> text="Email" >> nth=1')
await page.getByRole('menuitem', { name: 'Email' }).last().click()

await page.getByRole('button', { name: 'Cells to update' }).click()
await page.click('text=Add a value')
Expand Down Expand Up @@ -106,11 +106,11 @@ test.describe.parallel('Google sheets integration', () => {
await page.getByRole('button', { name: 'Select row(s)' }).click()
await page.getByRole('button', { name: 'Add filter rule' }).click()
await page.click('text=Select a column')
await page.click('button >> text="Email"')
await page.getByRole('menuitem', { name: 'Email' }).click()
await page.getByRole('button', { name: 'Select an operator' }).click()
await page.getByRole('menuitem', { name: 'Equal to' }).click()
await page.click('[aria-label="Insert a variable"]')
await page.click('button >> text="Email" >> nth=1')
await page.getByRole('menuitem', { name: 'Email' }).last().click()

await page.getByRole('button', { name: 'Add filter rule' }).click()
await page.getByRole('button', { name: 'AND', exact: true }).click()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test.describe('Condition block', () => {
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'
)
await page.click('button:has-text("Age")')
await page.getByRole('menuitem', { name: 'Age' }).click()
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
await page.fill('input[placeholder="Type a number..."]', '80')
Expand All @@ -31,7 +31,7 @@ test.describe('Condition block', () => {
':nth-match(input[placeholder="Search for a variable"], 2)',
'Age'
)
await page.click('button:has-text("Age")')
await page.getByRole('menuitem', { name: 'Age' }).click()
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Less than")', { force: true })
await page.fill(
Expand All @@ -44,7 +44,7 @@ test.describe('Condition block', () => {
'input[placeholder="Search for a variable"] >> nth=-1',
'Age'
)
await page.click('button:has-text("Age")')
await page.getByRole('menuitem', { name: 'Age' }).click()
await page.click('button:has-text("Select an operator")')
await page.click('button:has-text("Greater than")', { force: true })
await page.fill('input[placeholder="Type a number..."]', '20')
Expand Down
Loading

0 comments on commit 6db0464

Please sign in to comment.