feat(editor): ✨ Add send email integration
This commit is contained in:
@ -34,6 +34,7 @@ import { GoogleAnalyticsSettings } from './bodies/GoogleAnalyticsSettings'
|
||||
import { GoogleSheetsSettingsBody } from './bodies/GoogleSheetsSettingsBody'
|
||||
import { PhoneNumberSettingsBody } from './bodies/PhoneNumberSettingsBody'
|
||||
import { RedirectSettings } from './bodies/RedirectSettings'
|
||||
import { SendEmailSettings } from './bodies/SendEmailSettings/SendEmailSettings'
|
||||
import { SetVariableSettings } from './bodies/SetVariableSettings'
|
||||
import { WebhookSettings } from './bodies/WebhookSettings'
|
||||
|
||||
@ -213,6 +214,14 @@ export const StepSettings = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
case IntegrationStepType.EMAIL: {
|
||||
return (
|
||||
<SendEmailSettings
|
||||
options={step.options}
|
||||
onOptionsChange={handleOptionsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return <></>
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
||||
import { DropdownList } from 'components/shared/DropdownList'
|
||||
import { TableList, TableListItemProps } from 'components/shared/TableList'
|
||||
import { useTypebot } from 'contexts/TypebotContext'
|
||||
import { CredentialsType } from 'db'
|
||||
import {
|
||||
Cell,
|
||||
CredentialsType,
|
||||
ExtractingCell,
|
||||
GoogleSheetsAction,
|
||||
GoogleSheetsGetOptions,
|
||||
|
@ -0,0 +1,92 @@
|
||||
import { Stack, useDisclosure, Text } from '@chakra-ui/react'
|
||||
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
||||
import {
|
||||
InputWithVariableButton,
|
||||
TextareaWithVariableButton,
|
||||
} from 'components/shared/TextboxWithVariableButton'
|
||||
import { CredentialsType, SendEmailOptions } from 'models'
|
||||
import React from 'react'
|
||||
import { SmtpConfigModal } from './SmtpConfigModal'
|
||||
|
||||
type Props = {
|
||||
options: SendEmailOptions
|
||||
onOptionsChange: (options: SendEmailOptions) => void
|
||||
}
|
||||
|
||||
export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const handleCredentialsSelect = (credentialsId: string) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
credentialsId,
|
||||
})
|
||||
|
||||
const handleToChange = (recipientsStr: string) => {
|
||||
const recipients: string[] = recipientsStr
|
||||
.split(',')
|
||||
.map((str) => str.trim())
|
||||
onOptionsChange({
|
||||
...options,
|
||||
recipients,
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubjectChange = (subject: string) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
subject,
|
||||
})
|
||||
|
||||
const handleBodyChange = (body: string) =>
|
||||
onOptionsChange({
|
||||
...options,
|
||||
body,
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<Stack>
|
||||
<Text>From: </Text>
|
||||
<CredentialsDropdown
|
||||
type={CredentialsType.SMTP}
|
||||
currentCredentialsId={options.credentialsId}
|
||||
onCredentialsSelect={handleCredentialsSelect}
|
||||
onCreateNewClick={onOpen}
|
||||
defaultCredentialLabel={
|
||||
process.env.NEXT_PUBLIC_EMAIL_NOTIFICATIONS_FROM_EMAIL
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>To: </Text>
|
||||
<InputWithVariableButton
|
||||
onChange={handleToChange}
|
||||
initialValue={options.recipients.join(', ')}
|
||||
placeholder="email1@gmail.com, email2@gmail.com"
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>Subject: </Text>
|
||||
<InputWithVariableButton
|
||||
data-testid="subject-input"
|
||||
onChange={handleSubjectChange}
|
||||
initialValue={options.subject ?? ''}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text>Body: </Text>
|
||||
<TextareaWithVariableButton
|
||||
data-testid="body-input"
|
||||
minH="300px"
|
||||
onChange={handleBodyChange}
|
||||
initialValue={options.body ?? ''}
|
||||
/>
|
||||
</Stack>
|
||||
<SmtpConfigModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onNewCredentials={handleCredentialsSelect}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import { FormControl, FormLabel, HStack, Stack } from '@chakra-ui/react'
|
||||
import { isDefined } from '@udecode/plate-common'
|
||||
import { DebouncedInput } from 'components/shared/DebouncedInput'
|
||||
import { SmartNumberInput } from 'components/shared/SmartNumberInput'
|
||||
import { SwitchWithLabel } from 'components/shared/SwitchWithLabel'
|
||||
import { SmtpCredentialsData } from 'models'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
config: SmtpCredentialsData
|
||||
onConfigChange: (config: SmtpCredentialsData) => void
|
||||
}
|
||||
|
||||
export const SmtpConfigForm = ({ config, onConfigChange }: Props) => {
|
||||
const handleFromEmailChange = (email: string) =>
|
||||
onConfigChange({ ...config, from: { ...config.from, email } })
|
||||
const handleFromNameChange = (name: string) =>
|
||||
onConfigChange({ ...config, from: { ...config.from, name } })
|
||||
const handleHostChange = (host: string) => onConfigChange({ ...config, host })
|
||||
const handleUsernameChange = (username: string) =>
|
||||
onConfigChange({ ...config, username })
|
||||
const handlePasswordChange = (password: string) =>
|
||||
onConfigChange({ ...config, password })
|
||||
const handleTlsCheck = (isTlsEnabled: boolean) =>
|
||||
onConfigChange({ ...config, isTlsEnabled })
|
||||
const handlePortNumberChange = (port?: number) =>
|
||||
isDefined(port) && onConfigChange({ ...config, port })
|
||||
|
||||
return (
|
||||
<Stack as="form" spacing={4}>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>From email:</FormLabel>
|
||||
<DebouncedInput
|
||||
initialValue={config.from.email ?? ''}
|
||||
onChange={handleFromEmailChange}
|
||||
placeholder="notifications@provider.com"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>From name:</FormLabel>
|
||||
<DebouncedInput
|
||||
initialValue={config.from.name ?? ''}
|
||||
onChange={handleFromNameChange}
|
||||
placeholder="John Smith"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>Host:</FormLabel>
|
||||
<DebouncedInput
|
||||
initialValue={config.host ?? ''}
|
||||
onChange={handleHostChange}
|
||||
placeholder="mail.provider.com"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>Username / Email:</FormLabel>
|
||||
<DebouncedInput
|
||||
type="email"
|
||||
initialValue={config.username ?? ''}
|
||||
onChange={handleUsernameChange}
|
||||
placeholder="user@provider.com"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl isRequired>
|
||||
<FormLabel>Password:</FormLabel>
|
||||
<DebouncedInput
|
||||
type="password"
|
||||
initialValue={config.password ?? ''}
|
||||
onChange={handlePasswordChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<SwitchWithLabel
|
||||
id="Tls"
|
||||
label={'Use TLS?'}
|
||||
initialValue={config.isTlsEnabled ?? false}
|
||||
onCheckChange={handleTlsCheck}
|
||||
/>
|
||||
<FormControl as={HStack} justifyContent="space-between">
|
||||
<FormLabel mb="0">Port number:</FormLabel>
|
||||
<SmartNumberInput
|
||||
placeholder="25"
|
||||
value={config.port}
|
||||
onValueChange={handlePortNumberChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Button,
|
||||
useToast,
|
||||
} from '@chakra-ui/react'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import { CredentialsType, SmtpCredentialsData } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
import { createCredentials } from 'services/credentials'
|
||||
import { isNotDefined } from 'utils'
|
||||
import { SmtpConfigForm } from './SmtpConfigForm'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
onNewCredentials: (id: string) => void
|
||||
}
|
||||
|
||||
export const SmtpConfigModal = ({
|
||||
isOpen,
|
||||
onNewCredentials,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const { user, mutateCredentials } = useUser()
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const toast = useToast({
|
||||
position: 'top-right',
|
||||
status: 'error',
|
||||
})
|
||||
const [smtpConfig, setSmtpConfig] = useState<SmtpCredentialsData>({
|
||||
from: {},
|
||||
port: 25,
|
||||
})
|
||||
|
||||
const handleCreateClick = async () => {
|
||||
if (!user) return
|
||||
setIsCreating(true)
|
||||
const { data, error } = await createCredentials(user.id, {
|
||||
data: smtpConfig,
|
||||
name: smtpConfig.from.email as string,
|
||||
type: CredentialsType.SMTP,
|
||||
})
|
||||
await mutateCredentials()
|
||||
setIsCreating(false)
|
||||
if (error) return toast({ title: error.name, description: error.message })
|
||||
if (!data?.credentials)
|
||||
return toast({ description: "Credentials wasn't created" })
|
||||
onNewCredentials(data.credentials.id)
|
||||
onClose()
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Create SMTP config</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<SmtpConfigForm config={smtpConfig} onConfigChange={setSmtpConfig} />
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
mr={3}
|
||||
onClick={handleCreateClick}
|
||||
isDisabled={
|
||||
isNotDefined(smtpConfig.from.email) ||
|
||||
isNotDefined(smtpConfig.from.name) ||
|
||||
isNotDefined(smtpConfig.host) ||
|
||||
isNotDefined(smtpConfig.username) ||
|
||||
isNotDefined(smtpConfig.password)
|
||||
}
|
||||
isLoading={isCreating}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
<Button variant="ghost">Close</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { SendEmailSettings } from './SendEmailSettings'
|
@ -20,6 +20,7 @@ import {
|
||||
import { ConfigureContent } from './contents/ConfigureContent'
|
||||
import { ImageBubbleContent } from './contents/ImageBubbleContent'
|
||||
import { PlaceholderContent } from './contents/PlaceholderContent'
|
||||
import { SendEmailContent } from './contents/SendEmailContent'
|
||||
|
||||
type Props = {
|
||||
step: Step | StartStep
|
||||
@ -101,6 +102,9 @@ export const StepNodeContent = ({ step, indices }: Props) => {
|
||||
case IntegrationStepType.WEBHOOK: {
|
||||
return <WebhookContent step={step} />
|
||||
}
|
||||
case IntegrationStepType.EMAIL: {
|
||||
return <SendEmailContent step={step} />
|
||||
}
|
||||
case 'start': {
|
||||
return <Text>Start</Text>
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { Tag, Text, Wrap, WrapItem } from '@chakra-ui/react'
|
||||
import { SendEmailStep } from 'models'
|
||||
|
||||
type Props = {
|
||||
step: SendEmailStep
|
||||
}
|
||||
|
||||
export const SendEmailContent = ({ step }: Props) => {
|
||||
if (step.options.recipients.length === 0)
|
||||
return <Text color="gray.500">Configure...</Text>
|
||||
return (
|
||||
<Wrap isTruncated pr="6">
|
||||
<WrapItem>
|
||||
<Text>Send email to</Text>
|
||||
</WrapItem>
|
||||
{step.options.recipients.map((to) => (
|
||||
<WrapItem key={to}>
|
||||
<Tag>{to}</Tag>
|
||||
</WrapItem>
|
||||
))}
|
||||
</Wrap>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user