Skip to content

Commit

Permalink
feat(editor): ✨ Add send email integration
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Feb 7, 2022
1 parent f4336b8 commit d6238b3
Show file tree
Hide file tree
Showing 48 changed files with 2,133 additions and 2,620 deletions.
22 changes: 16 additions & 6 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
DATABASE_URL=postgresql://username:password@host:5450/typebot?schema=public

SECRET=secret
SECRET=q3t6v9y$B&E)H@McQfTjWnZr4u7x!z%C # 256-bits secret (can be generated here: https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx)
NEXTAUTH_URL=http://localhost:3000

# Used for email auth and email notifications
EMAIL_SERVER_USER=username
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smtp.example.com
EMAIL_SERVER_PORT=587
EMAIL_FROM=noreply@example.com
AUTH_EMAIL_SERVER_USERNAME=username
AUTH_EMAIL_SERVER_PASSWORD=password
AUTH_EMAIL_SERVER_HOST=smtp.example.com
AUTH_EMAIL_SERVER_PORT=587
AUTH_EMAIL_FROM_EMAIL=noreply@example.com
AUTH_EMAIL_FROM_NAME="John Smith"

# (Optional) Used for email notifications
EMAIL_NOTIFICATIONS_SERVER_USERNAME=username
EMAIL_NOTIFICATIONS_SERVER_PASSWORD=password
EMAIL_NOTIFICATIONS_SERVER_HOST=smtp.example.com
EMAIL_NOTIFICATIONS_SERVER_PORT=587
EMAIL_NOTIFICATIONS_FROM_EMAIL=noreply@example.com
EMAIL_NOTIFICATIONS_FROM_NAME="John Smith"


# Storage
# Used for uploading images, videos, etc...
Expand Down
7 changes: 7 additions & 0 deletions apps/builder/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,10 @@ export const EyeIcon = (props: IconProps) => (
<circle cx="12" cy="12" r="3"></circle>
</Icon>
)

export const SendEmailIcon = (props: IconProps) => (
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</Icon>
)
5 changes: 4 additions & 1 deletion apps/builder/components/editor/StepsSideBar/StepIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ImageIcon,
NumberIcon,
PhoneIcon,
SendEmailIcon,
TextIcon,
WebhookIcon,
} from 'assets/icons'
Expand Down Expand Up @@ -61,7 +62,9 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
case IntegrationStepType.GOOGLE_ANALYTICS:
return <GoogleAnalyticsLogo {...props} />
case IntegrationStepType.WEBHOOK:
return <WebhookIcon />
return <WebhookIcon {...props} />
case IntegrationStepType.EMAIL:
return <SendEmailIcon {...props} />
case 'start':
return <FlagIcon {...props} />
default:
Expand Down
2 changes: 2 additions & 0 deletions apps/builder/components/editor/StepsSideBar/StepTypeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const StepTypeLabel = ({ type }: Props) => {
)
case IntegrationStepType.WEBHOOK:
return <Text>Webhook</Text>
case IntegrationStepType.EMAIL:
return <Text>Email</Text>
default:
return <></>
}
Expand Down
19 changes: 17 additions & 2 deletions apps/builder/components/shared/CredentialsDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,30 @@ import {
import { ChevronLeftIcon, PlusIcon } from 'assets/icons'
import React, { useEffect, useMemo } from 'react'
import { useUser } from 'contexts/UserContext'
import { CredentialsType } from 'db'
import { useRouter } from 'next/router'
import { CredentialsType } from 'models'

type Props = Omit<MenuButtonProps, 'type'> & {
type: CredentialsType
currentCredentialsId?: string
onCredentialsSelect: (credentialId: string) => void
onCreateNewClick: () => void
defaultCredentialLabel?: string
}

export const CredentialsDropdown = ({
type,
currentCredentialsId,
onCredentialsSelect,
onCreateNewClick,
defaultCredentialLabel,
...props
}: Props) => {
const router = useRouter()
const { credentials } = useUser()

const defaultCredentialsLabel = defaultCredentialLabel ?? `Select an account`

const credentialsList = useMemo(() => {
return credentials.filter((credential) => credential.type === type)
}, [type, credentials])
Expand Down Expand Up @@ -70,11 +74,22 @@ export const CredentialsDropdown = ({
{...props}
>
<Text isTruncated overflowY="visible" h="20px">
{currentCredential ? currentCredential.name : 'Select an account'}
{currentCredential ? currentCredential.name : defaultCredentialsLabel}
</Text>
</MenuButton>
<MenuList maxW="500px">
<Stack maxH={'35vh'} overflowY="scroll" spacing="0">
{defaultCredentialLabel && (
<MenuItem
maxW="500px"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
onClick={handleMenuItemClick('default')}
>
{defaultCredentialLabel}
</MenuItem>
)}
{credentialsList.map((credentials) => (
<MenuItem
key={credentials.id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -213,6 +214,14 @@ export const StepSettings = ({
/>
)
}
case IntegrationStepType.EMAIL: {
return (
<SendEmailSettings
options={step.options}
onOptionsChange={handleOptionsChange}
/>
)
}
default: {
return <></>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
)
}
Original file line number Diff line number Diff line change
@@ -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>
)
}
Loading

0 comments on commit d6238b3

Please sign in to comment.