feat(editor): ✨ Add file upload input
This commit is contained in:
@ -20,6 +20,7 @@ import {
|
||||
SendEmailIcon,
|
||||
StarIcon,
|
||||
TextIcon,
|
||||
UploadIcon,
|
||||
WebhookIcon,
|
||||
} from 'assets/icons'
|
||||
import {
|
||||
@ -68,6 +69,8 @@ export const BlockIcon = ({ type, ...props }: BlockIconProps) => {
|
||||
return <CreditCardIcon color="orange.500" {...props} />
|
||||
case InputBlockType.RATING:
|
||||
return <StarIcon color="orange.500" {...props} />
|
||||
case InputBlockType.FILE:
|
||||
return <UploadIcon color="orange.500" {...props} />
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return <EditIcon color="purple.500" {...props} />
|
||||
case LogicBlockType.CONDITION:
|
||||
|
@ -43,6 +43,12 @@ export const BlockTypeLabel = ({ type }: Props): JSX.Element => {
|
||||
return <Text>Payment</Text>
|
||||
case InputBlockType.RATING:
|
||||
return <Text>Rating</Text>
|
||||
case InputBlockType.FILE:
|
||||
return (
|
||||
<Tooltip label="Upload Files">
|
||||
<Text>File</Text>
|
||||
</Tooltip>
|
||||
)
|
||||
case LogicBlockType.SET_VARIABLE:
|
||||
return <Text>Set variable</Text>
|
||||
case LogicBlockType.CONDITION:
|
||||
|
@ -19,12 +19,14 @@ type Props = {
|
||||
isReadOnly?: boolean
|
||||
debounceTimeout?: number
|
||||
withVariableButton?: boolean
|
||||
height?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
export const CodeEditor = ({
|
||||
value,
|
||||
lang,
|
||||
onChange,
|
||||
height = '250px',
|
||||
withVariableButton = true,
|
||||
isReadOnly = false,
|
||||
debounceTimeout = 1000,
|
||||
@ -92,7 +94,7 @@ export const CodeEditor = ({
|
||||
extensions.push(
|
||||
EditorView.theme({
|
||||
'&': { maxHeight: '500px' },
|
||||
'.cm-gutter,.cm-content': { minHeight: isReadOnly ? '0' : '250px' },
|
||||
'.cm-gutter,.cm-content': { minHeight: isReadOnly ? '0' : height },
|
||||
'.cm-scroller': { overflow: 'auto' },
|
||||
})
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
WithVariableContent,
|
||||
} from './contents'
|
||||
import { ConfigureContent } from './contents/ConfigureContent'
|
||||
import { FileInputContent } from './contents/FileInputContent'
|
||||
import { ImageBubbleContent } from './contents/ImageBubbleContent'
|
||||
import { PaymentInputContent } from './contents/PaymentInputContent'
|
||||
import { PlaceholderContent } from './contents/PlaceholderContent'
|
||||
@ -80,6 +81,9 @@ export const BlockNodeContent = ({ block, indices }: Props): JSX.Element => {
|
||||
case InputBlockType.RATING: {
|
||||
return <RatingInputContent block={block} />
|
||||
}
|
||||
case InputBlockType.FILE: {
|
||||
return <FileInputContent options={block.options} />
|
||||
}
|
||||
case LogicBlockType.SET_VARIABLE: {
|
||||
return <SetVariableContent block={block} />
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { Text } from '@chakra-ui/react'
|
||||
import { FileInputOptions } from 'models'
|
||||
|
||||
type Props = {
|
||||
options: FileInputOptions
|
||||
}
|
||||
|
||||
export const FileInputContent = ({ options: { isMultipleAllowed } }: Props) => (
|
||||
<Text noOfLines={0} pr="6">
|
||||
Collect {isMultipleAllowed ? 'files' : 'file'}
|
||||
</Text>
|
||||
)
|
@ -29,6 +29,7 @@ import {
|
||||
import { ChoiceInputSettingsBody } from './bodies/ChoiceInputSettingsBody'
|
||||
import { CodeSettings } from './bodies/CodeSettings'
|
||||
import { ConditionSettingsBody } from './bodies/ConditionSettingsBody'
|
||||
import { FileInputSettings } from './bodies/FileInputSettings'
|
||||
import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
||||
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
||||
import { PaymentSettings } from './bodies/PaymentSettings'
|
||||
@ -173,6 +174,14 @@ export const BlockSettings = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case InputBlockType.FILE: {
|
||||
return (
|
||||
<FileInputSettings
|
||||
options={block.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case LogicBlockType.SET_VARIABLE: {
|
||||
return (
|
||||
<SetVariableSettings
|
||||
|
@ -0,0 +1,64 @@
|
||||
import { FormLabel, Stack } from '@chakra-ui/react'
|
||||
import { CodeEditor } from 'components/shared/CodeEditor'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { Input } from 'components/shared/Textbox'
|
||||
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
|
||||
import { FileInputOptions, Variable } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
options: FileInputOptions
|
||||
onOptionsChange: (options: FileInputOptions) => void
|
||||
}
|
||||
|
||||
export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const handleButtonLabelChange = (button: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, button } })
|
||||
const handlePlaceholderLabelChange = (placeholder: string) =>
|
||||
onOptionsChange({ ...options, labels: { ...options.labels, placeholder } })
|
||||
const handleLongChange = (isMultipleAllowed: boolean) =>
|
||||
onOptionsChange({ ...options, isMultipleAllowed })
|
||||
const handleVariableChange = (variable?: Variable) =>
|
||||
onOptionsChange({ ...options, variableId: variable?.id })
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<SwitchWithLabel
|
||||
id="switch"
|
||||
label="Allow multiple files?"
|
||||
initialValue={options.isMultipleAllowed}
|
||||
onCheckChange={handleLongChange}
|
||||
/>
|
||||
<Stack>
|
||||
<FormLabel mb="0">Placeholder:</FormLabel>
|
||||
<CodeEditor
|
||||
lang="html"
|
||||
onChange={handlePlaceholderLabelChange}
|
||||
value={options.labels.placeholder}
|
||||
height={'100px'}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="button">
|
||||
Button label:
|
||||
</FormLabel>
|
||||
<Input
|
||||
id="button"
|
||||
defaultValue={options.labels.button}
|
||||
onChange={handleButtonLabelChange}
|
||||
withVariableButton={false}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<FormLabel mb="0" htmlFor="variable">
|
||||
Save upload URL{options.isMultipleAllowed ? 's' : ''} in a variable:
|
||||
</FormLabel>
|
||||
<VariableSearchInput
|
||||
initialVariableId={options.variableId}
|
||||
onSelectVariable={handleVariableChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -171,7 +171,12 @@ export const TypebotHeader = () => {
|
||||
<HStack right="40px" pos="absolute" display={['none', 'flex']}>
|
||||
<CollaborationMenuButton />
|
||||
{router.pathname.includes('/edit') && isNotDefined(rightPanel) && (
|
||||
<Button onClick={handlePreviewClick}>Preview</Button>
|
||||
<Button
|
||||
onClick={handlePreviewClick}
|
||||
isLoading={isNotDefined(typebot)}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
)}
|
||||
<PublishButton />
|
||||
</HStack>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button, ButtonProps, chakra } from '@chakra-ui/react'
|
||||
import React, { ChangeEvent, useState } from 'react'
|
||||
import { compressFile, uploadFile } from 'services/utils'
|
||||
import { compressFile } from 'services/utils'
|
||||
import { uploadFiles } from 'utils'
|
||||
|
||||
type UploadButtonProps = {
|
||||
filePath: string
|
||||
@ -20,11 +21,15 @@ export const UploadButton = ({
|
||||
if (!e.target?.files) return
|
||||
setIsUploading(true)
|
||||
const file = e.target.files[0]
|
||||
const { url } = await uploadFile(
|
||||
await compressFile(file),
|
||||
filePath + (includeFileName ? `/${file.name}` : '')
|
||||
)
|
||||
if (url) onFileUploaded(url)
|
||||
const urls = await uploadFiles({
|
||||
files: [
|
||||
{
|
||||
file: await compressFile(file),
|
||||
path: filePath + (includeFileName ? `/${file.name}` : ''),
|
||||
},
|
||||
],
|
||||
})
|
||||
if (urls.length) onFileUploaded(urls[0])
|
||||
setIsUploading(false)
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,6 @@ export const TemplatesModal = ({ isOpen, onClose, onTypebotChoose }: Props) => {
|
||||
const fetchTemplate = async (template: TemplateProps) => {
|
||||
setSelectedTemplate(template)
|
||||
const { data, error } = await sendRequest(`/templates/${template.fileName}`)
|
||||
console.log(data, error)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
setTypebot(data as Typebot)
|
||||
|
Reference in New Issue
Block a user