Feat: Added ability to overwrite sender mail and name for templates and campaigns
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "plunk",
|
"name": "plunk",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "agpl-3.0",
|
"license": "agpl-3.0",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export class Tasks {
|
|||||||
let subject = "";
|
let subject = "";
|
||||||
let body = "";
|
let body = "";
|
||||||
|
|
||||||
|
let email = "";
|
||||||
|
let name = "";
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
const { template, notevents } = action;
|
const { template, notevents } = action;
|
||||||
|
|
||||||
@@ -52,6 +55,9 @@ export class Tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
email = project.verified && project.email ? template.email ?? project.email : "[email protected]";
|
||||||
|
name = template.from ?? project.from ?? project.name;
|
||||||
|
|
||||||
({ subject, body } = EmailService.format({
|
({ subject, body } = EmailService.format({
|
||||||
subject: template.subject,
|
subject: template.subject,
|
||||||
body: template.body,
|
body: template.body,
|
||||||
@@ -62,6 +68,9 @@ export class Tasks {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
} else if (campaign) {
|
} else if (campaign) {
|
||||||
|
email = project.verified && project.email ? campaign.email ?? project.email : "[email protected]";
|
||||||
|
name = campaign.from ?? project.from ?? project.name;
|
||||||
|
|
||||||
({ subject, body } = EmailService.format({
|
({ subject, body } = EmailService.format({
|
||||||
subject: campaign.subject,
|
subject: campaign.subject,
|
||||||
body: campaign.body,
|
body: campaign.body,
|
||||||
@@ -75,8 +84,8 @@ export class Tasks {
|
|||||||
|
|
||||||
const { messageId } = await EmailService.send({
|
const { messageId } = await EmailService.send({
|
||||||
from: {
|
from: {
|
||||||
name: project.from ?? project.name,
|
name,
|
||||||
email: project.verified && project.email ? project.email : "[email protected]",
|
email,
|
||||||
},
|
},
|
||||||
to: [contact.email],
|
to: [contact.email],
|
||||||
content: {
|
content: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CampaignSchemas, UtilitySchemas } from "@plunk/shared";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { prisma } from "../../database/prisma";
|
import { prisma } from "../../database/prisma";
|
||||||
import { HttpException, NotFound } from "../../exceptions";
|
import { HttpException, NotAllowed, NotFound } from "../../exceptions";
|
||||||
import { type IJwt, type ISecret, isAuthenticated, isValidSecretKey } from "../../middleware/auth";
|
import { type IJwt, type ISecret, isAuthenticated, isValidSecretKey } from "../../middleware/auth";
|
||||||
import { CampaignService } from "../../services/CampaignService";
|
import { CampaignService } from "../../services/CampaignService";
|
||||||
import { ContactService } from "../../services/ContactService";
|
import { ContactService } from "../../services/ContactService";
|
||||||
@@ -160,6 +160,7 @@ export class Campaigns {
|
|||||||
subject: campaign.subject,
|
subject: campaign.subject,
|
||||||
body: campaign.body,
|
body: campaign.body,
|
||||||
style: campaign.style,
|
style: campaign.style,
|
||||||
|
email: campaign.email,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -180,7 +181,15 @@ export class Campaigns {
|
|||||||
throw new NotFound("project");
|
throw new NotFound("project");
|
||||||
}
|
}
|
||||||
|
|
||||||
let { subject, body, recipients, style } = CampaignSchemas.create.parse(req.body);
|
let { subject, body, recipients, style, email, from } = CampaignSchemas.create.parse(req.body);
|
||||||
|
|
||||||
|
if (email && !project.verified) {
|
||||||
|
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
|
||||||
|
throw new NotAllowed("The sender address must be the same domain as the project's email address");
|
||||||
|
}
|
||||||
|
|
||||||
if (recipients.length === 1 && recipients[0] === "all") {
|
if (recipients.length === 1 && recipients[0] === "all") {
|
||||||
const projectContacts = await prisma.contact.findMany({
|
const projectContacts = await prisma.contact.findMany({
|
||||||
@@ -197,6 +206,8 @@ export class Campaigns {
|
|||||||
subject,
|
subject,
|
||||||
body,
|
body,
|
||||||
style,
|
style,
|
||||||
|
from: from === "" ? null : from,
|
||||||
|
email: email === "" ? null : email,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -253,7 +264,15 @@ export class Campaigns {
|
|||||||
throw new NotFound("project");
|
throw new NotFound("project");
|
||||||
}
|
}
|
||||||
|
|
||||||
let { id, subject, body, recipients, style } = CampaignSchemas.update.parse(req.body);
|
let { id, subject, body, recipients, style, email, from } = CampaignSchemas.update.parse(req.body);
|
||||||
|
|
||||||
|
if (email && !project.verified) {
|
||||||
|
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
|
||||||
|
throw new NotAllowed("The sender address must be the same domain as the project's email address");
|
||||||
|
}
|
||||||
|
|
||||||
if (recipients.length === 1 && recipients[0] === "all") {
|
if (recipients.length === 1 && recipients[0] === "all") {
|
||||||
const projectContacts = await prisma.contact.findMany({
|
const projectContacts = await prisma.contact.findMany({
|
||||||
@@ -276,6 +295,8 @@ export class Campaigns {
|
|||||||
subject,
|
subject,
|
||||||
body,
|
body,
|
||||||
style,
|
style,
|
||||||
|
from: from === "" ? null : from,
|
||||||
|
email: email === "" ? null : email,
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
recipients: { select: { id: true } },
|
recipients: { select: { id: true } },
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export class Templates {
|
|||||||
body: template.body,
|
body: template.body,
|
||||||
type: template.type,
|
type: template.type,
|
||||||
style: template.style,
|
style: template.style,
|
||||||
|
email: template.email,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,7 +102,15 @@ export class Templates {
|
|||||||
throw new NotFound("project");
|
throw new NotFound("project");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { subject, body, type, style } = TemplateSchemas.create.parse(req.body);
|
const { subject, body, type, style, email, from } = TemplateSchemas.create.parse(req.body);
|
||||||
|
|
||||||
|
if (email && !project.verified) {
|
||||||
|
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
|
||||||
|
throw new NotAllowed("The sender address must be the same domain as the project's email address");
|
||||||
|
}
|
||||||
|
|
||||||
const template = await prisma.template.create({
|
const template = await prisma.template.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -110,6 +119,8 @@ export class Templates {
|
|||||||
body,
|
body,
|
||||||
type,
|
type,
|
||||||
style,
|
style,
|
||||||
|
from: from === "" ? null : from,
|
||||||
|
email: email === "" ? null : email,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,7 +162,7 @@ export class Templates {
|
|||||||
throw new NotFound("project");
|
throw new NotFound("project");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, subject, body, type, style } = TemplateSchemas.update.parse(req.body);
|
const { id, subject, body, type, style, email, from } = TemplateSchemas.update.parse(req.body);
|
||||||
|
|
||||||
let template = await TemplateService.id(id);
|
let template = await TemplateService.id(id);
|
||||||
|
|
||||||
@@ -159,9 +170,24 @@ export class Templates {
|
|||||||
throw new NotFound("template");
|
throw new NotFound("template");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (email && !project.verified) {
|
||||||
|
throw new NotAllowed("You need to attach a domain to your project to customize the sender address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email && email.split("@")[1] !== project.email?.split("@")[1]) {
|
||||||
|
throw new NotAllowed("The sender address must be the same domain as the project's email address");
|
||||||
|
}
|
||||||
|
|
||||||
template = await prisma.template.update({
|
template = await prisma.template.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { subject, body, type, style },
|
data: {
|
||||||
|
subject,
|
||||||
|
body,
|
||||||
|
type,
|
||||||
|
style,
|
||||||
|
from: from === "" ? null : from,
|
||||||
|
email: email === "" ? null : email,
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
actions: true,
|
actions: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ export class ActionService {
|
|||||||
|
|
||||||
const { messageId } = await EmailService.send({
|
const { messageId } = await EmailService.send({
|
||||||
from: {
|
from: {
|
||||||
name: project.from ?? project.name,
|
name: action.template.from ?? project.from ?? project.name,
|
||||||
email: project.verified && project.email ? project.email : "[email protected]",
|
email: project.verified && project.email ? action.template.email ?? project.email : "[email protected]",
|
||||||
},
|
},
|
||||||
to: [contact.email],
|
to: [contact.email],
|
||||||
content: {
|
content: {
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { network } from "../../lib/network";
|
|||||||
interface CampaignValues {
|
interface CampaignValues {
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
email?: string;
|
||||||
|
from?: string;
|
||||||
recipients: string[];
|
recipients: string[];
|
||||||
style: "PLUNK" | "HTML";
|
style: "PLUNK" | "HTML";
|
||||||
}
|
}
|
||||||
@@ -71,6 +73,8 @@ export default function Index() {
|
|||||||
watch,
|
watch,
|
||||||
reset,
|
reset,
|
||||||
setValue,
|
setValue,
|
||||||
|
setError,
|
||||||
|
clearErrors,
|
||||||
} = useForm<CampaignValues>({
|
} = useForm<CampaignValues>({
|
||||||
resolver: zodResolver(CampaignSchemas.update),
|
resolver: zodResolver(CampaignSchemas.update),
|
||||||
defaultValues: { recipients: [], body: undefined },
|
defaultValues: { recipients: [], body: undefined },
|
||||||
@@ -87,6 +91,21 @@ export default function Index() {
|
|||||||
});
|
});
|
||||||
}, [reset, campaign]);
|
}, [reset, campaign]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
watch((value, { name, type }) => {
|
||||||
|
if (name === "email") {
|
||||||
|
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
|
||||||
|
setError("email", {
|
||||||
|
type: "manual",
|
||||||
|
message: `The sender address must end with @${project.email?.split("@")[1]}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearErrors("email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [watch, project, setError, clearErrors]);
|
||||||
|
|
||||||
if (!project || !campaign || !events || (watch("body") as string | undefined) === undefined) {
|
if (!project || !campaign || !events || (watch("body") as string | undefined) === undefined) {
|
||||||
return <FullscreenLoader />;
|
return <FullscreenLoader />;
|
||||||
}
|
}
|
||||||
@@ -404,13 +423,35 @@ export default function Index() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit(update)} className="space-6 grid gap-6 sm:grid-cols-6">
|
<form onSubmit={handleSubmit(update)} className="space-6 grid gap-6 sm:grid-cols-6">
|
||||||
<Input
|
<div className={"sm:col-span-6 grid sm:grid-cols-6 gap-6"}>
|
||||||
className={"sm:col-span-6"}
|
<Input
|
||||||
label={"Subject"}
|
className={"sm:col-span-6"}
|
||||||
placeholder={`Welcome to ${project.name}!`}
|
label={"Subject"}
|
||||||
register={register("subject")}
|
placeholder={`Welcome to ${project.name}!`}
|
||||||
error={errors.subject}
|
register={register("subject")}
|
||||||
/>
|
error={errors.subject}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Email"}
|
||||||
|
placeholder={`${project.email}`}
|
||||||
|
register={register("email")}
|
||||||
|
error={errors.email}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Name"}
|
||||||
|
placeholder={`${project.name}`}
|
||||||
|
register={register("from")}
|
||||||
|
error={errors.from}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{contacts ? (
|
{contacts ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import dayjs from "dayjs";
|
|||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { Search, Users2, XIcon } from "lucide-react";
|
import { Search, Users2, XIcon } from "lucide-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { type FieldError, useForm } from "react-hook-form";
|
import { type FieldError, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Alert, Card, Dropdown, Editor, FullscreenLoader, Input, MultiselectDropdown } from "../../components";
|
import { Alert, Card, Dropdown, Editor, FullscreenLoader, Input, MultiselectDropdown } from "../../components";
|
||||||
@@ -20,6 +20,8 @@ import { network } from "../../lib/network";
|
|||||||
interface CampaignValues {
|
interface CampaignValues {
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
email?: string;
|
||||||
|
from?: string;
|
||||||
recipients: string[];
|
recipients: string[];
|
||||||
style: "PLUNK" | "HTML";
|
style: "PLUNK" | "HTML";
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,8 @@ export default function Index() {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
setValue,
|
setValue,
|
||||||
watch,
|
watch,
|
||||||
|
setError,
|
||||||
|
clearErrors,
|
||||||
} = useForm<CampaignValues>({
|
} = useForm<CampaignValues>({
|
||||||
resolver: zodResolver(CampaignSchemas.create),
|
resolver: zodResolver(CampaignSchemas.create),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -67,6 +71,21 @@ export default function Index() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
watch((value, { name, type }) => {
|
||||||
|
if (name === "email") {
|
||||||
|
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
|
||||||
|
setError("email", {
|
||||||
|
type: "manual",
|
||||||
|
message: `The sender address must end with @${project.email?.split("@")[1]}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearErrors("email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [watch, project, setError, clearErrors]);
|
||||||
|
|
||||||
if (!project || !events) {
|
if (!project || !events) {
|
||||||
return <FullscreenLoader />;
|
return <FullscreenLoader />;
|
||||||
}
|
}
|
||||||
@@ -194,13 +213,36 @@ export default function Index() {
|
|||||||
<Dashboard>
|
<Dashboard>
|
||||||
<Card title={"Create a new campaign"}>
|
<Card title={"Create a new campaign"}>
|
||||||
<form onSubmit={handleSubmit(create)} className="space-6 grid gap-6 sm:grid-cols-6">
|
<form onSubmit={handleSubmit(create)} className="space-6 grid gap-6 sm:grid-cols-6">
|
||||||
<Input
|
<div className={"sm:col-span-6 grid sm:grid-cols-6 gap-6"}>
|
||||||
className={"sm:col-span-6"}
|
<Input
|
||||||
label={"Subject"}
|
className={"sm:col-span-6"}
|
||||||
placeholder={`Welcome to ${project.name}!`}
|
label={"Subject"}
|
||||||
register={register("subject")}
|
placeholder={`Welcome to ${project.name}!`}
|
||||||
error={errors.subject}
|
register={register("subject")}
|
||||||
/>
|
error={errors.subject}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Email"}
|
||||||
|
placeholder={`${project.email}`}
|
||||||
|
register={register("email")}
|
||||||
|
error={errors.email}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Name"}
|
||||||
|
placeholder={`${project.name}`}
|
||||||
|
register={register("from")}
|
||||||
|
error={errors.from}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{contacts ? (
|
{contacts ? (
|
||||||
<>
|
<>
|
||||||
<div className={"sm:col-span-3"}>
|
<div className={"sm:col-span-3"}>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import { network } from "../../lib/network";
|
|||||||
interface TemplateValues {
|
interface TemplateValues {
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
email?: string;
|
||||||
|
from?: string;
|
||||||
type: "MARKETING" | "TRANSACTIONAL";
|
type: "MARKETING" | "TRANSACTIONAL";
|
||||||
style: "PLUNK" | "HTML";
|
style: "PLUNK" | "HTML";
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,8 @@ export default function Index() {
|
|||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
|
setError,
|
||||||
|
clearErrors,
|
||||||
} = useForm<TemplateValues>({
|
} = useForm<TemplateValues>({
|
||||||
resolver: zodResolver(TemplateSchemas.update),
|
resolver: zodResolver(TemplateSchemas.update),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -56,6 +60,21 @@ export default function Index() {
|
|||||||
reset(template);
|
reset(template);
|
||||||
}, [reset, template]);
|
}, [reset, template]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
watch((value, { name, type }) => {
|
||||||
|
if (name === "email") {
|
||||||
|
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
|
||||||
|
setError("email", {
|
||||||
|
type: "manual",
|
||||||
|
message: `The sender address must end with @${project.email?.split("@")[1]}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearErrors("email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [watch, project, setError, clearErrors]);
|
||||||
|
|
||||||
if (!project || !template || (watch("body") as string | undefined) === undefined) {
|
if (!project || !template || (watch("body") as string | undefined) === undefined) {
|
||||||
return <FullscreenLoader />;
|
return <FullscreenLoader />;
|
||||||
}
|
}
|
||||||
@@ -248,6 +267,26 @@ export default function Index() {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Email"}
|
||||||
|
placeholder={`${project.email}`}
|
||||||
|
register={register("email")}
|
||||||
|
error={errors.email}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Name"}
|
||||||
|
placeholder={`${project.name}`}
|
||||||
|
register={register("from")}
|
||||||
|
error={errors.from}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={"sm:col-span-6"}>
|
<div className={"sm:col-span-6"}>
|
||||||
<Editor
|
<Editor
|
||||||
value={watch("body")}
|
value={watch("body")}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { TemplateSchemas } from "@plunk/shared";
|
|||||||
import type { Template } from "@prisma/client";
|
import type { Template } from "@prisma/client";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Card, Dropdown, Editor, FullscreenLoader, Input, Tooltip } from "../../components";
|
import { Card, Dropdown, Editor, FullscreenLoader, Input, Tooltip } from "../../components";
|
||||||
@@ -15,6 +15,8 @@ import { network } from "../../lib/network";
|
|||||||
interface TemplateValues {
|
interface TemplateValues {
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
email?: string;
|
||||||
|
from?: string;
|
||||||
type: "MARKETING" | "TRANSACTIONAL";
|
type: "MARKETING" | "TRANSACTIONAL";
|
||||||
style: "PLUNK" | "HTML";
|
style: "PLUNK" | "HTML";
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,8 @@ export default function Index() {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
|
setError,
|
||||||
|
clearErrors,
|
||||||
} = useForm<TemplateValues>({
|
} = useForm<TemplateValues>({
|
||||||
resolver: zodResolver(TemplateSchemas.create),
|
resolver: zodResolver(TemplateSchemas.create),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -50,6 +54,21 @@ export default function Index() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
watch((value, { name, type }) => {
|
||||||
|
if (name === "email") {
|
||||||
|
if (value.email && project?.email && !value.email.endsWith(project.email.split("@")[1])) {
|
||||||
|
setError("email", {
|
||||||
|
type: "manual",
|
||||||
|
message: `The sender address must end with @${project.email?.split("@")[1]}`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearErrors("email");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [watch, project, setError, clearErrors]);
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
return <FullscreenLoader />;
|
return <FullscreenLoader />;
|
||||||
}
|
}
|
||||||
@@ -141,6 +160,26 @@ export default function Index() {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Email"}
|
||||||
|
placeholder={`${project.email}`}
|
||||||
|
register={register("email")}
|
||||||
|
error={errors.email}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{project.verified && (
|
||||||
|
<Input
|
||||||
|
className={"sm:col-span-3"}
|
||||||
|
label={"Sender Name"}
|
||||||
|
placeholder={`${project.name}`}
|
||||||
|
register={register("from")}
|
||||||
|
error={errors.from}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={"sm:col-span-6"}>
|
<div className={"sm:col-span-6"}>
|
||||||
<Editor
|
<Editor
|
||||||
value={watch("body")}
|
value={watch("body")}
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ export const CampaignSchemas = {
|
|||||||
.min(1, "Subject needs to be at least 1 character long")
|
.min(1, "Subject needs to be at least 1 character long")
|
||||||
.max(70, "Subject needs to be less than 70 characters long"),
|
.max(70, "Subject needs to be less than 70 characters long"),
|
||||||
body: z.string().min(1, "Body needs to be at least 1 character long"),
|
body: z.string().min(1, "Body needs to be at least 1 character long"),
|
||||||
|
email: email.nullish().or(z.literal("")),
|
||||||
|
from: z.string().nullish(),
|
||||||
recipients: z.array(z.string()),
|
recipients: z.array(z.string()),
|
||||||
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
||||||
}),
|
}),
|
||||||
@@ -143,6 +145,8 @@ export const CampaignSchemas = {
|
|||||||
.min(1, "Subject needs to be at least 1 character long")
|
.min(1, "Subject needs to be at least 1 character long")
|
||||||
.max(70, "Subject needs to be less than 70 characters long"),
|
.max(70, "Subject needs to be less than 70 characters long"),
|
||||||
body: z.string().min(1, "Body needs to be at least 1 character long"),
|
body: z.string().min(1, "Body needs to be at least 1 character long"),
|
||||||
|
email: email.nullish().or(z.literal("")),
|
||||||
|
from: z.string().nullish(),
|
||||||
recipients: z.array(z.string()),
|
recipients: z.array(z.string()),
|
||||||
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
||||||
}),
|
}),
|
||||||
@@ -208,6 +212,8 @@ export const TemplateSchemas = {
|
|||||||
create: z.object({
|
create: z.object({
|
||||||
subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"),
|
subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"),
|
||||||
body: z.string().min(1, "Body can't be empty"),
|
body: z.string().min(1, "Body can't be empty"),
|
||||||
|
email: email.nullish().or(z.literal("")),
|
||||||
|
from: z.string().nullish(),
|
||||||
type: z.nativeEnum(TemplateType).default("MARKETING"),
|
type: z.nativeEnum(TemplateType).default("MARKETING"),
|
||||||
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
||||||
}),
|
}),
|
||||||
@@ -215,6 +221,8 @@ export const TemplateSchemas = {
|
|||||||
id,
|
id,
|
||||||
subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"),
|
subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"),
|
||||||
body: z.string().min(1, "Body can't be empty"),
|
body: z.string().min(1, "Body can't be empty"),
|
||||||
|
email: email.nullish().or(z.literal("")),
|
||||||
|
from: z.string().nullish(),
|
||||||
type: z.nativeEnum(TemplateType).default("MARKETING"),
|
type: z.nativeEnum(TemplateType).default("MARKETING"),
|
||||||
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
style: z.nativeEnum(TemplateStyle).default("PLUNK"),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ model Template {
|
|||||||
// Details
|
// Details
|
||||||
subject String
|
subject String
|
||||||
body String
|
body String
|
||||||
|
email String?
|
||||||
|
from String?
|
||||||
|
|
||||||
type TemplateType
|
type TemplateType
|
||||||
style TemplateStyle
|
style TemplateStyle
|
||||||
@@ -159,8 +161,11 @@ model Action {
|
|||||||
model Campaign {
|
model Campaign {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
subject String
|
subject String
|
||||||
body String
|
body String
|
||||||
|
email String?
|
||||||
|
from String?
|
||||||
|
|
||||||
status CampaignStatus @default(DRAFT)
|
status CampaignStatus @default(DRAFT)
|
||||||
delivered DateTime?
|
delivered DateTime?
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user