2
0
Files
bot/apps/builder/src/features/blocks/logic/setVariable/components/SetVariableSettings.tsx
2024-06-27 08:22:22 +02:00

315 lines
9.3 KiB
TypeScript

import { Alert, AlertIcon, FormLabel, Stack, Tag, Text } from '@chakra-ui/react'
import { CodeEditor } from '@/components/inputs/CodeEditor'
import { SetVariableBlock, Variable } from '@typebot.io/schemas'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { Select } from '@/components/inputs/Select'
import { WhatsAppLogo } from '@/components/logos/WhatsAppLogo'
import {
defaultSetVariableOptions,
hiddenTypes,
sessionOnlySetVariableOptions,
valueTypes,
} from '@typebot.io/schemas/features/blocks/logic/setVariable/constants'
import { TextInput, Textarea } from '@/components/inputs'
import { isDefined } from '@typebot.io/lib'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { isInputBlock } from '@typebot.io/schemas/helpers'
import { RadioButtons } from '@/components/inputs/RadioButtons'
type Props = {
options: SetVariableBlock['options']
onOptionsChange: (options: SetVariableBlock['options']) => void
}
const setVarTypes = valueTypes.filter(
(type) => !hiddenTypes.includes(type as (typeof hiddenTypes)[number])
)
export const SetVariableSettings = ({ options, onOptionsChange }: Props) => {
const { typebot, updateVariable } = useTypebot()
const selectedVariable = typebot?.variables.find(
(variable) => variable.id === options?.variableId
)
const updateVariableId = (variable?: Pick<Variable, 'id'>) =>
onOptionsChange({
...options,
variableId: variable?.id,
})
const updateValueType = (type?: string) =>
onOptionsChange({
...options,
type: type as NonNullable<SetVariableBlock['options']>['type'],
})
const updateIsSessionVariable = (isSavingInResults: boolean) => {
if (!selectedVariable?.id) return
updateVariable(selectedVariable.id, {
isSessionVariable: !isSavingInResults,
})
}
const isSessionOnly =
options?.type &&
sessionOnlySetVariableOptions.includes(
options.type as (typeof sessionOnlySetVariableOptions)[number]
)
const isLinkedToAnswer =
options?.variableId &&
typebot?.groups.some((g) =>
g.blocks.some(
(b) => isInputBlock(b) && b.options?.variableId === options.variableId
)
)
return (
<Stack spacing={4}>
<Stack>
<FormLabel mb="0" htmlFor="variable-search">
Search or create variable:
</FormLabel>
<VariableSearchInput
onSelectVariable={updateVariableId}
initialVariableId={options?.variableId}
id="variable-search"
/>
</Stack>
<Stack spacing="4">
<Stack>
<Text mb="0" fontWeight="medium">
Value:
</Text>
<Select
selectedItem={options?.type ?? defaultSetVariableOptions.type}
items={setVarTypes.map((type) => ({
label: type,
value: type,
icon:
type === 'Contact name' || type === 'Phone number' ? (
<WhatsAppLogo />
) : undefined,
}))}
onSelect={updateValueType}
/>
</Stack>
{selectedVariable && !isSessionOnly && !isLinkedToAnswer && (
<SwitchWithLabel
key={selectedVariable.id}
label="Save in results?"
moreInfoContent="By default, the variable is saved only for the user chat session. Check this option if you want to also store the variable in the typebot Results table."
initialValue={!selectedVariable.isSessionVariable}
onCheckChange={updateIsSessionVariable}
/>
)}
<SetVariableValue options={options} onOptionsChange={onOptionsChange} />
</Stack>
</Stack>
)
}
const SetVariableValue = ({
options,
onOptionsChange,
}: {
options: SetVariableBlock['options']
onOptionsChange: (options: SetVariableBlock['options']) => void
}): JSX.Element | null => {
const updateExpression = (expressionToEvaluate: string) =>
onOptionsChange({
...options,
type: isDefined(options?.type) ? 'Custom' : undefined,
expressionToEvaluate,
})
const updateClientExecution = (isExecutedOnClient: boolean) =>
onOptionsChange({
...options,
isExecutedOnClient,
})
const updateListVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || (options.type !== 'Pop' && options.type !== 'Shift')) return
onOptionsChange({
...options,
saveItemInVariableId: variable?.id,
})
}
const updateItemVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return
onOptionsChange({
...options,
mapListItemParams: {
...options.mapListItemParams,
baseItemVariableId: variable?.id,
},
})
}
const updateBaseListVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return
onOptionsChange({
...options,
mapListItemParams: {
...options.mapListItemParams,
baseListVariableId: variable?.id,
},
})
}
const updateTargetListVariableId = (variable?: Pick<Variable, 'id'>) => {
if (!options || options.type !== 'Map item with same index') return
onOptionsChange({
...options,
mapListItemParams: {
...options.mapListItemParams,
targetListVariableId: variable?.id,
},
})
}
const updateItem = (item: string) => {
if (!options || options.type !== 'Append value(s)') return
onOptionsChange({
...options,
item,
})
}
const updateIsCode = (radio: 'Text' | 'Code') => {
if (options?.type && options.type !== 'Custom') return
onOptionsChange({
...options,
isCode: radio === 'Code',
})
}
switch (options?.type) {
case 'Custom':
case undefined:
return (
<>
<SwitchWithLabel
label="Execute on client?"
moreInfoContent="Check this if you need access to client-only variables like `window` or `document`."
initialValue={
options?.isExecutedOnClient ??
defaultSetVariableOptions.isExecutedOnClient
}
onCheckChange={updateClientExecution}
/>
<Stack>
<RadioButtons
size="sm"
options={['Text', 'Code']}
defaultValue={
options?.isCode ?? defaultSetVariableOptions.isCode
? 'Code'
: 'Text'
}
onSelect={updateIsCode}
/>
{options?.isCode ? (
<CodeEditor
defaultValue={options?.expressionToEvaluate ?? ''}
onChange={updateExpression}
lang="javascript"
/>
) : (
<Textarea
defaultValue={options?.expressionToEvaluate ?? ''}
onChange={updateExpression}
width="full"
/>
)}
</Stack>
</>
)
case 'Pop':
case 'Shift':
return (
<VariableSearchInput
initialVariableId={options.saveItemInVariableId}
onSelectVariable={updateListVariableId}
placeholder={
options.type === 'Shift' ? 'Shifted item' : 'Popped item'
}
/>
)
case 'Map item with same index': {
return (
<Stack p="2" rounded="md" borderWidth={1}>
<VariableSearchInput
initialVariableId={options.mapListItemParams?.baseItemVariableId}
onSelectVariable={updateItemVariableId}
placeholder="Base item"
/>
<VariableSearchInput
initialVariableId={options.mapListItemParams?.baseListVariableId}
onSelectVariable={updateBaseListVariableId}
placeholder="Base list"
/>
<VariableSearchInput
initialVariableId={options.mapListItemParams?.targetListVariableId}
onSelectVariable={updateTargetListVariableId}
placeholder="Target list"
/>
</Stack>
)
}
case 'Append value(s)': {
return <Textarea defaultValue={options.item} onChange={updateItem} />
}
case 'Moment of the day': {
return (
<Alert fontSize="sm">
<AlertIcon />
<Text>
Will return either <Tag size="sm">morning</Tag>,{' '}
<Tag size="sm">afternoon</Tag>,<Tag size="sm">evening</Tag> or{' '}
<Tag size="sm">night</Tag> based on the current user time.
</Text>
</Alert>
)
}
case 'Environment name': {
return (
<Alert fontSize="sm">
<AlertIcon />
<Text>
Will return either <Tag size="sm">web</Tag> or{' '}
<Tag size="sm">whatsapp</Tag>.
</Text>
</Alert>
)
}
case 'Now':
case 'Yesterday':
case 'Tomorrow': {
return (
<TextInput
direction="row"
label="Timezone"
onChange={(timeZone) => onOptionsChange({ ...options, timeZone })}
defaultValue={options.timeZone}
placeholder="Europe/Paris"
/>
)
}
case 'Contact name':
case 'Phone number':
case 'Random ID':
case 'User ID':
case 'Today':
case 'Result ID':
case 'Empty':
case 'Transcript':
return null
}
}