payload-plugin-newsletter 0.8.7 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  "use client";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,11 +18,22 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/exports/components.ts
22
32
  var components_exports = {};
23
33
  __export(components_exports, {
34
+ BroadcastEditor: () => BroadcastEditor,
35
+ EmailPreview: () => EmailPreview,
36
+ EmailPreviewField: () => EmailPreviewField,
24
37
  MagicLinkVerify: () => MagicLinkVerify,
25
38
  NewsletterForm: () => NewsletterForm,
26
39
  PreferencesForm: () => PreferencesForm,
@@ -878,8 +891,772 @@ function useNewsletterAuth(_options = {}) {
878
891
  // For backward compatibility
879
892
  };
880
893
  }
894
+
895
+ // src/components/Broadcasts/EmailPreview.tsx
896
+ var import_react5 = require("react");
897
+
898
+ // src/utils/emailSafeHtml.ts
899
+ var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
900
+ var EMAIL_SAFE_CONFIG = {
901
+ ALLOWED_TAGS: [
902
+ "p",
903
+ "br",
904
+ "strong",
905
+ "b",
906
+ "em",
907
+ "i",
908
+ "u",
909
+ "strike",
910
+ "s",
911
+ "span",
912
+ "a",
913
+ "h1",
914
+ "h2",
915
+ "h3",
916
+ "ul",
917
+ "ol",
918
+ "li",
919
+ "blockquote",
920
+ "hr"
921
+ ],
922
+ ALLOWED_ATTR: ["href", "style", "target", "rel", "align"],
923
+ ALLOWED_STYLES: {
924
+ "*": [
925
+ "color",
926
+ "background-color",
927
+ "font-size",
928
+ "font-weight",
929
+ "font-style",
930
+ "text-decoration",
931
+ "text-align",
932
+ "margin",
933
+ "margin-top",
934
+ "margin-right",
935
+ "margin-bottom",
936
+ "margin-left",
937
+ "padding",
938
+ "padding-top",
939
+ "padding-right",
940
+ "padding-bottom",
941
+ "padding-left",
942
+ "line-height",
943
+ "border-left",
944
+ "border-left-width",
945
+ "border-left-style",
946
+ "border-left-color"
947
+ ]
948
+ },
949
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
950
+ FORBID_ATTR: ["class", "id", "onclick", "onload", "onerror"]
951
+ };
952
+ async function convertToEmailSafeHtml(editorState, options) {
953
+ const rawHtml = await lexicalToEmailHtml(editorState);
954
+ const sanitizedHtml = import_isomorphic_dompurify.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
955
+ if (options?.wrapInTemplate) {
956
+ return wrapInEmailTemplate(sanitizedHtml, options.preheader);
957
+ }
958
+ return sanitizedHtml;
959
+ }
960
+ async function lexicalToEmailHtml(editorState) {
961
+ const { root } = editorState;
962
+ if (!root || !root.children) {
963
+ return "";
964
+ }
965
+ const html = root.children.map((node) => convertNode(node)).join("");
966
+ return html;
967
+ }
968
+ function convertNode(node) {
969
+ switch (node.type) {
970
+ case "paragraph":
971
+ return convertParagraph(node);
972
+ case "heading":
973
+ return convertHeading(node);
974
+ case "list":
975
+ return convertList(node);
976
+ case "listitem":
977
+ return convertListItem(node);
978
+ case "blockquote":
979
+ return convertBlockquote(node);
980
+ case "text":
981
+ return convertText(node);
982
+ case "link":
983
+ return convertLink(node);
984
+ case "linebreak":
985
+ return "<br>";
986
+ default:
987
+ if (node.children) {
988
+ return node.children.map(convertNode).join("");
989
+ }
990
+ return "";
991
+ }
992
+ }
993
+ function convertParagraph(node) {
994
+ const align = getAlignment(node.format);
995
+ const children = node.children?.map(convertNode).join("") || "";
996
+ if (!children.trim()) {
997
+ return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
998
+ }
999
+ return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
1000
+ }
1001
+ function convertHeading(node) {
1002
+ const tag = node.tag || "h1";
1003
+ const align = getAlignment(node.format);
1004
+ const children = node.children?.map(convertNode).join("") || "";
1005
+ const styles = {
1006
+ h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
1007
+ h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
1008
+ h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
1009
+ };
1010
+ const style = `${styles[tag] || styles.h3} text-align: ${align};`;
1011
+ return `<${tag} style="${style}">${children}</${tag}>`;
1012
+ }
1013
+ function convertList(node) {
1014
+ const tag = node.listType === "number" ? "ol" : "ul";
1015
+ const children = node.children?.map(convertNode).join("") || "";
1016
+ const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal;";
1017
+ return `<${tag} style="${style}">${children}</${tag}>`;
1018
+ }
1019
+ function convertListItem(node) {
1020
+ const children = node.children?.map(convertNode).join("") || "";
1021
+ return `<li style="margin: 0 0 8px 0;">${children}</li>`;
1022
+ }
1023
+ function convertBlockquote(node) {
1024
+ const children = node.children?.map(convertNode).join("") || "";
1025
+ const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
1026
+ return `<blockquote style="${style}">${children}</blockquote>`;
1027
+ }
1028
+ function convertText(node) {
1029
+ let text = escapeHtml(node.text || "");
1030
+ if (node.format & 1) {
1031
+ text = `<strong>${text}</strong>`;
1032
+ }
1033
+ if (node.format & 2) {
1034
+ text = `<em>${text}</em>`;
1035
+ }
1036
+ if (node.format & 8) {
1037
+ text = `<u>${text}</u>`;
1038
+ }
1039
+ if (node.format & 4) {
1040
+ text = `<strike>${text}</strike>`;
1041
+ }
1042
+ return text;
1043
+ }
1044
+ function convertLink(node) {
1045
+ const children = node.children?.map(convertNode).join("") || "";
1046
+ const url = node.fields?.url || "#";
1047
+ return `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${children}</a>`;
1048
+ }
1049
+ function getAlignment(format) {
1050
+ if (!format) return "left";
1051
+ if (format & 2) return "center";
1052
+ if (format & 3) return "right";
1053
+ if (format & 4) return "justify";
1054
+ return "left";
1055
+ }
1056
+ function escapeHtml(text) {
1057
+ const map = {
1058
+ "&": "&amp;",
1059
+ "<": "&lt;",
1060
+ ">": "&gt;",
1061
+ '"': "&quot;",
1062
+ "'": "&#039;"
1063
+ };
1064
+ return text.replace(/[&<>"']/g, (m) => map[m]);
1065
+ }
1066
+ function wrapInEmailTemplate(content, preheader) {
1067
+ return `<!DOCTYPE html>
1068
+ <html lang="en">
1069
+ <head>
1070
+ <meta charset="UTF-8">
1071
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1072
+ <title>Email</title>
1073
+ <!--[if mso]>
1074
+ <noscript>
1075
+ <xml>
1076
+ <o:OfficeDocumentSettings>
1077
+ <o:PixelsPerInch>96</o:PixelsPerInch>
1078
+ </o:OfficeDocumentSettings>
1079
+ </xml>
1080
+ </noscript>
1081
+ <![endif]-->
1082
+ </head>
1083
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #333333; background-color: #f3f4f6;">
1084
+ ${preheader ? `<div style="display: none; max-height: 0; overflow: hidden;">${escapeHtml(preheader)}</div>` : ""}
1085
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0;">
1086
+ <tr>
1087
+ <td align="center" style="padding: 20px 0;">
1088
+ <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
1089
+ <tr>
1090
+ <td style="padding: 40px 30px;">
1091
+ ${content}
1092
+ </td>
1093
+ </tr>
1094
+ </table>
1095
+ </td>
1096
+ </tr>
1097
+ </table>
1098
+ </body>
1099
+ </html>`;
1100
+ }
1101
+ function replacePersonalizationTags(html, sampleData) {
1102
+ return html.replace(/\{\{([^}]+)\}\}/g, (match, tag) => {
1103
+ const trimmedTag = tag.trim();
1104
+ return sampleData[trimmedTag] || match;
1105
+ });
1106
+ }
1107
+
1108
+ // src/utils/validateEmailHtml.ts
1109
+ function validateEmailHtml(html) {
1110
+ const warnings = [];
1111
+ const errors = [];
1112
+ const sizeInBytes = new Blob([html]).size;
1113
+ if (sizeInBytes > 102400) {
1114
+ warnings.push(`Email size (${Math.round(sizeInBytes / 1024)}KB) exceeds Gmail's 102KB limit - email may be clipped`);
1115
+ }
1116
+ if (html.includes("position:") && (html.includes("position: absolute") || html.includes("position: fixed"))) {
1117
+ errors.push("Absolute/fixed positioning is not supported in most email clients");
1118
+ }
1119
+ if (html.includes("display: flex") || html.includes("display: grid")) {
1120
+ errors.push("Flexbox and Grid layouts are not supported in many email clients");
1121
+ }
1122
+ if (html.includes("@media")) {
1123
+ warnings.push("Media queries may not work in all email clients");
1124
+ }
1125
+ const hasJavaScript = html.includes("<script") || html.includes("onclick") || html.includes("onload") || html.includes("javascript:");
1126
+ if (hasJavaScript) {
1127
+ errors.push("JavaScript is not supported in email and will be stripped by email clients");
1128
+ }
1129
+ const hasExternalStyles = html.includes("<link") && html.includes("stylesheet");
1130
+ if (hasExternalStyles) {
1131
+ errors.push("External stylesheets are not supported - use inline styles only");
1132
+ }
1133
+ if (html.includes("<form") || html.includes("<input") || html.includes("<button")) {
1134
+ errors.push("Forms and form elements are not reliably supported in email");
1135
+ }
1136
+ const unsupportedTags = [
1137
+ "video",
1138
+ "audio",
1139
+ "iframe",
1140
+ "embed",
1141
+ "object",
1142
+ "canvas",
1143
+ "svg"
1144
+ ];
1145
+ for (const tag of unsupportedTags) {
1146
+ if (html.includes(`<${tag}`)) {
1147
+ errors.push(`<${tag}> tags are not supported in email`);
1148
+ }
1149
+ }
1150
+ const imageCount = (html.match(/<img/g) || []).length;
1151
+ const linkCount = (html.match(/<a/g) || []).length;
1152
+ if (imageCount > 20) {
1153
+ warnings.push(`High number of images (${imageCount}) may affect email performance`);
1154
+ }
1155
+ const imagesWithoutAlt = (html.match(/<img(?![^>]*\balt\s*=)[^>]*>/g) || []).length;
1156
+ if (imagesWithoutAlt > 0) {
1157
+ warnings.push(`${imagesWithoutAlt} image(s) missing alt text - important for accessibility`);
1158
+ }
1159
+ const linksWithoutTarget = (html.match(/<a(?![^>]*\btarget\s*=)[^>]*>/g) || []).length;
1160
+ if (linksWithoutTarget > 0) {
1161
+ warnings.push(`${linksWithoutTarget} link(s) missing target="_blank" attribute`);
1162
+ }
1163
+ if (html.includes("margin: auto") || html.includes("margin:auto")) {
1164
+ warnings.push('margin: auto is not supported in Outlook - use align="center" or tables for centering');
1165
+ }
1166
+ if (html.includes("background-image")) {
1167
+ warnings.push("Background images are not reliably supported - consider using <img> tags instead");
1168
+ }
1169
+ if (html.match(/\d+\s*(rem|em)/)) {
1170
+ warnings.push("rem/em units may render inconsistently - use px for reliable sizing");
1171
+ }
1172
+ if (html.match(/margin[^:]*:\s*-\d+/)) {
1173
+ errors.push("Negative margins are not supported in many email clients");
1174
+ }
1175
+ const personalizationTags = html.match(/\{\{([^}]+)\}\}/g) || [];
1176
+ const validTags = ["subscriber.name", "subscriber.email", "subscriber.firstName", "subscriber.lastName"];
1177
+ for (const tag of personalizationTags) {
1178
+ const tagContent = tag.replace(/[{}]/g, "").trim();
1179
+ if (!validTags.includes(tagContent)) {
1180
+ warnings.push(`Unknown personalization tag: ${tag}`);
1181
+ }
1182
+ }
1183
+ return {
1184
+ valid: errors.length === 0,
1185
+ warnings,
1186
+ errors,
1187
+ stats: {
1188
+ sizeInBytes,
1189
+ imageCount,
1190
+ linkCount,
1191
+ hasExternalStyles,
1192
+ hasJavaScript
1193
+ }
1194
+ };
1195
+ }
1196
+
1197
+ // src/components/Broadcasts/EmailPreview.tsx
1198
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1199
+ var SAMPLE_DATA = {
1200
+ "subscriber.name": "John Doe",
1201
+ "subscriber.firstName": "John",
1202
+ "subscriber.lastName": "Doe",
1203
+ "subscriber.email": "john.doe@example.com"
1204
+ };
1205
+ var VIEWPORT_SIZES = {
1206
+ desktop: { width: 600, scale: 1 },
1207
+ mobile: { width: 320, scale: 0.8 }
1208
+ };
1209
+ var EmailPreview = ({
1210
+ content,
1211
+ subject,
1212
+ preheader,
1213
+ channel,
1214
+ mode = "desktop",
1215
+ onValidation
1216
+ }) => {
1217
+ const [html, setHtml] = (0, import_react5.useState)("");
1218
+ const [loading, setLoading] = (0, import_react5.useState)(false);
1219
+ const [validationResult, setValidationResult] = (0, import_react5.useState)(null);
1220
+ const iframeRef = (0, import_react5.useRef)(null);
1221
+ (0, import_react5.useEffect)(() => {
1222
+ const convertContent = async () => {
1223
+ if (!content) {
1224
+ setHtml("");
1225
+ return;
1226
+ }
1227
+ setLoading(true);
1228
+ try {
1229
+ const emailHtml = await convertToEmailSafeHtml(content, {
1230
+ wrapInTemplate: true,
1231
+ preheader
1232
+ });
1233
+ const personalizedHtml = replacePersonalizationTags(emailHtml, SAMPLE_DATA);
1234
+ const previewHtml = addEmailHeader(personalizedHtml, {
1235
+ subject,
1236
+ from: channel ? `${channel.fromName} <${channel.fromEmail}>` : "Newsletter <noreply@example.com>",
1237
+ to: SAMPLE_DATA["subscriber.email"]
1238
+ });
1239
+ setHtml(previewHtml);
1240
+ const validation = validateEmailHtml(emailHtml);
1241
+ setValidationResult(validation);
1242
+ onValidation?.(validation);
1243
+ } catch (error) {
1244
+ console.error("Failed to convert content to HTML:", error);
1245
+ setHtml("<p>Error converting content to HTML</p>");
1246
+ } finally {
1247
+ setLoading(false);
1248
+ }
1249
+ };
1250
+ convertContent();
1251
+ }, [content, subject, preheader, channel, onValidation]);
1252
+ (0, import_react5.useEffect)(() => {
1253
+ if (iframeRef.current && html) {
1254
+ const doc = iframeRef.current.contentDocument;
1255
+ if (doc) {
1256
+ doc.open();
1257
+ doc.write(html);
1258
+ doc.close();
1259
+ }
1260
+ }
1261
+ }, [html]);
1262
+ const viewport = VIEWPORT_SIZES[mode];
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
1264
+ validationResult && (validationResult.errors.length > 0 || validationResult.warnings.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { padding: "16px", borderBottom: "1px solid #e5e7eb" }, children: [
1265
+ validationResult.errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: "12px" }, children: [
1266
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h4", { style: { color: "#dc2626", margin: "0 0 8px 0", fontSize: "14px" }, children: [
1267
+ "Errors (",
1268
+ validationResult.errors.length,
1269
+ ")"
1270
+ ] }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#dc2626" }, children: validationResult.errors.map((error, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: error }, index)) })
1272
+ ] }),
1273
+ validationResult.warnings.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
1274
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h4", { style: { color: "#d97706", margin: "0 0 8px 0", fontSize: "14px" }, children: [
1275
+ "Warnings (",
1276
+ validationResult.warnings.length,
1277
+ ")"
1278
+ ] }),
1279
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: { margin: 0, paddingLeft: "20px", fontSize: "13px", color: "#d97706" }, children: validationResult.warnings.map((warning, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: warning }, index)) })
1280
+ ] })
1281
+ ] }),
1282
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1283
+ flex: 1,
1284
+ display: "flex",
1285
+ alignItems: "center",
1286
+ justifyContent: "center",
1287
+ backgroundColor: "#f3f4f6",
1288
+ padding: "20px",
1289
+ overflow: "auto"
1290
+ }, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Loading preview..." }) }) : html ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1291
+ backgroundColor: "white",
1292
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
1293
+ borderRadius: "8px",
1294
+ overflow: "hidden",
1295
+ transform: `scale(${viewport.scale})`,
1296
+ transformOrigin: "top center"
1297
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1298
+ "iframe",
1299
+ {
1300
+ ref: iframeRef,
1301
+ title: "Email Preview",
1302
+ style: {
1303
+ width: `${viewport.width}px`,
1304
+ height: "800px",
1305
+ border: "none",
1306
+ display: "block"
1307
+ },
1308
+ sandbox: "allow-same-origin"
1309
+ }
1310
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Start typing to see the email preview" }) }) }),
1311
+ validationResult && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
1312
+ padding: "12px 16px",
1313
+ borderTop: "1px solid #e5e7eb",
1314
+ fontSize: "13px",
1315
+ color: "#6b7280",
1316
+ display: "flex",
1317
+ gap: "24px"
1318
+ }, children: [
1319
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
1320
+ "Size: ",
1321
+ Math.round(validationResult.stats.sizeInBytes / 1024),
1322
+ "KB"
1323
+ ] }),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
1325
+ "Links: ",
1326
+ validationResult.stats.linkCount
1327
+ ] }),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
1329
+ "Images: ",
1330
+ validationResult.stats.imageCount
1331
+ ] }),
1332
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
1333
+ "Viewport: ",
1334
+ mode === "desktop" ? "600px" : "320px"
1335
+ ] })
1336
+ ] })
1337
+ ] });
1338
+ };
1339
+ function addEmailHeader(html, headers) {
1340
+ const headerHtml = `
1341
+ <div style="background-color: #f9fafb; border-bottom: 1px solid #e5e7eb; padding: 16px; font-family: monospace; font-size: 13px;">
1342
+ <div style="margin-bottom: 8px;"><strong>Subject:</strong> ${escapeHtml2(headers.subject)}</div>
1343
+ <div style="margin-bottom: 8px;"><strong>From:</strong> ${escapeHtml2(headers.from)}</div>
1344
+ <div><strong>To:</strong> ${escapeHtml2(headers.to)}</div>
1345
+ </div>
1346
+ `;
1347
+ return html.replace(/<body[^>]*>/, `$&${headerHtml}`);
1348
+ }
1349
+ function escapeHtml2(text) {
1350
+ const div = document.createElement("div");
1351
+ div.textContent = text;
1352
+ return div.innerHTML;
1353
+ }
1354
+
1355
+ // src/components/Broadcasts/EmailPreviewField.tsx
1356
+ var import_react6 = require("react");
1357
+ var import_ui = require("@payloadcms/ui");
1358
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1359
+ var EmailPreviewField = () => {
1360
+ const [previewMode, setPreviewMode] = (0, import_react6.useState)("desktop");
1361
+ const [isValid, setIsValid] = (0, import_react6.useState)(true);
1362
+ const [validationSummary, setValidationSummary] = (0, import_react6.useState)("");
1363
+ const fields = (0, import_ui.useFormFields)(([fields2]) => ({
1364
+ content: fields2.content,
1365
+ subject: fields2.subject,
1366
+ preheader: fields2.preheader,
1367
+ channel: fields2.channel
1368
+ }));
1369
+ const handleValidation = (result) => {
1370
+ setIsValid(result.valid);
1371
+ const errorCount = result.errors.length;
1372
+ const warningCount = result.warnings.length;
1373
+ if (errorCount > 0) {
1374
+ setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1375
+ } else if (warningCount > 0) {
1376
+ setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1377
+ } else {
1378
+ setValidationSummary("");
1379
+ }
1380
+ };
1381
+ const handleTestEmail = async () => {
1382
+ const pathParts = window.location.pathname.split("/");
1383
+ const broadcastId = pathParts[pathParts.length - 1];
1384
+ if (!broadcastId || broadcastId === "create") {
1385
+ alert("Please save the broadcast before sending a test email");
1386
+ return;
1387
+ }
1388
+ try {
1389
+ const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
1390
+ method: "POST",
1391
+ headers: {
1392
+ "Content-Type": "application/json"
1393
+ }
1394
+ });
1395
+ if (!response.ok) {
1396
+ const data = await response.json();
1397
+ throw new Error(data.error || "Failed to send test email");
1398
+ }
1399
+ alert("Test email sent successfully! Check your inbox.");
1400
+ } catch (error) {
1401
+ alert(error instanceof Error ? error.message : "Failed to send test email");
1402
+ }
1403
+ };
1404
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
1405
+ marginTop: "24px",
1406
+ border: "1px solid #e5e7eb",
1407
+ borderRadius: "8px",
1408
+ overflow: "hidden"
1409
+ }, children: [
1410
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
1411
+ display: "flex",
1412
+ alignItems: "center",
1413
+ justifyContent: "space-between",
1414
+ padding: "12px 16px",
1415
+ borderBottom: "1px solid #e5e7eb",
1416
+ backgroundColor: "#f9fafb"
1417
+ }, children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
1419
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 }, children: "Email Preview" }),
1420
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1422
+ "button",
1423
+ {
1424
+ type: "button",
1425
+ onClick: () => setPreviewMode("desktop"),
1426
+ style: {
1427
+ padding: "6px 12px",
1428
+ backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
1429
+ color: previewMode === "desktop" ? "white" : "#374151",
1430
+ border: "none",
1431
+ borderRadius: "4px 0 0 4px",
1432
+ fontSize: "14px",
1433
+ cursor: "pointer"
1434
+ },
1435
+ children: "Desktop"
1436
+ }
1437
+ ),
1438
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1439
+ "button",
1440
+ {
1441
+ type: "button",
1442
+ onClick: () => setPreviewMode("mobile"),
1443
+ style: {
1444
+ padding: "6px 12px",
1445
+ backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
1446
+ color: previewMode === "mobile" ? "white" : "#374151",
1447
+ border: "none",
1448
+ borderRadius: "0 4px 4px 0",
1449
+ fontSize: "14px",
1450
+ cursor: "pointer"
1451
+ },
1452
+ children: "Mobile"
1453
+ }
1454
+ )
1455
+ ] }),
1456
+ validationSummary && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
1457
+ padding: "6px 12px",
1458
+ backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
1459
+ color: isValid ? "#92400e" : "#991b1b",
1460
+ borderRadius: "4px",
1461
+ fontSize: "13px"
1462
+ }, children: validationSummary })
1463
+ ] }),
1464
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1465
+ "button",
1466
+ {
1467
+ type: "button",
1468
+ onClick: handleTestEmail,
1469
+ style: {
1470
+ padding: "6px 12px",
1471
+ backgroundColor: "#10b981",
1472
+ color: "white",
1473
+ border: "none",
1474
+ borderRadius: "4px",
1475
+ fontSize: "14px",
1476
+ cursor: "pointer"
1477
+ },
1478
+ children: "Send Test Email"
1479
+ }
1480
+ )
1481
+ ] }),
1482
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height: "600px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1483
+ EmailPreview,
1484
+ {
1485
+ content: fields.content?.value || null,
1486
+ subject: fields.subject?.value || "Email Subject",
1487
+ preheader: fields.preheader?.value,
1488
+ channel: fields.channel?.value,
1489
+ mode: previewMode,
1490
+ onValidation: handleValidation
1491
+ }
1492
+ ) })
1493
+ ] });
1494
+ };
1495
+
1496
+ // src/components/Broadcasts/BroadcastEditor.tsx
1497
+ var import_react7 = require("react");
1498
+ var import_ui2 = require("@payloadcms/ui");
1499
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1500
+ var BroadcastEditor = (props) => {
1501
+ const { value, setValue } = (0, import_ui2.useField)({ path: props.path });
1502
+ const [showPreview, setShowPreview] = (0, import_react7.useState)(true);
1503
+ const [previewMode, setPreviewMode] = (0, import_react7.useState)("desktop");
1504
+ const [isValid, setIsValid] = (0, import_react7.useState)(true);
1505
+ const [validationSummary, setValidationSummary] = (0, import_react7.useState)("");
1506
+ const fields = (0, import_ui2.useFormFields)(([fields2]) => ({
1507
+ subject: fields2.subject,
1508
+ preheader: fields2.preheader,
1509
+ channel: fields2.channel
1510
+ }));
1511
+ const handleValidation = (0, import_react7.useCallback)((result) => {
1512
+ setIsValid(result.valid);
1513
+ const errorCount = result.errors.length;
1514
+ const warningCount = result.warnings.length;
1515
+ if (errorCount > 0) {
1516
+ setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1517
+ } else if (warningCount > 0) {
1518
+ setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
1519
+ } else {
1520
+ setValidationSummary("");
1521
+ }
1522
+ }, []);
1523
+ const handleTestEmail = async () => {
1524
+ const pathParts = window.location.pathname.split("/");
1525
+ const broadcastId = pathParts[pathParts.length - 1];
1526
+ if (!broadcastId || broadcastId === "create") {
1527
+ alert("Please save the broadcast before sending a test email");
1528
+ return;
1529
+ }
1530
+ try {
1531
+ const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
1532
+ method: "POST",
1533
+ headers: {
1534
+ "Content-Type": "application/json"
1535
+ }
1536
+ });
1537
+ if (!response.ok) {
1538
+ const data = await response.json();
1539
+ throw new Error(data.error || "Failed to send test email");
1540
+ }
1541
+ alert("Test email sent successfully! Check your inbox.");
1542
+ } catch (error) {
1543
+ alert(error instanceof Error ? error.message : "Failed to send test email");
1544
+ }
1545
+ };
1546
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { height: "600px", display: "flex", flexDirection: "column" }, children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
1548
+ display: "flex",
1549
+ alignItems: "center",
1550
+ justifyContent: "space-between",
1551
+ padding: "12px 16px",
1552
+ borderBottom: "1px solid #e5e7eb",
1553
+ backgroundColor: "#f9fafb"
1554
+ }, children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
1556
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1557
+ "button",
1558
+ {
1559
+ type: "button",
1560
+ onClick: () => setShowPreview(!showPreview),
1561
+ style: {
1562
+ padding: "6px 12px",
1563
+ backgroundColor: showPreview ? "#3b82f6" : "#e5e7eb",
1564
+ color: showPreview ? "white" : "#374151",
1565
+ border: "none",
1566
+ borderRadius: "4px",
1567
+ fontSize: "14px",
1568
+ cursor: "pointer"
1569
+ },
1570
+ children: showPreview ? "Hide Preview" : "Show Preview"
1571
+ }
1572
+ ),
1573
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
1574
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1575
+ "button",
1576
+ {
1577
+ type: "button",
1578
+ onClick: () => setPreviewMode("desktop"),
1579
+ style: {
1580
+ padding: "6px 12px",
1581
+ backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
1582
+ color: previewMode === "desktop" ? "white" : "#374151",
1583
+ border: "none",
1584
+ borderRadius: "4px 0 0 4px",
1585
+ fontSize: "14px",
1586
+ cursor: "pointer"
1587
+ },
1588
+ children: "Desktop"
1589
+ }
1590
+ ),
1591
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1592
+ "button",
1593
+ {
1594
+ type: "button",
1595
+ onClick: () => setPreviewMode("mobile"),
1596
+ style: {
1597
+ padding: "6px 12px",
1598
+ backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
1599
+ color: previewMode === "mobile" ? "white" : "#374151",
1600
+ border: "none",
1601
+ borderRadius: "0 4px 4px 0",
1602
+ fontSize: "14px",
1603
+ cursor: "pointer"
1604
+ },
1605
+ children: "Mobile"
1606
+ }
1607
+ )
1608
+ ] }),
1609
+ showPreview && validationSummary && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
1610
+ padding: "6px 12px",
1611
+ backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
1612
+ color: isValid ? "#92400e" : "#991b1b",
1613
+ borderRadius: "4px",
1614
+ fontSize: "13px"
1615
+ }, children: validationSummary })
1616
+ ] }),
1617
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1618
+ "button",
1619
+ {
1620
+ type: "button",
1621
+ onClick: handleTestEmail,
1622
+ style: {
1623
+ padding: "6px 12px",
1624
+ backgroundColor: "#10b981",
1625
+ color: "white",
1626
+ border: "none",
1627
+ borderRadius: "4px",
1628
+ fontSize: "14px",
1629
+ cursor: "pointer"
1630
+ },
1631
+ children: "Send Test Email"
1632
+ }
1633
+ )
1634
+ ] }),
1635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
1637
+ flex: showPreview ? "0 0 50%" : "1",
1638
+ overflow: "auto",
1639
+ borderRight: showPreview ? "1px solid #e5e7eb" : "none"
1640
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rich-text-lexical" }) }) }),
1641
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: "0 0 50%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1642
+ EmailPreview,
1643
+ {
1644
+ content: value,
1645
+ subject: fields.subject?.value || "Email Subject",
1646
+ preheader: fields.preheader?.value,
1647
+ channel: fields.channel?.value,
1648
+ mode: previewMode,
1649
+ onValidation: handleValidation
1650
+ }
1651
+ ) })
1652
+ ] })
1653
+ ] });
1654
+ };
881
1655
  // Annotate the CommonJS export names for ESM import in node:
882
1656
  0 && (module.exports = {
1657
+ BroadcastEditor,
1658
+ EmailPreview,
1659
+ EmailPreviewField,
883
1660
  MagicLinkVerify,
884
1661
  NewsletterForm,
885
1662
  PreferencesForm,