payload-plugin-newsletter 0.16.9 → 0.17.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.
@@ -968,62 +968,73 @@ async function convertToEmailSafeHtml(editorState, options) {
968
968
  if (!editorState) {
969
969
  return "";
970
970
  }
971
- const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl);
971
+ const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
972
972
  const sanitizedHtml = import_isomorphic_dompurify.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
973
973
  if (options?.wrapInTemplate) {
974
974
  return wrapInEmailTemplate(sanitizedHtml, options.preheader);
975
975
  }
976
976
  return sanitizedHtml;
977
977
  }
978
- async function lexicalToEmailHtml(editorState, mediaUrl) {
978
+ async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
979
979
  const { root } = editorState;
980
980
  if (!root || !root.children) {
981
981
  return "";
982
982
  }
983
- const html = root.children.map((node) => convertNode(node, mediaUrl)).join("");
984
- return html;
983
+ const htmlParts = await Promise.all(
984
+ root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
985
+ );
986
+ return htmlParts.join("");
985
987
  }
986
- function convertNode(node, mediaUrl) {
988
+ async function convertNode(node, mediaUrl, customBlockConverter) {
987
989
  switch (node.type) {
988
990
  case "paragraph":
989
- return convertParagraph(node, mediaUrl);
991
+ return convertParagraph(node, mediaUrl, customBlockConverter);
990
992
  case "heading":
991
- return convertHeading(node, mediaUrl);
993
+ return convertHeading(node, mediaUrl, customBlockConverter);
992
994
  case "list":
993
- return convertList(node, mediaUrl);
995
+ return convertList(node, mediaUrl, customBlockConverter);
994
996
  case "listitem":
995
- return convertListItem(node, mediaUrl);
997
+ return convertListItem(node, mediaUrl, customBlockConverter);
996
998
  case "blockquote":
997
- return convertBlockquote(node, mediaUrl);
999
+ return convertBlockquote(node, mediaUrl, customBlockConverter);
998
1000
  case "text":
999
1001
  return convertText(node);
1000
1002
  case "link":
1001
- return convertLink(node, mediaUrl);
1003
+ return convertLink(node, mediaUrl, customBlockConverter);
1002
1004
  case "linebreak":
1003
1005
  return "<br>";
1004
1006
  case "upload":
1005
1007
  return convertUpload(node, mediaUrl);
1006
1008
  case "block":
1007
- return convertBlock(node, mediaUrl);
1009
+ return await convertBlock(node, mediaUrl, customBlockConverter);
1008
1010
  default:
1009
1011
  if (node.children) {
1010
- return node.children.map((child) => convertNode(child, mediaUrl)).join("");
1012
+ const childParts = await Promise.all(
1013
+ node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
1014
+ );
1015
+ return childParts.join("");
1011
1016
  }
1012
1017
  return "";
1013
1018
  }
1014
1019
  }
1015
- function convertParagraph(node, mediaUrl) {
1020
+ async function convertParagraph(node, mediaUrl, customBlockConverter) {
1016
1021
  const align = getAlignment(node.format);
1017
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1022
+ const childParts = await Promise.all(
1023
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1024
+ );
1025
+ const children = childParts.join("");
1018
1026
  if (!children.trim()) {
1019
1027
  return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
1020
1028
  }
1021
1029
  return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
1022
1030
  }
1023
- function convertHeading(node, mediaUrl) {
1031
+ async function convertHeading(node, mediaUrl, customBlockConverter) {
1024
1032
  const tag = node.tag || "h1";
1025
1033
  const align = getAlignment(node.format);
1026
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1034
+ const childParts = await Promise.all(
1035
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1036
+ );
1037
+ const children = childParts.join("");
1027
1038
  const styles = {
1028
1039
  h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
1029
1040
  h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
@@ -1032,18 +1043,27 @@ function convertHeading(node, mediaUrl) {
1032
1043
  const style = `${styles[tag] || styles.h3} text-align: ${align};`;
1033
1044
  return `<${tag} style="${style}">${children}</${tag}>`;
1034
1045
  }
1035
- function convertList(node, mediaUrl) {
1046
+ async function convertList(node, mediaUrl, customBlockConverter) {
1036
1047
  const tag = node.listType === "number" ? "ol" : "ul";
1037
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1048
+ const childParts = await Promise.all(
1049
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1050
+ );
1051
+ const children = childParts.join("");
1038
1052
  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;";
1039
1053
  return `<${tag} style="${style}">${children}</${tag}>`;
1040
1054
  }
1041
- function convertListItem(node, mediaUrl) {
1042
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1055
+ async function convertListItem(node, mediaUrl, customBlockConverter) {
1056
+ const childParts = await Promise.all(
1057
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1058
+ );
1059
+ const children = childParts.join("");
1043
1060
  return `<li style="margin: 0 0 8px 0;">${children}</li>`;
1044
1061
  }
1045
- function convertBlockquote(node, mediaUrl) {
1046
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1062
+ async function convertBlockquote(node, mediaUrl, customBlockConverter) {
1063
+ const childParts = await Promise.all(
1064
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1065
+ );
1066
+ const children = childParts.join("");
1047
1067
  const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
1048
1068
  return `<blockquote style="${style}">${children}</blockquote>`;
1049
1069
  }
@@ -1063,8 +1083,11 @@ function convertText(node) {
1063
1083
  }
1064
1084
  return text;
1065
1085
  }
1066
- function convertLink(node, mediaUrl) {
1067
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1086
+ async function convertLink(node, mediaUrl, customBlockConverter) {
1087
+ const childParts = await Promise.all(
1088
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1089
+ );
1090
+ const children = childParts.join("");
1068
1091
  const url = node.fields?.url || "#";
1069
1092
  const newTab = node.fields?.newTab ?? false;
1070
1093
  const targetAttr = newTab ? ' target="_blank"' : "";
@@ -1095,8 +1118,18 @@ function convertUpload(node, mediaUrl) {
1095
1118
  }
1096
1119
  return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
1097
1120
  }
1098
- function convertBlock(node, mediaUrl) {
1099
- const blockType = node.fields?.blockName;
1121
+ async function convertBlock(node, mediaUrl, customBlockConverter) {
1122
+ const blockType = node.fields?.blockName || node.blockName;
1123
+ if (customBlockConverter) {
1124
+ try {
1125
+ const customHtml = await customBlockConverter(node, mediaUrl);
1126
+ if (customHtml) {
1127
+ return customHtml;
1128
+ }
1129
+ } catch (error) {
1130
+ console.error(`Custom block converter error for ${blockType}:`, error);
1131
+ }
1132
+ }
1100
1133
  switch (blockType) {
1101
1134
  case "button":
1102
1135
  return convertButtonBlock(node.fields);
@@ -1104,7 +1137,10 @@ function convertBlock(node, mediaUrl) {
1104
1137
  return convertDividerBlock(node.fields);
1105
1138
  default:
1106
1139
  if (node.children) {
1107
- return node.children.map((child) => convertNode(child, mediaUrl)).join("");
1140
+ const childParts = await Promise.all(
1141
+ node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
1142
+ );
1143
+ return childParts.join("");
1108
1144
  }
1109
1145
  return "";
1110
1146
  }
@@ -1738,208 +1774,11 @@ var BroadcastEditor = (props) => {
1738
1774
  };
1739
1775
 
1740
1776
  // src/components/Broadcasts/BroadcastInlinePreview.tsx
1741
- var import_react9 = require("react");
1742
- var import_ui3 = require("@payloadcms/ui");
1743
-
1744
- // src/utils/contentTransformer.ts
1745
- async function transformContentForPreview(lexicalState, options = {}) {
1746
- const html = await convertToEmailSafeHtml(lexicalState, {
1747
- mediaUrl: options.mediaUrl
1748
- });
1749
- const processedHtml = processCustomBlocks(html);
1750
- return processedHtml;
1751
- }
1752
- function processCustomBlocks(html) {
1753
- return html;
1754
- }
1755
-
1756
- // src/email-templates/DefaultBroadcastTemplate.tsx
1757
- var import_components2 = require("@react-email/components");
1758
- var import_jsx_runtime7 = require("react/jsx-runtime");
1759
- var DefaultBroadcastTemplate = ({
1760
- subject,
1761
- preheader,
1762
- content
1763
- }) => {
1764
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_components2.Html, { children: [
1765
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Head, {}),
1766
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Preview, { children: preheader || subject }),
1767
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Body, { style: main, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_components2.Container, { style: container, children: [
1768
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Section, { style: contentSection, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { dangerouslySetInnerHTML: { __html: content } }) }),
1769
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Hr, { style: divider }),
1770
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_components2.Section, { style: footer, children: [
1771
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Text, { style: footerText, children: "You're receiving this email because you subscribed to our newsletter." }),
1772
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Text, { style: footerText, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_components2.Link, { href: "{{unsubscribe_url}}", style: footerLink, children: "Unsubscribe" }) })
1773
- ] })
1774
- ] }) })
1775
- ] });
1776
- };
1777
- var main = {
1778
- backgroundColor: "#ffffff",
1779
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
1780
- };
1781
- var container = {
1782
- margin: "0 auto",
1783
- padding: "40px 20px",
1784
- maxWidth: "600px"
1785
- };
1786
- var contentSection = {
1787
- fontSize: "16px",
1788
- lineHeight: "1.6",
1789
- color: "#374151"
1790
- };
1791
- var divider = {
1792
- borderColor: "#e5e7eb",
1793
- margin: "40px 0 20px"
1794
- };
1795
- var footer = {
1796
- textAlign: "center"
1797
- };
1798
- var footerText = {
1799
- fontSize: "14px",
1800
- lineHeight: "1.5",
1801
- color: "#6b7280",
1802
- margin: "0 0 10px"
1803
- };
1804
- var footerLink = {
1805
- color: "#6b7280",
1806
- textDecoration: "underline"
1807
- };
1808
-
1809
- // src/utils/templateLoader.ts
1810
- var TemplateLoader = class {
1811
- constructor() {
1812
- this.loadAttempted = false;
1813
- this.defaultTemplate = DefaultBroadcastTemplate;
1814
- }
1815
- async loadTemplate() {
1816
- if (!this.loadAttempted) {
1817
- this.loadAttempted = true;
1818
- await this.attemptCustomTemplateLoad();
1819
- }
1820
- return this.customTemplate || this.defaultTemplate;
1821
- }
1822
- async attemptCustomTemplateLoad() {
1823
- try {
1824
- const customTemplatePath = `${process.cwd()}/email-templates/broadcast-template`;
1825
- const module2 = await import(
1826
- /* @vite-ignore */
1827
- /* webpackIgnore: true */
1828
- customTemplatePath
1829
- ).catch(() => null);
1830
- if (module2) {
1831
- this.customTemplate = module2.default || module2.BroadcastTemplate;
1832
- }
1833
- } catch {
1834
- }
1835
- }
1836
- // Reset for testing
1837
- reset() {
1838
- this.customTemplate = void 0;
1839
- this.loadAttempted = false;
1840
- }
1841
- };
1842
- var templateLoader = new TemplateLoader();
1843
- async function loadTemplate() {
1844
- return templateLoader.loadTemplate();
1845
- }
1846
-
1847
- // src/components/Broadcasts/EmailRenderer.tsx
1848
1777
  var import_react8 = require("react");
1849
- var import_render = require("@react-email/render");
1850
- var import_jsx_runtime8 = require("react/jsx-runtime");
1851
- var EmailRenderer = ({
1852
- template,
1853
- data,
1854
- device = "desktop",
1855
- onRender
1856
- }) => {
1857
- const [renderedHtml, setRenderedHtml] = (0, import_react8.useState)("");
1858
- const [error, setError] = (0, import_react8.useState)(null);
1859
- const iframeRef = (0, import_react8.useRef)(null);
1860
- const renderEmail = (0, import_react8.useCallback)(async () => {
1861
- try {
1862
- const TemplateComponent = template;
1863
- const element = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TemplateComponent, { ...data });
1864
- const html = await (0, import_render.render)(element, {
1865
- pretty: true
1866
- });
1867
- setRenderedHtml(html);
1868
- onRender?.(html);
1869
- setError(null);
1870
- } catch (err) {
1871
- setError(err);
1872
- console.error("Failed to render email template:", err);
1873
- }
1874
- }, [template, data, onRender]);
1875
- (0, import_react8.useEffect)(() => {
1876
- renderEmail();
1877
- }, [renderEmail]);
1878
- (0, import_react8.useEffect)(() => {
1879
- if (iframeRef.current && renderedHtml) {
1880
- const iframe = iframeRef.current;
1881
- const doc = iframe.contentDocument || iframe.contentWindow?.document;
1882
- if (doc) {
1883
- doc.open();
1884
- doc.write(renderedHtml);
1885
- doc.close();
1886
- }
1887
- }
1888
- }, [renderedHtml]);
1889
- const containerStyle = {
1890
- width: "100%",
1891
- height: "100%",
1892
- display: "flex",
1893
- alignItems: "flex-start",
1894
- justifyContent: "center",
1895
- overflow: "auto",
1896
- padding: "2rem",
1897
- boxSizing: "border-box"
1898
- };
1899
- const iframeStyle = {
1900
- width: device === "mobile" ? "375px" : "600px",
1901
- height: "100%",
1902
- minHeight: "600px",
1903
- background: "white",
1904
- boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
1905
- borderRadius: device === "mobile" ? "20px" : "8px",
1906
- border: "none",
1907
- display: "block"
1908
- };
1909
- const errorStyle = {
1910
- background: "white",
1911
- border: "1px solid #ef4444",
1912
- borderRadius: "4px",
1913
- padding: "2rem",
1914
- maxWidth: "500px"
1915
- };
1916
- if (error) {
1917
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: errorStyle, children: [
1918
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: "Template Render Error" }),
1919
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { style: {
1920
- background: "#f9fafb",
1921
- padding: "1rem",
1922
- borderRadius: "4px",
1923
- overflowX: "auto",
1924
- fontSize: "12px",
1925
- color: "#374151",
1926
- margin: 0
1927
- }, children: error.message })
1928
- ] });
1929
- }
1930
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1931
- "iframe",
1932
- {
1933
- ref: iframeRef,
1934
- style: iframeStyle,
1935
- sandbox: "allow-same-origin",
1936
- title: "Email Preview"
1937
- }
1938
- ) });
1939
- };
1778
+ var import_ui3 = require("@payloadcms/ui");
1940
1779
 
1941
1780
  // src/components/Broadcasts/PreviewControls.tsx
1942
- var import_jsx_runtime9 = require("react/jsx-runtime");
1781
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1943
1782
  var PreviewControls = ({
1944
1783
  onUpdate,
1945
1784
  device,
@@ -1981,8 +1820,8 @@ var PreviewControls = ({
1981
1820
  cursor: "pointer",
1982
1821
  fontSize: "14px"
1983
1822
  });
1984
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: controlsStyle, children: [
1985
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1823
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: controlsStyle, children: [
1824
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1986
1825
  "button",
1987
1826
  {
1988
1827
  style: updateButtonStyle,
@@ -1991,33 +1830,33 @@ var PreviewControls = ({
1991
1830
  children: isLoading ? "Updating..." : "Update Preview"
1992
1831
  }
1993
1832
  ),
1994
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: deviceSelectorStyle, children: [
1995
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1833
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: deviceSelectorStyle, children: [
1834
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1996
1835
  "button",
1997
1836
  {
1998
1837
  style: deviceButtonStyle(device === "desktop"),
1999
1838
  onClick: () => onDeviceChange("desktop"),
2000
1839
  "aria-label": "Desktop view",
2001
1840
  children: [
2002
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2003
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
2004
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
2005
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
1841
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1842
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
1843
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
1844
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
2006
1845
  ] }),
2007
1846
  "Desktop"
2008
1847
  ]
2009
1848
  }
2010
1849
  ),
2011
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1850
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2012
1851
  "button",
2013
1852
  {
2014
1853
  style: deviceButtonStyle(device === "mobile"),
2015
1854
  onClick: () => onDeviceChange("mobile"),
2016
1855
  "aria-label": "Mobile view",
2017
1856
  children: [
2018
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
2019
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
2020
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
1857
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1858
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
1859
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
2021
1860
  ] }),
2022
1861
  "Mobile"
2023
1862
  ]
@@ -2028,19 +1867,19 @@ var PreviewControls = ({
2028
1867
  };
2029
1868
 
2030
1869
  // src/components/Broadcasts/BroadcastInlinePreview.tsx
2031
- var import_jsx_runtime10 = require("react/jsx-runtime");
1870
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2032
1871
  var BroadcastInlinePreview = () => {
2033
- const [device, setDevice] = (0, import_react9.useState)("desktop");
2034
- const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
2035
- const [showPreview, setShowPreview] = (0, import_react9.useState)(false);
2036
- const [previewData, setPreviewData] = (0, import_react9.useState)(null);
2037
- const [error, setError] = (0, import_react9.useState)(null);
1872
+ const [device, setDevice] = (0, import_react8.useState)("desktop");
1873
+ const [isLoading, setIsLoading] = (0, import_react8.useState)(false);
1874
+ const [showPreview, setShowPreview] = (0, import_react8.useState)(false);
1875
+ const [previewHtml, setPreviewHtml] = (0, import_react8.useState)(null);
1876
+ const [error, setError] = (0, import_react8.useState)(null);
2038
1877
  const fields = (0, import_ui3.useFormFields)(([fields2]) => ({
2039
1878
  subject: fields2["subject"]?.value,
2040
1879
  preheader: fields2["contentSection.preheader"]?.value,
2041
1880
  content: fields2["contentSection.content"]?.value
2042
1881
  }));
2043
- const updatePreview = (0, import_react9.useCallback)(async () => {
1882
+ const updatePreview = (0, import_react8.useCallback)(async () => {
2044
1883
  if (!fields.content) {
2045
1884
  setError(new Error("Please add some content before previewing"));
2046
1885
  return;
@@ -2048,18 +1887,22 @@ var BroadcastInlinePreview = () => {
2048
1887
  setIsLoading(true);
2049
1888
  setError(null);
2050
1889
  try {
2051
- const htmlContent = await transformContentForPreview(fields.content, {
2052
- mediaUrl: "/api/media"
2053
- });
2054
- const template = await loadTemplate();
2055
- setPreviewData({
2056
- template,
2057
- data: {
2058
- subject: fields.subject || "",
2059
- preheader: fields.preheader || "",
2060
- content: htmlContent
2061
- }
1890
+ const response = await fetch("/api/broadcasts/preview", {
1891
+ method: "POST",
1892
+ headers: {
1893
+ "Content-Type": "application/json"
1894
+ },
1895
+ body: JSON.stringify({
1896
+ content: fields.content,
1897
+ preheader: fields.preheader,
1898
+ subject: fields.subject
1899
+ })
2062
1900
  });
1901
+ const data = await response.json();
1902
+ if (!response.ok || !data.success) {
1903
+ throw new Error(data.error || "Failed to generate preview");
1904
+ }
1905
+ setPreviewHtml(data.preview.html);
2063
1906
  setShowPreview(true);
2064
1907
  } catch (err) {
2065
1908
  setError(err);
@@ -2111,10 +1954,10 @@ var BroadcastInlinePreview = () => {
2111
1954
  fontSize: "14px",
2112
1955
  fontWeight: 500
2113
1956
  };
2114
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: containerStyle, children: [
2115
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: headerStyle, children: [
2116
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { style: titleStyle, children: "Email Preview" }),
2117
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1957
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: containerStyle, children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: headerStyle, children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { style: titleStyle, children: "Email Preview" }),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2118
1961
  "button",
2119
1962
  {
2120
1963
  onClick: () => showPreview ? setShowPreview(false) : updatePreview(),
@@ -2124,9 +1967,9 @@ var BroadcastInlinePreview = () => {
2124
1967
  }
2125
1968
  )
2126
1969
  ] }),
2127
- showPreview && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: errorStyle, children: [
2128
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
2129
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1970
+ showPreview && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: errorStyle, children: [
1971
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
1972
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2130
1973
  "button",
2131
1974
  {
2132
1975
  onClick: updatePreview,
@@ -2141,8 +1984,8 @@ var BroadcastInlinePreview = () => {
2141
1984
  children: "Retry"
2142
1985
  }
2143
1986
  )
2144
- ] }) : previewData ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2145
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1987
+ ] }) : previewHtml ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1988
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2146
1989
  PreviewControls,
2147
1990
  {
2148
1991
  onUpdate: updatePreview,
@@ -2151,12 +1994,42 @@ var BroadcastInlinePreview = () => {
2151
1994
  isLoading
2152
1995
  }
2153
1996
  ),
2154
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2155
- EmailRenderer,
1997
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1998
+ "div",
2156
1999
  {
2157
- template: previewData.template,
2158
- data: previewData.data,
2159
- device
2000
+ style: {
2001
+ flex: 1,
2002
+ padding: device === "mobile" ? "1rem" : "2rem",
2003
+ display: "flex",
2004
+ justifyContent: "center",
2005
+ overflow: "auto"
2006
+ },
2007
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2008
+ "div",
2009
+ {
2010
+ style: {
2011
+ width: device === "mobile" ? "375px" : "600px",
2012
+ maxWidth: "100%",
2013
+ background: "white",
2014
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
2015
+ borderRadius: "8px",
2016
+ overflow: "hidden"
2017
+ },
2018
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2019
+ "iframe",
2020
+ {
2021
+ srcDoc: previewHtml,
2022
+ style: {
2023
+ width: "100%",
2024
+ height: "100%",
2025
+ minHeight: "600px",
2026
+ border: "none"
2027
+ },
2028
+ title: "Email Preview"
2029
+ }
2030
+ )
2031
+ }
2032
+ )
2160
2033
  }
2161
2034
  )
2162
2035
  ] }) : null })
@@ -2164,9 +2037,9 @@ var BroadcastInlinePreview = () => {
2164
2037
  };
2165
2038
 
2166
2039
  // src/components/Broadcasts/BroadcastPreviewField.tsx
2167
- var import_jsx_runtime11 = require("react/jsx-runtime");
2040
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2168
2041
  var BroadcastPreviewField = () => {
2169
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: {
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: {
2170
2043
  padding: "1rem",
2171
2044
  background: "#f9fafb",
2172
2045
  borderRadius: "4px",
@@ -2175,8 +2048,102 @@ var BroadcastPreviewField = () => {
2175
2048
  }, children: "Email preview is available inline below the content editor." });
2176
2049
  };
2177
2050
 
2051
+ // src/components/Broadcasts/EmailRenderer.tsx
2052
+ var import_react9 = require("react");
2053
+ var import_render = require("@react-email/render");
2054
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2055
+ var EmailRenderer = ({
2056
+ template,
2057
+ data,
2058
+ device = "desktop",
2059
+ onRender
2060
+ }) => {
2061
+ const [renderedHtml, setRenderedHtml] = (0, import_react9.useState)("");
2062
+ const [error, setError] = (0, import_react9.useState)(null);
2063
+ const iframeRef = (0, import_react9.useRef)(null);
2064
+ const renderEmail = (0, import_react9.useCallback)(async () => {
2065
+ try {
2066
+ const TemplateComponent = template;
2067
+ const element = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TemplateComponent, { ...data });
2068
+ const html = await (0, import_render.render)(element, {
2069
+ pretty: true
2070
+ });
2071
+ setRenderedHtml(html);
2072
+ onRender?.(html);
2073
+ setError(null);
2074
+ } catch (err) {
2075
+ setError(err);
2076
+ console.error("Failed to render email template:", err);
2077
+ }
2078
+ }, [template, data, onRender]);
2079
+ (0, import_react9.useEffect)(() => {
2080
+ renderEmail();
2081
+ }, [renderEmail]);
2082
+ (0, import_react9.useEffect)(() => {
2083
+ if (iframeRef.current && renderedHtml) {
2084
+ const iframe = iframeRef.current;
2085
+ const doc = iframe.contentDocument || iframe.contentWindow?.document;
2086
+ if (doc) {
2087
+ doc.open();
2088
+ doc.write(renderedHtml);
2089
+ doc.close();
2090
+ }
2091
+ }
2092
+ }, [renderedHtml]);
2093
+ const containerStyle = {
2094
+ width: "100%",
2095
+ height: "100%",
2096
+ display: "flex",
2097
+ alignItems: "flex-start",
2098
+ justifyContent: "center",
2099
+ overflow: "auto",
2100
+ padding: "2rem",
2101
+ boxSizing: "border-box"
2102
+ };
2103
+ const iframeStyle = {
2104
+ width: device === "mobile" ? "375px" : "600px",
2105
+ height: "100%",
2106
+ minHeight: "600px",
2107
+ background: "white",
2108
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
2109
+ borderRadius: device === "mobile" ? "20px" : "8px",
2110
+ border: "none",
2111
+ display: "block"
2112
+ };
2113
+ const errorStyle = {
2114
+ background: "white",
2115
+ border: "1px solid #ef4444",
2116
+ borderRadius: "4px",
2117
+ padding: "2rem",
2118
+ maxWidth: "500px"
2119
+ };
2120
+ if (error) {
2121
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: errorStyle, children: [
2122
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: "Template Render Error" }),
2123
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { style: {
2124
+ background: "#f9fafb",
2125
+ padding: "1rem",
2126
+ borderRadius: "4px",
2127
+ overflowX: "auto",
2128
+ fontSize: "12px",
2129
+ color: "#374151",
2130
+ margin: 0
2131
+ }, children: error.message })
2132
+ ] });
2133
+ }
2134
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2135
+ "iframe",
2136
+ {
2137
+ ref: iframeRef,
2138
+ style: iframeStyle,
2139
+ sandbox: "allow-same-origin",
2140
+ title: "Email Preview"
2141
+ }
2142
+ ) });
2143
+ };
2144
+
2178
2145
  // src/components/Broadcasts/StatusBadge.tsx
2179
- var import_jsx_runtime12 = require("react/jsx-runtime");
2146
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2180
2147
  var statusConfig = {
2181
2148
  ["draft" /* DRAFT */]: {
2182
2149
  label: "Draft",
@@ -2224,7 +2191,7 @@ var statusConfig = {
2224
2191
  var StatusBadge = ({ cellData }) => {
2225
2192
  const status = cellData;
2226
2193
  const config = statusConfig[status] || statusConfig["draft" /* DRAFT */];
2227
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2194
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2228
2195
  "span",
2229
2196
  {
2230
2197
  style: {
@@ -2246,6 +2213,59 @@ var StatusBadge = ({ cellData }) => {
2246
2213
  var EmptyField = () => {
2247
2214
  return null;
2248
2215
  };
2216
+
2217
+ // src/email-templates/DefaultBroadcastTemplate.tsx
2218
+ var import_components2 = require("@react-email/components");
2219
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2220
+ var DefaultBroadcastTemplate = ({
2221
+ subject,
2222
+ preheader,
2223
+ content
2224
+ }) => {
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_components2.Html, { children: [
2226
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Head, {}),
2227
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Preview, { children: preheader || subject }),
2228
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Body, { style: main, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_components2.Container, { style: container, children: [
2229
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Section, { style: contentSection, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { dangerouslySetInnerHTML: { __html: content } }) }),
2230
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Hr, { style: divider }),
2231
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_components2.Section, { style: footer, children: [
2232
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Text, { style: footerText, children: "You're receiving this email because you subscribed to our newsletter." }),
2233
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Text, { style: footerText, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_components2.Link, { href: "{{unsubscribe_url}}", style: footerLink, children: "Unsubscribe" }) })
2234
+ ] })
2235
+ ] }) })
2236
+ ] });
2237
+ };
2238
+ var main = {
2239
+ backgroundColor: "#ffffff",
2240
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
2241
+ };
2242
+ var container = {
2243
+ margin: "0 auto",
2244
+ padding: "40px 20px",
2245
+ maxWidth: "600px"
2246
+ };
2247
+ var contentSection = {
2248
+ fontSize: "16px",
2249
+ lineHeight: "1.6",
2250
+ color: "#374151"
2251
+ };
2252
+ var divider = {
2253
+ borderColor: "#e5e7eb",
2254
+ margin: "40px 0 20px"
2255
+ };
2256
+ var footer = {
2257
+ textAlign: "center"
2258
+ };
2259
+ var footerText = {
2260
+ fontSize: "14px",
2261
+ lineHeight: "1.5",
2262
+ color: "#6b7280",
2263
+ margin: "0 0 10px"
2264
+ };
2265
+ var footerLink = {
2266
+ color: "#6b7280",
2267
+ textDecoration: "underline"
2268
+ };
2249
2269
  // Annotate the CommonJS export names for ESM import in node:
2250
2270
  0 && (module.exports = {
2251
2271
  BroadcastEditor,