Adds from name and auto text email (#116)

This commit is contained in:
Chris Anderson 2023-04-07 18:01:40 -04:00 committed by GitHub
parent 4fe89013a5
commit b3b3e7d1e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 59 additions and 27 deletions

View file

@ -1,6 +1,8 @@
export type NamedEmail = { name: string, address: string }
export interface Email {
to: string
from: string
from: string | NamedEmail
cc?: string
bcc?: string
reply_to?: string

View file

@ -3,6 +3,8 @@ import { Webhook } from '../providers/webhook/Webhook'
import { ChannelType } from '../config/channels'
import Model, { ModelParams } from '../core/Model'
import { isValid, IsValidSchema } from '../core/validate'
import { Email, NamedEmail } from '../providers/email/Email'
import { htmlToText } from 'html-to-text'
export default class Template extends Model {
project_id!: number
@ -44,26 +46,17 @@ export type TemplateParams = Omit<Template, ModelParams | 'map' | 'screenshotUrl
export type TemplateUpdateParams = Pick<Template, 'type' | 'data'>
export type TemplateType = EmailTemplate | TextTemplate | PushTemplate | WebhookTemplate
export interface CompiledEmail {
from: string
cc?: string
bcc?: string
reply_to?: string
subject: string
preheader?: string
text: string
html: string
}
type CompiledEmail = Omit<Email, 'to' | 'headers'> & { preheader?: string }
export class EmailTemplate extends Template {
declare type: 'email'
from!: string
from!: string | NamedEmail
cc?: string
bcc?: string
reply_to?: string
subject!: string
preheader?: string
text!: string
text?: string
html!: string
parseJson(json: any) {
@ -75,17 +68,28 @@ export class EmailTemplate extends Template {
this.reply_to = json?.data.reply_to
this.subject = json?.data.subject ?? ''
this.preheader = json?.data.preheader
this.text = json?.data.text ?? ''
this.text = json?.data.text
this.html = json?.data.html ?? ''
}
compile(variables: Variables): CompiledEmail {
const html = Render(this.html, variables)
const email: CompiledEmail = {
subject: Render(this.subject, variables),
from: Render(this.from, variables),
html: Render(this.html, variables),
text: Render(this.text, variables),
from: typeof this.from === 'string'
? Render(this.from, variables)
: {
name: Render(this.from.name, variables),
address: Render(this.from.address, variables),
},
html,
// If the text copy has been left empty, generate from HTML
text: this.text !== undefined && this.text !== ''
? Render(this.text, variables)
: htmlToText(html),
}
if (this.preheader) email.preheader = Render(this.preheader, variables)
if (this.reply_to) email.reply_to = Render(this.reply_to, variables)
if (this.cc) email.cc = Render(this.cc, variables)

View file

@ -24,8 +24,18 @@ const templateDataEmailParams = {
type: 'object',
properties: {
from: {
type: 'string',
type: 'object',
nullable: true,
properties: {
name: {
type: 'string',
nullable: true,
},
address: {
type: 'string',
nullable: true,
},
},
},
cc: {
type: 'string',

View file

@ -300,7 +300,7 @@ export type CampaignLaunchParams = Pick<Campaign, 'send_at' | 'send_in_user_time
export type CampaignUser = User & { state: CampaignSendState, send_at: string }
export interface EmailTemplateData {
from: string
from: { name: string, address: string }
cc?: string
bcc?: string
reply_to?: string

View file

@ -12,7 +12,7 @@ export default function Preview({ template }: PreviewProps) {
const EmailFrame = () => <div className="email-frame">
<div className="email-frame-header">
<span className="email-from">{data.from}</span>
<span className="email-from">{data.from.name} &lt;{data.from.address}&gt;</span>
<span className="email-subject">{data.subject}</span>
</div>
<Iframe content={data.html ?? ''} />

View file

@ -27,6 +27,7 @@ interface ListSelectionProps extends SelectionProps<CampaignCreateParams> {
project: Project
title: string
value?: List[]
required: boolean
}
const ListSelection = ({
@ -35,6 +36,7 @@ const ListSelection = ({
name,
title,
value,
required,
}: ListSelectionProps) => {
const [isOpen, setIsOpen] = useState(false)
const [lists, setLists] = useState<List[]>(value ?? [])
@ -57,7 +59,7 @@ const ListSelection = ({
control,
name,
rules: {
required: true,
required,
},
})
@ -243,6 +245,7 @@ export function CampaignForm({ campaign, disableListSelection, onSave }: Campaig
name="list_ids"
value={campaign?.lists}
control={form.control}
required={true}
/>
<ListSelection
project={project}
@ -250,6 +253,7 @@ export function CampaignForm({ campaign, disableListSelection, onSave }: Campaig
name="exclusion_list_ids"
value={campaign?.exclusion_lists}
control={form.control}
required={false}
/>
</>
)

View file

@ -17,6 +17,7 @@ import ImageGalleryModal from './ImageGalleryModal'
import Modal from '../../ui/Modal'
import { ImageIcon } from '../../ui/icons'
import EditLocalesModal from './EditLocalesModal'
import { toast } from 'react-hot-toast'
const HtmlEditor = ({ template, setTemplate }: { template: Template, setTemplate: (template: Template) => void }) => {
@ -26,6 +27,7 @@ const HtmlEditor = ({ template, setTemplate }: { template: Template, setTemplate
const [data, setData] = useState(template.data)
const [selectedIndex, setSelectedIndex] = useState(0)
const [showImages, setShowImages] = useState(false)
const [isSaving, setIsSaving] = useState(false)
function handleMount(editor: Editor.IStandaloneCodeEditor) {
setMonaco(editor)
@ -63,9 +65,17 @@ const HtmlEditor = ({ template, setTemplate }: { template: Template, setTemplate
}
}
function handleSave() {
template.data = data
setTemplate(template)
async function handleSave() {
try {
setIsSaving(true)
template.data = data
await setTemplate(template)
toast.success('Saved!')
} catch (error) {
toast.error('Unable to save template.')
} finally {
setIsSaving(false)
}
}
return (
@ -120,7 +130,7 @@ const HtmlEditor = ({ template, setTemplate }: { template: Template, setTemplate
<span className="publish-label">Last updated at</span>
<span className="publish-date">{formatDate(preferences, template.updated_at)}</span>
</div>
<Button onClick={() => handleSave()}>Save</Button>
<Button onClick={async () => await handleSave()} isLoading={isSaving}>Save</Button>
</div>
<ImageGalleryModal

View file

@ -13,7 +13,8 @@ import TextInput from '../../ui/form/TextInput'
import api from '../../api'
const EmailTable = ({ data }: { data: EmailTemplateData }) => <InfoTable rows={{
'From Email': data.from,
'From Email': data.from?.address,
'From Name': data.from?.name,
'Reply To': data.reply_to,
CC: data.cc,
BCC: data.bcc,
@ -33,7 +34,8 @@ const EmailForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> })
name="data.preheader"
label="Preheader"
textarea />
<TextInput.Field form={form} name="data.from" label="From Email" required />
<TextInput.Field form={form} name="data.from.address" label="From Email" required />
<TextInput.Field form={form} name="data.from.name" label="From Name" required />
<TextInput.Field form={form} name="data.reply_to" label="Reply To" />
<TextInput.Field form={form} name="data.cc" label="CC" />
<TextInput.Field form={form} name="data.bcc" label="BCC" />