feat(integrations): 🚸 Add Reply-To field for email sending
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { Stack, useDisclosure, Text } from '@chakra-ui/react'
|
import { Stack, useDisclosure, Text } from '@chakra-ui/react'
|
||||||
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
import { CredentialsDropdown } from 'components/shared/CredentialsDropdown'
|
||||||
import { Input, Textarea } from 'components/shared/Textbox'
|
import { Input, Textarea } from 'components/shared/Textbox'
|
||||||
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { CredentialsType, SendEmailOptions } from 'models'
|
import { CredentialsType, SendEmailOptions } from 'models'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { isDefined } from 'utils'
|
||||||
import { SmtpConfigModal } from './SmtpConfigModal'
|
import { SmtpConfigModal } from './SmtpConfigModal'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -11,9 +13,16 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
||||||
|
const { owner } = useTypebot()
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||||
const [refreshCredentialsKey, setRefreshCredentialsKey] = useState(0)
|
const [refreshCredentialsKey, setRefreshCredentialsKey] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDefined(options.replyTo) || !owner?.email) return
|
||||||
|
handleReplyToChange(owner.email)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleCredentialsSelect = (credentialsId?: string) => {
|
const handleCredentialsSelect = (credentialsId?: string) => {
|
||||||
setRefreshCredentialsKey(refreshCredentialsKey + 1)
|
setRefreshCredentialsKey(refreshCredentialsKey + 1)
|
||||||
onOptionsChange({
|
onOptionsChange({
|
||||||
@@ -60,6 +69,12 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleReplyToChange = (replyTo: string) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
replyTo,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing={4}>
|
<Stack spacing={4}>
|
||||||
<Stack>
|
<Stack>
|
||||||
@@ -75,6 +90,14 @@ export const SendEmailSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
refreshDropdownKey={refreshCredentialsKey}
|
refreshDropdownKey={refreshCredentialsKey}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Text>Reply to: </Text>
|
||||||
|
<Input
|
||||||
|
onChange={handleReplyToChange}
|
||||||
|
defaultValue={options.replyTo}
|
||||||
|
placeholder={owner?.email ?? 'email@gmail.com'}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text>To: </Text>
|
<Text>To: </Text>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ export const TextBox = ({
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
const [carretPosition, setCarretPosition] = useState<number>(0)
|
const [carretPosition, setCarretPosition] = useState<number>(0)
|
||||||
const [value, setValue] = useState(props.defaultValue)
|
const [value, setValue] = useState(props.defaultValue ?? '')
|
||||||
|
const [isTouched, setIsTouched] = useState(false)
|
||||||
const debounced = useDebouncedCallback(
|
const debounced = useDebouncedCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
onChange(value)
|
onChange(value)
|
||||||
@@ -44,6 +45,12 @@ export const TextBox = ({
|
|||||||
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
process.env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.defaultValue !== value && value === '' && !isTouched)
|
||||||
|
setValue(props.defaultValue ?? '')
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [props.defaultValue])
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
debounced.flush()
|
debounced.flush()
|
||||||
@@ -54,12 +61,14 @@ export const TextBox = ({
|
|||||||
const handleChange = (
|
const handleChange = (
|
||||||
e: ChangeEvent<HTMLInputElement & HTMLTextAreaElement>
|
e: ChangeEvent<HTMLInputElement & HTMLTextAreaElement>
|
||||||
) => {
|
) => {
|
||||||
|
setIsTouched(true)
|
||||||
setValue(e.target.value)
|
setValue(e.target.value)
|
||||||
debounced(e.target.value)
|
debounced(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleVariableSelected = (variable?: Variable) => {
|
const handleVariableSelected = (variable?: Variable) => {
|
||||||
if (!textBoxRef.current || !variable) return
|
if (!textBoxRef.current || !variable) return
|
||||||
|
setIsTouched(true)
|
||||||
const cursorPosition = carretPosition
|
const cursorPosition = carretPosition
|
||||||
const textBeforeCursorPosition = textBoxRef.current.value.substring(
|
const textBeforeCursorPosition = textBoxRef.current.value.substring(
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const defaultFrom = {
|
|||||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await cors(req, res)
|
await cors(req, res)
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { credentialsId, recipients, body, subject, cc, bcc } = (
|
const { credentialsId, recipients, body, subject, cc, bcc, replyTo } = (
|
||||||
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
typeof req.body === 'string' ? JSON.parse(req.body) : req.body
|
||||||
) as SendEmailOptions
|
) as SendEmailOptions
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
cc: cc?.join(''),
|
cc: cc?.join(''),
|
||||||
bcc: bcc?.join(''),
|
bcc: bcc?.join(''),
|
||||||
to: recipients.join(', '),
|
to: recipients.join(', '),
|
||||||
|
replyTo,
|
||||||
subject,
|
subject,
|
||||||
text: body,
|
text: body,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -241,6 +241,7 @@ const sendEmail = async (
|
|||||||
return step.outgoingEdgeId
|
return step.outgoingEdgeId
|
||||||
}
|
}
|
||||||
const { options } = step
|
const { options } = step
|
||||||
|
const replyTo = parseVariables(variables)(options.replyTo)
|
||||||
const { error } = await sendRequest({
|
const { error } = await sendRequest({
|
||||||
url: `${apiHost}/api/integrations/email`,
|
url: `${apiHost}/api/integrations/email`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -251,6 +252,7 @@ const sendEmail = async (
|
|||||||
body: parseVariables(variables)(options.body ?? ''),
|
body: parseVariables(variables)(options.body ?? ''),
|
||||||
cc: (options.cc ?? []).map(parseVariables(variables)),
|
cc: (options.cc ?? []).map(parseVariables(variables)),
|
||||||
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
bcc: (options.bcc ?? []).map(parseVariables(variables)),
|
||||||
|
replyTo: replyTo !== '' ? replyTo : undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
onNewLog(
|
onNewLog(
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export type SendEmailStep = StepBase & {
|
|||||||
export type SendEmailOptions = {
|
export type SendEmailOptions = {
|
||||||
credentialsId: string | 'default'
|
credentialsId: string | 'default'
|
||||||
recipients: string[]
|
recipients: string[]
|
||||||
|
replyTo?: string
|
||||||
cc?: string[]
|
cc?: string[]
|
||||||
bcc?: string[]
|
bcc?: string[]
|
||||||
subject?: string
|
subject?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user