payload-plugin-newsletter 0.16.10 → 0.17.1

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.
@@ -917,62 +917,73 @@ async function convertToEmailSafeHtml(editorState, options) {
917
917
  if (!editorState) {
918
918
  return "";
919
919
  }
920
- const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl);
920
+ const rawHtml = await lexicalToEmailHtml(editorState, options?.mediaUrl, options?.customBlockConverter);
921
921
  const sanitizedHtml = DOMPurify.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
922
922
  if (options?.wrapInTemplate) {
923
923
  return wrapInEmailTemplate(sanitizedHtml, options.preheader);
924
924
  }
925
925
  return sanitizedHtml;
926
926
  }
927
- async function lexicalToEmailHtml(editorState, mediaUrl) {
927
+ async function lexicalToEmailHtml(editorState, mediaUrl, customBlockConverter) {
928
928
  const { root } = editorState;
929
929
  if (!root || !root.children) {
930
930
  return "";
931
931
  }
932
- const html = root.children.map((node) => convertNode(node, mediaUrl)).join("");
933
- return html;
932
+ const htmlParts = await Promise.all(
933
+ root.children.map((node) => convertNode(node, mediaUrl, customBlockConverter))
934
+ );
935
+ return htmlParts.join("");
934
936
  }
935
- function convertNode(node, mediaUrl) {
937
+ async function convertNode(node, mediaUrl, customBlockConverter) {
936
938
  switch (node.type) {
937
939
  case "paragraph":
938
- return convertParagraph(node, mediaUrl);
940
+ return convertParagraph(node, mediaUrl, customBlockConverter);
939
941
  case "heading":
940
- return convertHeading(node, mediaUrl);
942
+ return convertHeading(node, mediaUrl, customBlockConverter);
941
943
  case "list":
942
- return convertList(node, mediaUrl);
944
+ return convertList(node, mediaUrl, customBlockConverter);
943
945
  case "listitem":
944
- return convertListItem(node, mediaUrl);
946
+ return convertListItem(node, mediaUrl, customBlockConverter);
945
947
  case "blockquote":
946
- return convertBlockquote(node, mediaUrl);
948
+ return convertBlockquote(node, mediaUrl, customBlockConverter);
947
949
  case "text":
948
950
  return convertText(node);
949
951
  case "link":
950
- return convertLink(node, mediaUrl);
952
+ return convertLink(node, mediaUrl, customBlockConverter);
951
953
  case "linebreak":
952
954
  return "<br>";
953
955
  case "upload":
954
956
  return convertUpload(node, mediaUrl);
955
957
  case "block":
956
- return convertBlock(node, mediaUrl);
958
+ return await convertBlock(node, mediaUrl, customBlockConverter);
957
959
  default:
958
960
  if (node.children) {
959
- return node.children.map((child) => convertNode(child, mediaUrl)).join("");
961
+ const childParts = await Promise.all(
962
+ node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
963
+ );
964
+ return childParts.join("");
960
965
  }
961
966
  return "";
962
967
  }
963
968
  }
964
- function convertParagraph(node, mediaUrl) {
969
+ async function convertParagraph(node, mediaUrl, customBlockConverter) {
965
970
  const align = getAlignment(node.format);
966
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
971
+ const childParts = await Promise.all(
972
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
973
+ );
974
+ const children = childParts.join("");
967
975
  if (!children.trim()) {
968
976
  return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
969
977
  }
970
978
  return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
971
979
  }
972
- function convertHeading(node, mediaUrl) {
980
+ async function convertHeading(node, mediaUrl, customBlockConverter) {
973
981
  const tag = node.tag || "h1";
974
982
  const align = getAlignment(node.format);
975
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
983
+ const childParts = await Promise.all(
984
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
985
+ );
986
+ const children = childParts.join("");
976
987
  const styles = {
977
988
  h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
978
989
  h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
@@ -981,18 +992,27 @@ function convertHeading(node, mediaUrl) {
981
992
  const style = `${styles[tag] || styles.h3} text-align: ${align};`;
982
993
  return `<${tag} style="${style}">${children}</${tag}>`;
983
994
  }
984
- function convertList(node, mediaUrl) {
995
+ async function convertList(node, mediaUrl, customBlockConverter) {
985
996
  const tag = node.listType === "number" ? "ol" : "ul";
986
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
997
+ const childParts = await Promise.all(
998
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
999
+ );
1000
+ const children = childParts.join("");
987
1001
  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;";
988
1002
  return `<${tag} style="${style}">${children}</${tag}>`;
989
1003
  }
990
- function convertListItem(node, mediaUrl) {
991
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1004
+ async function convertListItem(node, mediaUrl, customBlockConverter) {
1005
+ const childParts = await Promise.all(
1006
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1007
+ );
1008
+ const children = childParts.join("");
992
1009
  return `<li style="margin: 0 0 8px 0;">${children}</li>`;
993
1010
  }
994
- function convertBlockquote(node, mediaUrl) {
995
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1011
+ async function convertBlockquote(node, mediaUrl, customBlockConverter) {
1012
+ const childParts = await Promise.all(
1013
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1014
+ );
1015
+ const children = childParts.join("");
996
1016
  const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
997
1017
  return `<blockquote style="${style}">${children}</blockquote>`;
998
1018
  }
@@ -1012,8 +1032,11 @@ function convertText(node) {
1012
1032
  }
1013
1033
  return text;
1014
1034
  }
1015
- function convertLink(node, mediaUrl) {
1016
- const children = node.children?.map((child) => convertNode(child, mediaUrl)).join("") || "";
1035
+ async function convertLink(node, mediaUrl, customBlockConverter) {
1036
+ const childParts = await Promise.all(
1037
+ (node.children || []).map((child) => convertNode(child, mediaUrl, customBlockConverter))
1038
+ );
1039
+ const children = childParts.join("");
1017
1040
  const url = node.fields?.url || "#";
1018
1041
  const newTab = node.fields?.newTab ?? false;
1019
1042
  const targetAttr = newTab ? ' target="_blank"' : "";
@@ -1044,8 +1067,18 @@ function convertUpload(node, mediaUrl) {
1044
1067
  }
1045
1068
  return `<div style="margin: 0 0 16px 0; text-align: center;">${imgHtml}</div>`;
1046
1069
  }
1047
- function convertBlock(node, mediaUrl) {
1048
- const blockType = node.fields?.blockName;
1070
+ async function convertBlock(node, mediaUrl, customBlockConverter) {
1071
+ const blockType = node.fields?.blockName || node.blockName;
1072
+ if (customBlockConverter) {
1073
+ try {
1074
+ const customHtml = await customBlockConverter(node, mediaUrl);
1075
+ if (customHtml) {
1076
+ return customHtml;
1077
+ }
1078
+ } catch (error) {
1079
+ console.error(`Custom block converter error for ${blockType}:`, error);
1080
+ }
1081
+ }
1049
1082
  switch (blockType) {
1050
1083
  case "button":
1051
1084
  return convertButtonBlock(node.fields);
@@ -1053,7 +1086,10 @@ function convertBlock(node, mediaUrl) {
1053
1086
  return convertDividerBlock(node.fields);
1054
1087
  default:
1055
1088
  if (node.children) {
1056
- return node.children.map((child) => convertNode(child, mediaUrl)).join("");
1089
+ const childParts = await Promise.all(
1090
+ node.children.map((child) => convertNode(child, mediaUrl, customBlockConverter))
1091
+ );
1092
+ return childParts.join("");
1057
1093
  }
1058
1094
  return "";
1059
1095
  }
@@ -1687,218 +1723,11 @@ var BroadcastEditor = (props) => {
1687
1723
  };
1688
1724
 
1689
1725
  // src/components/Broadcasts/BroadcastInlinePreview.tsx
1690
- import { useState as useState9, useCallback as useCallback4 } from "react";
1726
+ import { useState as useState8, useCallback as useCallback3 } from "react";
1691
1727
  import { useFormFields as useFormFields3 } from "@payloadcms/ui";
1692
1728
 
1693
- // src/utils/contentTransformer.ts
1694
- async function transformContentForPreview(lexicalState, options = {}) {
1695
- const html = await convertToEmailSafeHtml(lexicalState, {
1696
- mediaUrl: options.mediaUrl
1697
- });
1698
- const processedHtml = processCustomBlocks(html);
1699
- return processedHtml;
1700
- }
1701
- function processCustomBlocks(html) {
1702
- return html;
1703
- }
1704
-
1705
- // src/email-templates/DefaultBroadcastTemplate.tsx
1706
- import {
1707
- Body,
1708
- Container,
1709
- Head,
1710
- Hr,
1711
- Html,
1712
- Link,
1713
- Preview,
1714
- Section,
1715
- Text
1716
- } from "@react-email/components";
1717
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1718
- var DefaultBroadcastTemplate = ({
1719
- subject,
1720
- preheader,
1721
- content
1722
- }) => {
1723
- return /* @__PURE__ */ jsxs7(Html, { children: [
1724
- /* @__PURE__ */ jsx7(Head, {}),
1725
- /* @__PURE__ */ jsx7(Preview, { children: preheader || subject }),
1726
- /* @__PURE__ */ jsx7(Body, { style: main, children: /* @__PURE__ */ jsxs7(Container, { style: container, children: [
1727
- /* @__PURE__ */ jsx7(Section, { style: contentSection, children: /* @__PURE__ */ jsx7("div", { dangerouslySetInnerHTML: { __html: content } }) }),
1728
- /* @__PURE__ */ jsx7(Hr, { style: divider }),
1729
- /* @__PURE__ */ jsxs7(Section, { style: footer, children: [
1730
- /* @__PURE__ */ jsx7(Text, { style: footerText, children: "You're receiving this email because you subscribed to our newsletter." }),
1731
- /* @__PURE__ */ jsx7(Text, { style: footerText, children: /* @__PURE__ */ jsx7(Link, { href: "{{unsubscribe_url}}", style: footerLink, children: "Unsubscribe" }) })
1732
- ] })
1733
- ] }) })
1734
- ] });
1735
- };
1736
- var main = {
1737
- backgroundColor: "#ffffff",
1738
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
1739
- };
1740
- var container = {
1741
- margin: "0 auto",
1742
- padding: "40px 20px",
1743
- maxWidth: "600px"
1744
- };
1745
- var contentSection = {
1746
- fontSize: "16px",
1747
- lineHeight: "1.6",
1748
- color: "#374151"
1749
- };
1750
- var divider = {
1751
- borderColor: "#e5e7eb",
1752
- margin: "40px 0 20px"
1753
- };
1754
- var footer = {
1755
- textAlign: "center"
1756
- };
1757
- var footerText = {
1758
- fontSize: "14px",
1759
- lineHeight: "1.5",
1760
- color: "#6b7280",
1761
- margin: "0 0 10px"
1762
- };
1763
- var footerLink = {
1764
- color: "#6b7280",
1765
- textDecoration: "underline"
1766
- };
1767
-
1768
- // src/utils/templateLoader.ts
1769
- var TemplateLoader = class {
1770
- constructor() {
1771
- this.loadAttempted = false;
1772
- this.defaultTemplate = DefaultBroadcastTemplate;
1773
- }
1774
- async loadTemplate() {
1775
- if (!this.loadAttempted) {
1776
- this.loadAttempted = true;
1777
- await this.attemptCustomTemplateLoad();
1778
- }
1779
- return this.customTemplate || this.defaultTemplate;
1780
- }
1781
- async attemptCustomTemplateLoad() {
1782
- try {
1783
- const customTemplatePath = `${process.cwd()}/email-templates/broadcast-template`;
1784
- const module = await import(
1785
- /* @vite-ignore */
1786
- /* webpackIgnore: true */
1787
- customTemplatePath
1788
- ).catch(() => null);
1789
- if (module) {
1790
- this.customTemplate = module.default || module.BroadcastTemplate;
1791
- }
1792
- } catch {
1793
- }
1794
- }
1795
- // Reset for testing
1796
- reset() {
1797
- this.customTemplate = void 0;
1798
- this.loadAttempted = false;
1799
- }
1800
- };
1801
- var templateLoader = new TemplateLoader();
1802
- async function loadTemplate() {
1803
- return templateLoader.loadTemplate();
1804
- }
1805
-
1806
- // src/components/Broadcasts/EmailRenderer.tsx
1807
- import { useEffect as useEffect5, useState as useState8, useCallback as useCallback3, useRef as useRef2 } from "react";
1808
- import { render } from "@react-email/render";
1809
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1810
- var EmailRenderer = ({
1811
- template,
1812
- data,
1813
- device = "desktop",
1814
- onRender
1815
- }) => {
1816
- const [renderedHtml, setRenderedHtml] = useState8("");
1817
- const [error, setError] = useState8(null);
1818
- const iframeRef = useRef2(null);
1819
- const renderEmail = useCallback3(async () => {
1820
- try {
1821
- const TemplateComponent = template;
1822
- const element = /* @__PURE__ */ jsx8(TemplateComponent, { ...data });
1823
- const html = await render(element, {
1824
- pretty: true
1825
- });
1826
- setRenderedHtml(html);
1827
- onRender?.(html);
1828
- setError(null);
1829
- } catch (err) {
1830
- setError(err);
1831
- console.error("Failed to render email template:", err);
1832
- }
1833
- }, [template, data, onRender]);
1834
- useEffect5(() => {
1835
- renderEmail();
1836
- }, [renderEmail]);
1837
- useEffect5(() => {
1838
- if (iframeRef.current && renderedHtml) {
1839
- const iframe = iframeRef.current;
1840
- const doc = iframe.contentDocument || iframe.contentWindow?.document;
1841
- if (doc) {
1842
- doc.open();
1843
- doc.write(renderedHtml);
1844
- doc.close();
1845
- }
1846
- }
1847
- }, [renderedHtml]);
1848
- const containerStyle = {
1849
- width: "100%",
1850
- height: "100%",
1851
- display: "flex",
1852
- alignItems: "flex-start",
1853
- justifyContent: "center",
1854
- overflow: "auto",
1855
- padding: "2rem",
1856
- boxSizing: "border-box"
1857
- };
1858
- const iframeStyle = {
1859
- width: device === "mobile" ? "375px" : "600px",
1860
- height: "100%",
1861
- minHeight: "600px",
1862
- background: "white",
1863
- boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
1864
- borderRadius: device === "mobile" ? "20px" : "8px",
1865
- border: "none",
1866
- display: "block"
1867
- };
1868
- const errorStyle = {
1869
- background: "white",
1870
- border: "1px solid #ef4444",
1871
- borderRadius: "4px",
1872
- padding: "2rem",
1873
- maxWidth: "500px"
1874
- };
1875
- if (error) {
1876
- return /* @__PURE__ */ jsxs8("div", { style: errorStyle, children: [
1877
- /* @__PURE__ */ jsx8("h3", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: "Template Render Error" }),
1878
- /* @__PURE__ */ jsx8("pre", { style: {
1879
- background: "#f9fafb",
1880
- padding: "1rem",
1881
- borderRadius: "4px",
1882
- overflowX: "auto",
1883
- fontSize: "12px",
1884
- color: "#374151",
1885
- margin: 0
1886
- }, children: error.message })
1887
- ] });
1888
- }
1889
- return /* @__PURE__ */ jsx8("div", { style: containerStyle, children: /* @__PURE__ */ jsx8(
1890
- "iframe",
1891
- {
1892
- ref: iframeRef,
1893
- style: iframeStyle,
1894
- sandbox: "allow-same-origin",
1895
- title: "Email Preview"
1896
- }
1897
- ) });
1898
- };
1899
-
1900
1729
  // src/components/Broadcasts/PreviewControls.tsx
1901
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1730
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1902
1731
  var PreviewControls = ({
1903
1732
  onUpdate,
1904
1733
  device,
@@ -1940,8 +1769,8 @@ var PreviewControls = ({
1940
1769
  cursor: "pointer",
1941
1770
  fontSize: "14px"
1942
1771
  });
1943
- return /* @__PURE__ */ jsxs9("div", { style: controlsStyle, children: [
1944
- /* @__PURE__ */ jsx9(
1772
+ return /* @__PURE__ */ jsxs7("div", { style: controlsStyle, children: [
1773
+ /* @__PURE__ */ jsx7(
1945
1774
  "button",
1946
1775
  {
1947
1776
  style: updateButtonStyle,
@@ -1950,33 +1779,33 @@ var PreviewControls = ({
1950
1779
  children: isLoading ? "Updating..." : "Update Preview"
1951
1780
  }
1952
1781
  ),
1953
- /* @__PURE__ */ jsxs9("div", { style: deviceSelectorStyle, children: [
1954
- /* @__PURE__ */ jsxs9(
1782
+ /* @__PURE__ */ jsxs7("div", { style: deviceSelectorStyle, children: [
1783
+ /* @__PURE__ */ jsxs7(
1955
1784
  "button",
1956
1785
  {
1957
1786
  style: deviceButtonStyle(device === "desktop"),
1958
1787
  onClick: () => onDeviceChange("desktop"),
1959
1788
  "aria-label": "Desktop view",
1960
1789
  children: [
1961
- /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1962
- /* @__PURE__ */ jsx9("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
1963
- /* @__PURE__ */ jsx9("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
1964
- /* @__PURE__ */ jsx9("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
1790
+ /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1791
+ /* @__PURE__ */ jsx7("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
1792
+ /* @__PURE__ */ jsx7("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
1793
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
1965
1794
  ] }),
1966
1795
  "Desktop"
1967
1796
  ]
1968
1797
  }
1969
1798
  ),
1970
- /* @__PURE__ */ jsxs9(
1799
+ /* @__PURE__ */ jsxs7(
1971
1800
  "button",
1972
1801
  {
1973
1802
  style: deviceButtonStyle(device === "mobile"),
1974
1803
  onClick: () => onDeviceChange("mobile"),
1975
1804
  "aria-label": "Mobile view",
1976
1805
  children: [
1977
- /* @__PURE__ */ jsxs9("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1978
- /* @__PURE__ */ jsx9("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
1979
- /* @__PURE__ */ jsx9("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
1806
+ /* @__PURE__ */ jsxs7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1807
+ /* @__PURE__ */ jsx7("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2", ry: "2" }),
1808
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "18", x2: "12", y2: "18" })
1980
1809
  ] }),
1981
1810
  "Mobile"
1982
1811
  ]
@@ -1987,19 +1816,19 @@ var PreviewControls = ({
1987
1816
  };
1988
1817
 
1989
1818
  // src/components/Broadcasts/BroadcastInlinePreview.tsx
1990
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1819
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1991
1820
  var BroadcastInlinePreview = () => {
1992
- const [device, setDevice] = useState9("desktop");
1993
- const [isLoading, setIsLoading] = useState9(false);
1994
- const [showPreview, setShowPreview] = useState9(false);
1995
- const [previewData, setPreviewData] = useState9(null);
1996
- const [error, setError] = useState9(null);
1821
+ const [device, setDevice] = useState8("desktop");
1822
+ const [isLoading, setIsLoading] = useState8(false);
1823
+ const [showPreview, setShowPreview] = useState8(false);
1824
+ const [previewHtml, setPreviewHtml] = useState8(null);
1825
+ const [error, setError] = useState8(null);
1997
1826
  const fields = useFormFields3(([fields2]) => ({
1998
1827
  subject: fields2["subject"]?.value,
1999
1828
  preheader: fields2["contentSection.preheader"]?.value,
2000
1829
  content: fields2["contentSection.content"]?.value
2001
1830
  }));
2002
- const updatePreview = useCallback4(async () => {
1831
+ const updatePreview = useCallback3(async () => {
2003
1832
  if (!fields.content) {
2004
1833
  setError(new Error("Please add some content before previewing"));
2005
1834
  return;
@@ -2007,18 +1836,22 @@ var BroadcastInlinePreview = () => {
2007
1836
  setIsLoading(true);
2008
1837
  setError(null);
2009
1838
  try {
2010
- const htmlContent = await transformContentForPreview(fields.content, {
2011
- mediaUrl: "/api/media"
2012
- });
2013
- const template = await loadTemplate();
2014
- setPreviewData({
2015
- template,
2016
- data: {
2017
- subject: fields.subject || "",
2018
- preheader: fields.preheader || "",
2019
- content: htmlContent
2020
- }
1839
+ const response = await fetch("/api/broadcasts/preview", {
1840
+ method: "POST",
1841
+ headers: {
1842
+ "Content-Type": "application/json"
1843
+ },
1844
+ body: JSON.stringify({
1845
+ content: fields.content,
1846
+ preheader: fields.preheader,
1847
+ subject: fields.subject
1848
+ })
2021
1849
  });
1850
+ const data = await response.json();
1851
+ if (!response.ok || !data.success) {
1852
+ throw new Error(data.error || "Failed to generate preview");
1853
+ }
1854
+ setPreviewHtml(data.preview.html);
2022
1855
  setShowPreview(true);
2023
1856
  } catch (err) {
2024
1857
  setError(err);
@@ -2070,10 +1903,10 @@ var BroadcastInlinePreview = () => {
2070
1903
  fontSize: "14px",
2071
1904
  fontWeight: 500
2072
1905
  };
2073
- return /* @__PURE__ */ jsxs10("div", { style: containerStyle, children: [
2074
- /* @__PURE__ */ jsxs10("div", { style: headerStyle, children: [
2075
- /* @__PURE__ */ jsx10("h3", { style: titleStyle, children: "Email Preview" }),
2076
- /* @__PURE__ */ jsx10(
1906
+ return /* @__PURE__ */ jsxs8("div", { style: containerStyle, children: [
1907
+ /* @__PURE__ */ jsxs8("div", { style: headerStyle, children: [
1908
+ /* @__PURE__ */ jsx8("h3", { style: titleStyle, children: "Email Preview" }),
1909
+ /* @__PURE__ */ jsx8(
2077
1910
  "button",
2078
1911
  {
2079
1912
  onClick: () => showPreview ? setShowPreview(false) : updatePreview(),
@@ -2083,9 +1916,9 @@ var BroadcastInlinePreview = () => {
2083
1916
  }
2084
1917
  )
2085
1918
  ] }),
2086
- showPreview && /* @__PURE__ */ jsx10("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ jsxs10("div", { style: errorStyle, children: [
2087
- /* @__PURE__ */ jsx10("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
2088
- /* @__PURE__ */ jsx10(
1919
+ showPreview && /* @__PURE__ */ jsx8("div", { style: previewContainerStyle, children: error ? /* @__PURE__ */ jsxs8("div", { style: errorStyle, children: [
1920
+ /* @__PURE__ */ jsx8("p", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: error.message }),
1921
+ /* @__PURE__ */ jsx8(
2089
1922
  "button",
2090
1923
  {
2091
1924
  onClick: updatePreview,
@@ -2100,8 +1933,8 @@ var BroadcastInlinePreview = () => {
2100
1933
  children: "Retry"
2101
1934
  }
2102
1935
  )
2103
- ] }) : previewData ? /* @__PURE__ */ jsxs10(Fragment2, { children: [
2104
- /* @__PURE__ */ jsx10(
1936
+ ] }) : previewHtml ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
1937
+ /* @__PURE__ */ jsx8(
2105
1938
  PreviewControls,
2106
1939
  {
2107
1940
  onUpdate: updatePreview,
@@ -2110,12 +1943,42 @@ var BroadcastInlinePreview = () => {
2110
1943
  isLoading
2111
1944
  }
2112
1945
  ),
2113
- /* @__PURE__ */ jsx10(
2114
- EmailRenderer,
1946
+ /* @__PURE__ */ jsx8(
1947
+ "div",
2115
1948
  {
2116
- template: previewData.template,
2117
- data: previewData.data,
2118
- device
1949
+ style: {
1950
+ flex: 1,
1951
+ padding: device === "mobile" ? "1rem" : "2rem",
1952
+ display: "flex",
1953
+ justifyContent: "center",
1954
+ overflow: "auto"
1955
+ },
1956
+ children: /* @__PURE__ */ jsx8(
1957
+ "div",
1958
+ {
1959
+ style: {
1960
+ width: device === "mobile" ? "375px" : "600px",
1961
+ maxWidth: "100%",
1962
+ background: "white",
1963
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
1964
+ borderRadius: "8px",
1965
+ overflow: "hidden"
1966
+ },
1967
+ children: /* @__PURE__ */ jsx8(
1968
+ "iframe",
1969
+ {
1970
+ srcDoc: previewHtml,
1971
+ style: {
1972
+ width: "100%",
1973
+ height: "100%",
1974
+ minHeight: "600px",
1975
+ border: "none"
1976
+ },
1977
+ title: "Email Preview"
1978
+ }
1979
+ )
1980
+ }
1981
+ )
2119
1982
  }
2120
1983
  )
2121
1984
  ] }) : null })
@@ -2123,9 +1986,9 @@ var BroadcastInlinePreview = () => {
2123
1986
  };
2124
1987
 
2125
1988
  // src/components/Broadcasts/BroadcastPreviewField.tsx
2126
- import { jsx as jsx11 } from "react/jsx-runtime";
1989
+ import { jsx as jsx9 } from "react/jsx-runtime";
2127
1990
  var BroadcastPreviewField = () => {
2128
- return /* @__PURE__ */ jsx11("div", { style: {
1991
+ return /* @__PURE__ */ jsx9("div", { style: {
2129
1992
  padding: "1rem",
2130
1993
  background: "#f9fafb",
2131
1994
  borderRadius: "4px",
@@ -2134,8 +1997,102 @@ var BroadcastPreviewField = () => {
2134
1997
  }, children: "Email preview is available inline below the content editor." });
2135
1998
  };
2136
1999
 
2000
+ // src/components/Broadcasts/EmailRenderer.tsx
2001
+ import { useEffect as useEffect5, useState as useState9, useCallback as useCallback4, useRef as useRef2 } from "react";
2002
+ import { render } from "@react-email/render";
2003
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2004
+ var EmailRenderer = ({
2005
+ template,
2006
+ data,
2007
+ device = "desktop",
2008
+ onRender
2009
+ }) => {
2010
+ const [renderedHtml, setRenderedHtml] = useState9("");
2011
+ const [error, setError] = useState9(null);
2012
+ const iframeRef = useRef2(null);
2013
+ const renderEmail = useCallback4(async () => {
2014
+ try {
2015
+ const TemplateComponent = template;
2016
+ const element = /* @__PURE__ */ jsx10(TemplateComponent, { ...data });
2017
+ const html = await render(element, {
2018
+ pretty: true
2019
+ });
2020
+ setRenderedHtml(html);
2021
+ onRender?.(html);
2022
+ setError(null);
2023
+ } catch (err) {
2024
+ setError(err);
2025
+ console.error("Failed to render email template:", err);
2026
+ }
2027
+ }, [template, data, onRender]);
2028
+ useEffect5(() => {
2029
+ renderEmail();
2030
+ }, [renderEmail]);
2031
+ useEffect5(() => {
2032
+ if (iframeRef.current && renderedHtml) {
2033
+ const iframe = iframeRef.current;
2034
+ const doc = iframe.contentDocument || iframe.contentWindow?.document;
2035
+ if (doc) {
2036
+ doc.open();
2037
+ doc.write(renderedHtml);
2038
+ doc.close();
2039
+ }
2040
+ }
2041
+ }, [renderedHtml]);
2042
+ const containerStyle = {
2043
+ width: "100%",
2044
+ height: "100%",
2045
+ display: "flex",
2046
+ alignItems: "flex-start",
2047
+ justifyContent: "center",
2048
+ overflow: "auto",
2049
+ padding: "2rem",
2050
+ boxSizing: "border-box"
2051
+ };
2052
+ const iframeStyle = {
2053
+ width: device === "mobile" ? "375px" : "600px",
2054
+ height: "100%",
2055
+ minHeight: "600px",
2056
+ background: "white",
2057
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
2058
+ borderRadius: device === "mobile" ? "20px" : "8px",
2059
+ border: "none",
2060
+ display: "block"
2061
+ };
2062
+ const errorStyle = {
2063
+ background: "white",
2064
+ border: "1px solid #ef4444",
2065
+ borderRadius: "4px",
2066
+ padding: "2rem",
2067
+ maxWidth: "500px"
2068
+ };
2069
+ if (error) {
2070
+ return /* @__PURE__ */ jsxs9("div", { style: errorStyle, children: [
2071
+ /* @__PURE__ */ jsx10("h3", { style: { color: "#ef4444", margin: "0 0 1rem" }, children: "Template Render Error" }),
2072
+ /* @__PURE__ */ jsx10("pre", { style: {
2073
+ background: "#f9fafb",
2074
+ padding: "1rem",
2075
+ borderRadius: "4px",
2076
+ overflowX: "auto",
2077
+ fontSize: "12px",
2078
+ color: "#374151",
2079
+ margin: 0
2080
+ }, children: error.message })
2081
+ ] });
2082
+ }
2083
+ return /* @__PURE__ */ jsx10("div", { style: containerStyle, children: /* @__PURE__ */ jsx10(
2084
+ "iframe",
2085
+ {
2086
+ ref: iframeRef,
2087
+ style: iframeStyle,
2088
+ sandbox: "allow-same-origin",
2089
+ title: "Email Preview"
2090
+ }
2091
+ ) });
2092
+ };
2093
+
2137
2094
  // src/components/Broadcasts/StatusBadge.tsx
2138
- import { jsx as jsx12 } from "react/jsx-runtime";
2095
+ import { jsx as jsx11 } from "react/jsx-runtime";
2139
2096
  var statusConfig = {
2140
2097
  ["draft" /* DRAFT */]: {
2141
2098
  label: "Draft",
@@ -2183,7 +2140,7 @@ var statusConfig = {
2183
2140
  var StatusBadge = ({ cellData }) => {
2184
2141
  const status = cellData;
2185
2142
  const config = statusConfig[status] || statusConfig["draft" /* DRAFT */];
2186
- return /* @__PURE__ */ jsx12(
2143
+ return /* @__PURE__ */ jsx11(
2187
2144
  "span",
2188
2145
  {
2189
2146
  style: {
@@ -2205,6 +2162,69 @@ var StatusBadge = ({ cellData }) => {
2205
2162
  var EmptyField = () => {
2206
2163
  return null;
2207
2164
  };
2165
+
2166
+ // src/email-templates/DefaultBroadcastTemplate.tsx
2167
+ import {
2168
+ Body,
2169
+ Container,
2170
+ Head,
2171
+ Hr,
2172
+ Html,
2173
+ Link,
2174
+ Preview,
2175
+ Section,
2176
+ Text
2177
+ } from "@react-email/components";
2178
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2179
+ var DefaultBroadcastTemplate = ({
2180
+ subject,
2181
+ preheader,
2182
+ content
2183
+ }) => {
2184
+ return /* @__PURE__ */ jsxs10(Html, { children: [
2185
+ /* @__PURE__ */ jsx12(Head, {}),
2186
+ /* @__PURE__ */ jsx12(Preview, { children: preheader || subject }),
2187
+ /* @__PURE__ */ jsx12(Body, { style: main, children: /* @__PURE__ */ jsxs10(Container, { style: container, children: [
2188
+ /* @__PURE__ */ jsx12(Section, { style: contentSection, children: /* @__PURE__ */ jsx12("div", { dangerouslySetInnerHTML: { __html: content } }) }),
2189
+ /* @__PURE__ */ jsx12(Hr, { style: divider }),
2190
+ /* @__PURE__ */ jsxs10(Section, { style: footer, children: [
2191
+ /* @__PURE__ */ jsx12(Text, { style: footerText, children: "You're receiving this email because you subscribed to our newsletter." }),
2192
+ /* @__PURE__ */ jsx12(Text, { style: footerText, children: /* @__PURE__ */ jsx12(Link, { href: "{{unsubscribe_url}}", style: footerLink, children: "Unsubscribe" }) })
2193
+ ] })
2194
+ ] }) })
2195
+ ] });
2196
+ };
2197
+ var main = {
2198
+ backgroundColor: "#ffffff",
2199
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
2200
+ };
2201
+ var container = {
2202
+ margin: "0 auto",
2203
+ padding: "40px 20px",
2204
+ maxWidth: "600px"
2205
+ };
2206
+ var contentSection = {
2207
+ fontSize: "16px",
2208
+ lineHeight: "1.6",
2209
+ color: "#374151"
2210
+ };
2211
+ var divider = {
2212
+ borderColor: "#e5e7eb",
2213
+ margin: "40px 0 20px"
2214
+ };
2215
+ var footer = {
2216
+ textAlign: "center"
2217
+ };
2218
+ var footerText = {
2219
+ fontSize: "14px",
2220
+ lineHeight: "1.5",
2221
+ color: "#6b7280",
2222
+ margin: "0 0 10px"
2223
+ };
2224
+ var footerLink = {
2225
+ color: "#6b7280",
2226
+ textDecoration: "underline"
2227
+ };
2208
2228
  export {
2209
2229
  BroadcastEditor,
2210
2230
  BroadcastInlinePreview,