UNPKG

payload-plugin-newsletter

Version:

Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration

1,668 lines 60.4 kB
"use strict";
"use client";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/exports/components.ts
var components_exports = {};
__export(components_exports, {
  BroadcastEditor: () => BroadcastEditor,
  EmailPreview: () => EmailPreview,
  EmailPreviewField: () => EmailPreviewField,
  MagicLinkVerify: () => MagicLinkVerify,
  NewsletterForm: () => NewsletterForm,
  PreferencesForm: () => PreferencesForm,
  createMagicLinkVerify: () => createMagicLinkVerify,
  createNewsletterForm: () => createNewsletterForm,
  createPreferencesForm: () => createPreferencesForm,
  useNewsletterAuth: () => useNewsletterAuth
});
module.exports = __toCommonJS(components_exports);

// src/components/NewsletterForm.tsx
var import_react = require("react");
var import_jsx_runtime = require("react/jsx-runtime");
var defaultStyles = {
  form: {
    display: "flex",
    flexDirection: "column",
    gap: "1rem",
    maxWidth: "400px",
    margin: "0 auto"
  },
  inputGroup: {
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem"
  },
  label: {
    fontSize: "0.875rem",
    fontWeight: "500",
    color: "#374151"
  },
  input: {
    padding: "0.5rem 0.75rem",
    fontSize: "1rem",
    border: "1px solid #e5e7eb",
    borderRadius: "0.375rem",
    outline: "none",
    transition: "border-color 0.2s"
  },
  button: {
    padding: "0.75rem 1.5rem",
    fontSize: "1rem",
    fontWeight: "500",
    color: "#ffffff",
    backgroundColor: "#3b82f6",
    border: "none",
    borderRadius: "0.375rem",
    cursor: "pointer",
    transition: "background-color 0.2s"
  },
  buttonDisabled: {
    opacity: 0.5,
    cursor: "not-allowed"
  },
  error: {
    fontSize: "0.875rem",
    color: "#ef4444",
    marginTop: "0.25rem"
  },
  success: {
    fontSize: "0.875rem",
    color: "#10b981",
    marginTop: "0.25rem"
  },
  checkbox: {
    display: "flex",
    alignItems: "center",
    gap: "0.5rem"
  },
  checkboxInput: {
    width: "1rem",
    height: "1rem"
  },
  checkboxLabel: {
    fontSize: "0.875rem",
    color: "#374151"
  }
};
var NewsletterForm = ({
  onSuccess,
  onError,
  showName = false,
  showPreferences = false,
  leadMagnet,
  className,
  styles: customStyles = {},
  apiEndpoint = "/api/newsletter/subscribe",
  buttonText = "Subscribe",
  loadingText = "Subscribing...",
  successMessage = "Successfully subscribed!",
  placeholders = {
    email: "Enter your email",
    name: "Enter your name"
  },
  labels = {
    email: "Email",
    name: "Name",
    newsletter: "Newsletter updates",
    announcements: "Product announcements"
  }
}) => {
  const [email, setEmail] = (0, import_react.useState)("");
  const [name, setName] = (0, import_react.useState)("");
  const [preferences, setPreferences] = (0, import_react.useState)({
    newsletter: true,
    announcements: true
  });
  const [loading, setLoading] = (0, import_react.useState)(false);
  const [error, setError] = (0, import_react.useState)(null);
  const [success, setSuccess] = (0, import_react.useState)(false);
  const styles = {
    form: { ...defaultStyles.form, ...customStyles.form },
    inputGroup: { ...defaultStyles.inputGroup, ...customStyles.inputGroup },
    label: { ...defaultStyles.label, ...customStyles.label },
    input: { ...defaultStyles.input, ...customStyles.input },
    button: { ...defaultStyles.button, ...customStyles.button },
    buttonDisabled: { ...defaultStyles.buttonDisabled, ...customStyles.buttonDisabled },
    error: { ...defaultStyles.error, ...customStyles.error },
    success: { ...defaultStyles.success, ...customStyles.success },
    checkbox: { ...defaultStyles.checkbox, ...customStyles.checkbox },
    checkboxInput: { ...defaultStyles.checkboxInput, ...customStyles.checkboxInput },
    checkboxLabel: { ...defaultStyles.checkboxLabel, ...customStyles.checkboxLabel }
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    setError(null);
    setLoading(true);
    try {
      const payload = {
        email,
        ...showName && name && { name },
        ...showPreferences && { preferences },
        ...leadMagnet && { leadMagnet: leadMagnet.id },
        metadata: {
          signupPage: window.location.href,
          ...typeof window !== "undefined" && window.location.search && {
            utmParams: Object.fromEntries(new URLSearchParams(window.location.search))
          }
        }
      };
      const response = await fetch(apiEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify(payload)
      });
      const data = await response.json();
      if (!response.ok) {
        throw new Error(data.error || data.errors?.join(", ") || "Subscription failed");
      }
      setSuccess(true);
      setEmail("");
      setName("");
      if (onSuccess) {
        onSuccess(data.subscriber);
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : "An error occurred";
      setError(errorMessage);
      if (onError) {
        onError(new Error(errorMessage));
      }
    } finally {
      setLoading(false);
    }
  };
  if (success && !showPreferences) {
    return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, style: styles.form, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.success, children: successMessage }) });
  }
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className, style: styles.form, children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "email", style: styles.label, children: labels.email }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
        "input",
        {
          id: "email",
          type: "email",
          value: email,
          onChange: (e) => setEmail(e.target.value),
          placeholder: placeholders.email,
          required: true,
          disabled: loading,
          style: {
            ...styles.input,
            ...loading && { opacity: 0.5 }
          }
        }
      )
    ] }),
    showName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "name", style: styles.label, children: labels.name }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
        "input",
        {
          id: "name",
          type: "text",
          value: name,
          onChange: (e) => setName(e.target.value),
          placeholder: placeholders.name,
          disabled: loading,
          style: {
            ...styles.input,
            ...loading && { opacity: 0.5 }
          }
        }
      )
    ] }),
    showPreferences && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputGroup, children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { style: styles.label, children: "Email Preferences" }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.checkbox, children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
          "input",
          {
            id: "newsletter",
            type: "checkbox",
            checked: preferences.newsletter,
            onChange: (e) => setPreferences({ ...preferences, newsletter: e.target.checked }),
            disabled: loading,
            style: styles.checkboxInput
          }
        ),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "newsletter", style: styles.checkboxLabel, children: labels.newsletter })
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.checkbox, children: [
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
          "input",
          {
            id: "announcements",
            type: "checkbox",
            checked: preferences.announcements,
            onChange: (e) => setPreferences({ ...preferences, announcements: e.target.checked }),
            disabled: loading,
            style: styles.checkboxInput
          }
        ),
        /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "announcements", style: styles.checkboxLabel, children: labels.announcements })
      ] })
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
      "button",
      {
        type: "submit",
        disabled: loading,
        style: {
          ...styles.button,
          ...loading && styles.buttonDisabled
        },
        children: loading ? loadingText : buttonText
      }
    ),
    error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, children: error }),
    success && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.success, children: successMessage })
  ] });
};
function createNewsletterForm(defaultProps) {
  return (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NewsletterForm, { ...defaultProps, ...props });
}

// src/components/PreferencesForm.tsx
var import_react2 = require("react");
var import_jsx_runtime2 = require("react/jsx-runtime");
var defaultStyles2 = {
  container: {
    maxWidth: "600px",
    margin: "0 auto",
    padding: "2rem"
  },
  heading: {
    fontSize: "1.5rem",
    fontWeight: "600",
    marginBottom: "1.5rem",
    color: "#111827"
  },
  form: {
    display: "flex",
    flexDirection: "column",
    gap: "1.5rem"
  },
  section: {
    padding: "1.5rem",
    backgroundColor: "#f9fafb",
    borderRadius: "0.5rem",
    border: "1px solid #e5e7eb"
  },
  sectionTitle: {
    fontSize: "1.125rem",
    fontWeight: "500",
    marginBottom: "1rem",
    color: "#111827"
  },
  inputGroup: {
    display: "flex",
    flexDirection: "column",
    gap: "0.5rem"
  },
  label: {
    fontSize: "0.875rem",
    fontWeight: "500",
    color: "#374151"
  },
  input: {
    padding: "0.5rem 0.75rem",
    fontSize: "1rem",
    border: "1px solid #e5e7eb",
    borderRadius: "0.375rem",
    outline: "none",
    transition: "border-color 0.2s"
  },
  select: {
    padding: "0.5rem 0.75rem",
    fontSize: "1rem",
    border: "1px solid #e5e7eb",
    borderRadius: "0.375rem",
    outline: "none",
    backgroundColor: "#ffffff"
  },
  checkbox: {
    display: "flex",
    alignItems: "center",
    gap: "0.5rem",
    marginBottom: "0.5rem"
  },
  checkboxInput: {
    width: "1rem",
    height: "1rem"
  },
  checkboxLabel: {
    fontSize: "0.875rem",
    color: "#374151"
  },
  buttonGroup: {
    display: "flex",
    gap: "1rem",
    marginTop: "1rem"
  },
  button: {
    padding: "0.75rem 1.5rem",
    fontSize: "1rem",
    fontWeight: "500",
    borderRadius: "0.375rem",
    cursor: "pointer",
    transition: "all 0.2s",
    border: "none"
  },
  primaryButton: {
    color: "#ffffff",
    backgroundColor: "#3b82f6"
  },
  secondaryButton: {
    color: "#374151",
    backgroundColor: "#ffffff",
    border: "1px solid #e5e7eb"
  },
  dangerButton: {
    color: "#ffffff",
    backgroundColor: "#ef4444"
  },
  error: {
    fontSize: "0.875rem",
    color: "#ef4444",
    marginTop: "0.5rem"
  },
  success: {
    fontSize: "0.875rem",
    color: "#10b981",
    marginTop: "0.5rem"
  },
  info: {
    fontSize: "0.875rem",
    color: "#6b7280",
    marginTop: "0.5rem"
  }
};
var PreferencesForm = ({
  subscriber: initialSubscriber,
  onSuccess,
  onError,
  className,
  styles: customStyles = {},
  sessionToken,
  apiEndpoint = "/api/newsletter/preferences",
  showUnsubscribe = true,
  locales = ["en"],
  labels = {
    title: "Newsletter Preferences",
    personalInfo: "Personal Information",
    emailPreferences: "Email Preferences",
    name: "Name",
    language: "Preferred Language",
    newsletter: "Newsletter updates",
    announcements: "Product announcements",
    saveButton: "Save Preferences",
    unsubscribeButton: "Unsubscribe",
    saving: "Saving...",
    saved: "Preferences saved successfully!",
    unsubscribeConfirm: "Are you sure you want to unsubscribe? This cannot be undone."
  }
}) => {
  const [subscriber, setSubscriber] = (0, import_react2.useState)(initialSubscriber || {});
  const [loading, setLoading] = (0, import_react2.useState)(false);
  const [loadingData, setLoadingData] = (0, import_react2.useState)(!initialSubscriber);
  const [error, setError] = (0, import_react2.useState)(null);
  const [success, setSuccess] = (0, import_react2.useState)(false);
  const styles = {
    container: { ...defaultStyles2.container, ...customStyles.container },
    heading: { ...defaultStyles2.heading, ...customStyles.heading },
    form: { ...defaultStyles2.form, ...customStyles.form },
    section: { ...defaultStyles2.section, ...customStyles.section },
    sectionTitle: { ...defaultStyles2.sectionTitle, ...customStyles.sectionTitle },
    inputGroup: { ...defaultStyles2.inputGroup, ...customStyles.inputGroup },
    label: { ...defaultStyles2.label, ...customStyles.label },
    input: { ...defaultStyles2.input, ...customStyles.input },
    select: { ...defaultStyles2.select, ...customStyles.select },
    checkbox: { ...defaultStyles2.checkbox, ...customStyles.checkbox },
    checkboxInput: { ...defaultStyles2.checkboxInput, ...customStyles.checkboxInput },
    checkboxLabel: { ...defaultStyles2.checkboxLabel, ...customStyles.checkboxLabel },
    buttonGroup: { ...defaultStyles2.buttonGroup, ...customStyles.buttonGroup },
    button: { ...defaultStyles2.button, ...customStyles.button },
    primaryButton: { ...defaultStyles2.primaryButton, ...customStyles.primaryButton },
    secondaryButton: { ...defaultStyles2.secondaryButton, ...customStyles.secondaryButton },
    dangerButton: { ...defaultStyles2.dangerButton, ...customStyles.dangerButton },
    error: { ...defaultStyles2.error, ...customStyles.error },
    success: { ...defaultStyles2.success, ...customStyles.success },
    info: { ...defaultStyles2.info, ...customStyles.info }
  };
  (0, import_react2.useEffect)(() => {
    if (!initialSubscriber && sessionToken) {
      fetchPreferences();
    }
  }, []);
  const fetchPreferences = async () => {
    try {
      const response = await fetch(apiEndpoint, {
        headers: {
          "Authorization": `Bearer ${sessionToken}`
        }
      });
      if (!response.ok) {
        throw new Error("Failed to load preferences");
      }
      const data = await response.json();
      setSubscriber(data.subscriber);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to load preferences");
      if (onError) {
        onError(err instanceof Error ? err : new Error("Failed to load preferences"));
      }
    } finally {
      setLoadingData(false);
    }
  };
  const handleSave = async (e) => {
    e.preventDefault();
    setError(null);
    setSuccess(false);
    setLoading(true);
    try {
      const response = await fetch(apiEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${sessionToken}`
        },
        body: JSON.stringify({
          name: subscriber.name,
          locale: subscriber.locale,
          emailPreferences: subscriber.emailPreferences
        })
      });
      const data = await response.json();
      if (!response.ok) {
        throw new Error(data.error || "Failed to save preferences");
      }
      setSubscriber(data.subscriber);
      setSuccess(true);
      if (onSuccess) {
        onSuccess(data.subscriber);
      }
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : "An error occurred";
      setError(errorMessage);
      if (onError) {
        onError(new Error(errorMessage));
      }
    } finally {
      setLoading(false);
    }
  };
  const handleUnsubscribe = async () => {
    if (!window.confirm(labels.unsubscribeConfirm)) {
      return;
    }
    setLoading(true);
    setError(null);
    try {
      const response = await fetch("/api/newsletter/unsubscribe", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "Authorization": `Bearer ${sessionToken}`
        },
        body: JSON.stringify({
          email: subscriber.email
        })
      });
      if (!response.ok) {
        throw new Error("Failed to unsubscribe");
      }
      setSubscriber({ ...subscriber, subscriptionStatus: "unsubscribed" });
      if (onSuccess) {
        onSuccess({ ...subscriber, subscriptionStatus: "unsubscribed" });
      }
    } catch (err) {
      setError("Failed to unsubscribe. Please try again.");
      if (onError) {
        onError(err instanceof Error ? err : new Error("Failed to unsubscribe"));
      }
    } finally {
      setLoading(false);
    }
  };
  if (loadingData) {
    return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.info, children: "Loading preferences..." }) });
  }
  if (subscriber.subscriptionStatus === "unsubscribed") {
    return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: styles.container, children: [
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: styles.heading, children: "Unsubscribed" }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.info, children: "You have been unsubscribed from all emails. To resubscribe, please sign up again." })
    ] });
  }
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: styles.container, children: [
    /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: styles.heading, children: labels.title }),
    /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSave, style: styles.form, children: [
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.section, children: [
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: styles.sectionTitle, children: labels.personalInfo }),
        /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.inputGroup, children: [
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "name", style: styles.label, children: labels.name }),
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
            "input",
            {
              id: "name",
              type: "text",
              value: subscriber.name || "",
              onChange: (e) => setSubscriber({ ...subscriber, name: e.target.value }),
              disabled: loading,
              style: styles.input
            }
          )
        ] }),
        locales.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.inputGroup, children: [
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "locale", style: styles.label, children: labels.language }),
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
            "select",
            {
              id: "locale",
              value: subscriber.locale || locales[0],
              onChange: (e) => setSubscriber({ ...subscriber, locale: e.target.value }),
              disabled: loading,
              style: styles.select,
              children: locales.map((locale) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: locale, children: locale.toUpperCase() }, locale))
            }
          )
        ] })
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.section, children: [
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: styles.sectionTitle, children: labels.emailPreferences }),
        /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.checkbox, children: [
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
            "input",
            {
              id: "pref-newsletter",
              type: "checkbox",
              checked: subscriber.emailPreferences?.newsletter ?? true,
              onChange: (e) => setSubscriber({
                ...subscriber,
                emailPreferences: {
                  ...subscriber.emailPreferences,
                  newsletter: e.target.checked
                }
              }),
              disabled: loading,
              style: styles.checkboxInput
            }
          ),
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "pref-newsletter", style: styles.checkboxLabel, children: labels.newsletter })
        ] }),
        /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.checkbox, children: [
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
            "input",
            {
              id: "pref-announcements",
              type: "checkbox",
              checked: subscriber.emailPreferences?.announcements ?? true,
              onChange: (e) => setSubscriber({
                ...subscriber,
                emailPreferences: {
                  ...subscriber.emailPreferences,
                  announcements: e.target.checked
                }
              }),
              disabled: loading,
              style: styles.checkboxInput
            }
          ),
          /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "pref-announcements", style: styles.checkboxLabel, children: labels.announcements })
        ] })
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.buttonGroup, children: [
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
          "button",
          {
            type: "submit",
            disabled: loading,
            style: {
              ...styles.button,
              ...styles.primaryButton,
              ...loading && { opacity: 0.5, cursor: "not-allowed" }
            },
            children: loading ? labels.saving : labels.saveButton
          }
        ),
        showUnsubscribe && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
          "button",
          {
            type: "button",
            onClick: handleUnsubscribe,
            disabled: loading,
            style: {
              ...styles.button,
              ...styles.dangerButton,
              ...loading && { opacity: 0.5, cursor: "not-allowed" }
            },
            children: labels.unsubscribeButton
          }
        )
      ] }),
      error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.error, children: error }),
      success && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.success, children: labels.saved })
    ] })
  ] });
};
function createPreferencesForm(defaultProps) {
  return (props) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PreferencesForm, { ...defaultProps, ...props });
}

// src/components/MagicLinkVerify.tsx
var import_react3 = require("react");
var import_jsx_runtime3 = require("react/jsx-runtime");
var defaultStyles3 = {
  container: {
    maxWidth: "400px",
    margin: "4rem auto",
    padding: "2rem",
    textAlign: "center"
  },
  heading: {
    fontSize: "1.5rem",
    fontWeight: "600",
    marginBottom: "1rem",
    color: "#111827"
  },
  message: {
    fontSize: "1rem",
    color: "#6b7280",
    marginBottom: "1.5rem"
  },
  error: {
    fontSize: "1rem",
    color: "#ef4444",
    marginBottom: "1.5rem"
  },
  button: {
    padding: "0.75rem 1.5rem",
    fontSize: "1rem",
    fontWeight: "500",
    color: "#ffffff",
    backgroundColor: "#3b82f6",
    border: "none",
    borderRadius: "0.375rem",
    cursor: "pointer",
    transition: "background-color 0.2s"
  }
};
var MagicLinkVerify = ({
  token: propToken,
  onSuccess,
  onError,
  apiEndpoint = "/api/newsletter/verify-magic-link",
  className,
  styles: customStyles = {},
  labels = {
    verifying: "Verifying your magic link...",
    success: "Successfully verified! Redirecting...",
    error: "Failed to verify magic link",
    expired: "This magic link has expired. Please request a new one.",
    invalid: "This magic link is invalid. Please request a new one.",
    redirecting: "Redirecting to your preferences...",
    tryAgain: "Try Again"
  }
}) => {
  const [status, setStatus] = (0, import_react3.useState)("verifying");
  const [error, setError] = (0, import_react3.useState)(null);
  const [_sessionToken, setSessionToken] = (0, import_react3.useState)(null);
  const styles = {
    container: { ...defaultStyles3.container, ...customStyles.container },
    heading: { ...defaultStyles3.heading, ...customStyles.heading },
    message: { ...defaultStyles3.message, ...customStyles.message },
    error: { ...defaultStyles3.error, ...customStyles.error },
    button: { ...defaultStyles3.button, ...customStyles.button }
  };
  (0, import_react3.useEffect)(() => {
    const token = propToken || new URLSearchParams(window.location.search).get("token");
    if (token) {
      verifyToken(token);
    } else {
      setStatus("error");
      setError(labels.invalid || "Invalid magic link");
    }
  }, [propToken]);
  const verifyToken = async (token) => {
    try {
      const response = await fetch(apiEndpoint, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ token })
      });
      const data = await response.json();
      if (!response.ok) {
        if (data.error?.includes("expired")) {
          throw new Error(labels.expired);
        }
        throw new Error(data.error || labels.error);
      }
      setStatus("success");
      setSessionToken(data.sessionToken);
      if (typeof window !== "undefined" && data.sessionToken) {
        localStorage.setItem("newsletter_session", data.sessionToken);
      }
      if (onSuccess) {
        onSuccess(data.sessionToken, data.subscriber);
      }
    } catch (err) {
      setStatus("error");
      const errorMessage = err instanceof Error ? err.message : labels.error || "Verification failed";
      setError(errorMessage);
      if (onError) {
        onError(err instanceof Error ? err : new Error(errorMessage));
      }
    }
  };
  const handleTryAgain = () => {
    window.location.href = "/";
  };
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: styles.container, children: [
    status === "verifying" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Verifying" }),
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.message, children: labels.verifying })
    ] }),
    status === "success" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Success!" }),
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.message, children: labels.success })
    ] }),
    status === "error" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.heading, children: "Verification Failed" }),
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.error, children: error }),
      /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleTryAgain, style: styles.button, children: labels.tryAgain })
    ] })
  ] });
};
function createMagicLinkVerify(defaultProps) {
  return (props) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicLinkVerify, { ...defaultProps, ...props });
}

// src/hooks/useNewsletterAuth.ts
var import_react4 = require("react");
function useNewsletterAuth(_options = {}) {
  const [subscriber, setSubscriber] = (0, import_react4.useState)(null);
  const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
  const [error, setError] = (0, import_react4.useState)(null);
  const checkAuth = (0, import_react4.useCallback)(async () => {
    try {
      const response = await fetch("/api/newsletter/me", {
        method: "GET",
        credentials: "include",
        headers: {
          "Content-Type": "application/json"
        }
      });
      if (response.ok) {
        const data = await response.json();
        setSubscriber(data.subscriber);
        setError(null);
      } else {
        setSubscriber(null);
        if (response.status !== 401) {
          setError(new Error("Failed to check authentication"));
        }
      }
    } catch (err) {
      console.error("Auth check failed:", err);
      setError(err instanceof Error ? err : new Error("An error occurred"));
      setSubscriber(null);
    } finally {
      setIsLoading(false);
    }
  }, []);
  (0, import_react4.useEffect)(() => {
    checkAuth();
  }, [checkAuth]);
  const signOut = (0, import_react4.useCallback)(async () => {
    try {
      const response = await fetch("/api/newsletter/signout", {
        method: "POST",
        credentials: "include",
        headers: {
          "Content-Type": "application/json"
        }
      });
      if (response.ok) {
        setSubscriber(null);
        setError(null);
      } else {
        throw new Error("Failed to sign out");
      }
    } catch (err) {
      console.error("Sign out error:", err);
      setError(err instanceof Error ? err : new Error("Sign out failed"));
      throw err;
    }
  }, []);
  const refreshAuth = (0, import_react4.useCallback)(async () => {
    setIsLoading(true);
    await checkAuth();
  }, [checkAuth]);
  const login = (0, import_react4.useCallback)(async (_token) => {
    await refreshAuth();
  }, [refreshAuth]);
  return {
    subscriber,
    isAuthenticated: !!subscriber,
    isLoading,
    loading: isLoading,
    // Alias for backward compatibility
    error,
    signOut,
    logout: signOut,
    // Alias for backward compatibility
    refreshAuth,
    refreshSubscriber: refreshAuth,
    // Alias for backward compatibility
    login
    // For backward compatibility
  };
}

// src/components/Broadcasts/EmailPreview.tsx
var import_react5 = require("react");

// src/utils/emailSafeHtml.ts
var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
var EMAIL_SAFE_CONFIG = {
  ALLOWED_TAGS: [
    "p",
    "br",
    "strong",
    "b",
    "em",
    "i",
    "u",
    "strike",
    "s",
    "span",
    "a",
    "h1",
    "h2",
    "h3",
    "ul",
    "ol",
    "li",
    "blockquote",
    "hr"
  ],
  ALLOWED_ATTR: ["href", "style", "target", "rel", "align"],
  ALLOWED_STYLES: {
    "*": [
      "color",
      "background-color",
      "font-size",
      "font-weight",
      "font-style",
      "text-decoration",
      "text-align",
      "margin",
      "margin-top",
      "margin-right",
      "margin-bottom",
      "margin-left",
      "padding",
      "padding-top",
      "padding-right",
      "padding-bottom",
      "padding-left",
      "line-height",
      "border-left",
      "border-left-width",
      "border-left-style",
      "border-left-color"
    ]
  },
  FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
  FORBID_ATTR: ["class", "id", "onclick", "onload", "onerror"]
};
async function convertToEmailSafeHtml(editorState, options) {
  const rawHtml = await lexicalToEmailHtml(editorState);
  const sanitizedHtml = import_isomorphic_dompurify.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
  if (options?.wrapInTemplate) {
    return wrapInEmailTemplate(sanitizedHtml, options.preheader);
  }
  return sanitizedHtml;
}
async function lexicalToEmailHtml(editorState) {
  const { root } = editorState;
  if (!root || !root.children) {
    return "";
  }
  const html = root.children.map((node) => convertNode(node)).join("");
  return html;
}
function convertNode(node) {
  switch (node.type) {
    case "paragraph":
      return convertParagraph(node);
    case "heading":
      return convertHeading(node);
    case "list":
      return convertList(node);
    case "listitem":
      return convertListItem(node);
    case "blockquote":
      return convertBlockquote(node);
    case "text":
      return convertText(node);
    case "link":
      return convertLink(node);
    case "linebreak":
      return "<br>";
    default:
      if (node.children) {
        return node.children.map(convertNode).join("");
      }
      return "";
  }
}
function convertParagraph(node) {
  const align = getAlignment(node.format);
  const children = node.children?.map(convertNode).join("") || "";
  if (!children.trim()) {
    return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
  }
  return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
}
function convertHeading(node) {
  const tag = node.tag || "h1";
  const align = getAlignment(node.format);
  const children = node.children?.map(convertNode).join("") || "";
  const styles = {
    h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
    h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
    h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
  };
  const style = `${styles[tag] || styles.h3} text-align: ${align};`;
  return `<${tag} style="${style}">${children}</${tag}>`;
}
function convertList(node) {
  const tag = node.listType === "number" ? "ol" : "ul";
  const children = node.children?.map(convertNode).join("") || "";
  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;";
  return `<${tag} style="${style}">${children}</${tag}>`;
}
function convertListItem(node) {
  const children = node.children?.map(convertNode).join("") || "";
  return `<li style="margin: 0 0 8px 0;">${children}</li>`;
}
function convertBlockquote(node) {
  const children = node.children?.map(convertNode).join("") || "";
  const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
  return `<blockquote style="${style}">${children}</blockquote>`;
}
function convertText(node) {
  let text = escapeHtml(node.text || "");
  if (node.format & 1) {
    text = `<strong>${text}</strong>`;
  }
  if (node.format & 2) {
    text = `<em>${text}</em>`;
  }
  if (node.format & 8) {
    text = `<u>${text}</u>`;
  }
  if (node.format & 4) {
    text = `<strike>${text}</strike>`;
  }
  return text;
}
function convertLink(node) {
  const children = node.children?.map(convertNode).join("") || "";
  const url = node.fields?.url || "#";
  return `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${children}</a>`;
}
function getAlignment(format) {
  if (!format) return "left";
  if (format & 2) return "center";
  if (format & 3) return "right";
  if (format & 4) return "justify";
  return "left";
}
function escapeHtml(text) {
  const map = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#039;"
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}
function wrapInEmailTemplate(content, preheader) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Email</title>
  <!--[if mso]>
  <noscript>
    <xml>
      <o:OfficeDocumentSettings>
        <o:PixelsPerInch>96</o:PixelsPerInch>
      </o:OfficeDocumentSettings>
    </xml>
  </noscript>
  <![endif]-->
</head>
<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;">
  ${preheader ? `<div style="display: none; max-height: 0; overflow: hidden;">${escapeHtml(preheader)}</div>` : ""}
  <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0;">
    <tr>
      <td align="center" style="padding: 20px 0;">
        <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
          <tr>
            <td style="padding: 40px 30px;">
              ${content}
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`;
}
function replacePersonalizationTags(html, sampleData) {
  return html.replace(/\{\{([^}]+)\}\}/g, (match, tag) => {
    const trimmedTag = tag.trim();
    return sampleData[trimmedTag] || match;
  });
}

// src/utils/validateEmailHtml.ts
function validateEmailHtml(html) {
  const warnings = [];
  const errors = [];
  const sizeInBytes = new Blob([html]).size;
  if (sizeInBytes > 102400) {
    warnings.push(`Email size (${Math.round(sizeInBytes / 1024)}KB) exceeds Gmail's 102KB limit - email may be clipped`);
  }
  if (html.includes("position:") && (html.includes("position: absolute") || html.includes("position: fixed"))) {
    errors.push("Absolute/fixed positioning is not supported in most email clients");
  }
  if (html.includes("display: flex") || html.includes("display: grid")) {
    errors.push("Flexbox and Grid layouts are not supported in many email clients");
  }
  if (html.includes("@media")) {
    warnings.push("Media queries may not work in all email clients");
  }
  const hasJavaScript = html.includes("<script") || html.includes("onclick") || html.includes("onload") || html.includes("javascript:");
  if (hasJavaScript) {
    errors.push("JavaScript is not supported in email and will be stripped by email clients");
  }
  const hasExternalStyles = html.includes("<link") && html.includes("stylesheet");
  if (hasExternalStyles) {
    errors.push("External stylesheets are not supported - use inline styles only");
  }
  if (html.includes("<form") || html.includes("<input") || html.includes("<button")) {
    errors.push("Forms and form elements are not reliably supported in email");
  }
  const unsupportedTags = [
    "video",
    "audio",
    "iframe",
    "embed",
    "object",
    "canvas",
    "svg"
  ];
  for (const tag of unsupportedTags) {
    if (html.includes(`<${tag}`)) {
      errors.push(`<${tag}> tags are not supported in email`);
    }
  }
  const imageCount = (html.match(/<img/g) || []).length;
  const linkCount = (html.match(/<a/g) || []).length;
  if (imageCount > 20) {
    warnings.push(`High number of images (${imageCount}) may affect email performance`);
  }
  const imagesWithoutAlt = (html.match(/<img(?![^>]*\balt\s*=)[^>]*>/g) || []).length;
  if (imagesWithoutAlt > 0) {
    warnings.push(`${imagesWithoutAlt} image(s) missing alt text - important for accessibility`);
  }
  const linksWithoutTarget = (html.match(/<a(?![^>]*\btarget\s*=)[^>]*>/g) || []).length;
  if (linksWithoutTarget > 0) {
    warnings.push(`${linksWithoutTarget} link(s) missing target="_blank" attribute`);
  }
  if (html.includes("margin: auto") || html.includes("margin:auto")) {
    warnings.push('margin: auto is not supported in Outlook - use align="center" or tables for centering');
  }
  if (html.includes("background-image")) {
    warnings.push("Background images are not reliably supported - consider using <img> tags instead");
  }
  if (html.match(/\d+\s*(rem|em)/)) {
    warnings.push("rem/em units may render inconsistently - use px for reliable sizing");
  }
  if (html.match(/margin[^:]*:\s*-\d+/)) {
    errors.push("Negative margins are not supported in many email clients");
  }
  const personalizationTags = html.match(/\{\{([^}]+)\}\}/g) || [];
  const validTags = ["subscriber.name", "subscriber.email", "subscriber.firstName", "subscriber.lastName"];
  for (const tag of personalizationTags) {
    const tagContent = tag.replace(/[{}]/g, "").trim();
    if (!validTags.includes(tagContent)) {
      warnings.push(`Unknown personalization tag: ${tag}`);
    }
  }
  return {
    valid: errors.length === 0,
    warnings,
    errors,
    stats: {
      sizeInBytes,
      imageCount,
      linkCount,
      hasExternalStyles,
      hasJavaScript
    }
  };
}

// src/components/Broadcasts/EmailPreview.tsx
var import_jsx_runtime4 = require("react/jsx-runtime");
var SAMPLE_DATA = {
  "subscriber.name": "John Doe",
  "subscriber.firstName": "John",
  "subscriber.lastName": "Doe",
  "subscriber.email": "john.doe@example.com"
};
var VIEWPORT_SIZES = {
  desktop: { width: 600, scale: 1 },
  mobile: { width: 320, scale: 0.8 }
};
var EmailPreview = ({
  content,
  subject,
  preheader,
  channel,
  mode = "desktop",
  onValidation
}) => {
  const [html, setHtml] = (0, import_react5.useState)("");
  const [loading, setLoading] = (0, import_react5.useState)(false);
  const [validationResult, setValidationResult] = (0, import_react5.useState)(null);
  const iframeRef = (0, import_react5.useRef)(null);
  (0, import_react5.useEffect)(() => {
    const convertContent = async () => {
      if (!content) {
        setHtml("");
        return;
      }
      setLoading(true);
      try {
        const emailHtml = await convertToEmailSafeHtml(content, {
          wrapInTemplate: true,
          preheader
        });
        const personalizedHtml = replacePersonalizationTags(emailHtml, SAMPLE_DATA);
        const previewHtml = addEmailHeader(personalizedHtml, {
          subject,
          from: channel ? `${channel.fromName} <${channel.fromEmail}>` : "Newsletter <noreply@example.com>",
          to: SAMPLE_DATA["subscriber.email"]
        });
        setHtml(previewHtml);
        const validation = validateEmailHtml(emailHtml);
        setValidationResult(validation);
        onValidation?.(validation);
      } catch (error) {
        console.error("Failed to convert content to HTML:", error);
        setHtml("<p>Error converting content to HTML</p>");
      } finally {
        setLoading(false);
      }
    };
    convertContent();
  }, [content, subject, preheader, channel, onValidation]);
  (0, import_react5.useEffect)(() => {
    if (iframeRef.current && html) {
      const doc = iframeRef.current.contentDocument;
      if (doc) {
        doc.open();
        doc.write(html);
        doc.close();
      }
    }
  }, [html]);
  const viewport = VIEWPORT_SIZES[mode];
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { height: "100%", display: "flex", flexDirection: "column" }, children: [
    validationResult && (validationResult.errors.length > 0 || validationResult.warnings.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { padding: "16px", borderBottom: "1px solid #e5e7eb" }, children: [
      validationResult.errors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { marginBottom: "12px" }, children: [
        /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h4", { style: { color: "#dc2626", margin: "0 0 8px 0", fontSize: "14px" }, children: [
          "Errors (",
          validationResult.errors.length,
          ")"
        ] }),
        /* @__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)) })
      ] }),
      validationResult.warnings.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
        /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("h4", { style: { color: "#d97706", margin: "0 0 8px 0", fontSize: "14px" }, children: [
          "Warnings (",
          validationResult.warnings.length,
          ")"
        ] }),
        /* @__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)) })
      ] })
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
      flex: 1,
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      backgroundColor: "#f3f4f6",
      padding: "20px",
      overflow: "auto"
    }, 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: {
      backgroundColor: "white",
      boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
      borderRadius: "8px",
      overflow: "hidden",
      transform: `scale(${viewport.scale})`,
      transformOrigin: "top center"
    }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
      "iframe",
      {
        ref: iframeRef,
        title: "Email Preview",
        style: {
          width: `${viewport.width}px`,
          height: "800px",
          border: "none",
          display: "block"
        },
        sandbox: "allow-same-origin"
      }
    ) }) : /* @__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" }) }) }),
    validationResult && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
      padding: "12px 16px",
      borderTop: "1px solid #e5e7eb",
      fontSize: "13px",
      color: "#6b7280",
      display: "flex",
      gap: "24px"
    }, children: [
      /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
        "Size: ",
        Math.round(validationResult.stats.sizeInBytes / 1024),
        "KB"
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
        "Links: ",
        validationResult.stats.linkCount
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
        "Images: ",
        validationResult.stats.imageCount
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
        "Viewport: ",
        mode === "desktop" ? "600px" : "320px"
      ] })
    ] })
  ] });
};
function addEmailHeader(html, headers) {
  const headerHtml = `
    <div style="background-color: #f9fafb; border-bottom: 1px solid #e5e7eb; padding: 16px; font-family: monospace; font-size: 13px;">
      <div style="margin-bottom: 8px;"><strong>Subject:</strong> ${escapeHtml2(headers.subject)}</div>
      <div style="margin-bottom: 8px;"><strong>From:</strong> ${escapeHtml2(headers.from)}</div>
      <div><strong>To:</strong> ${escapeHtml2(headers.to)}</div>
    </div>
  `;
  return html.replace(/<body[^>]*>/, `$&${headerHtml}`);
}
function escapeHtml2(text) {
  const div = document.createElement("div");
  div.textContent = text;
  return div.innerHTML;
}

// src/components/Broadcasts/EmailPreviewField.tsx
var import_react6 = require("react");
var import_ui = require("@payloadcms/ui");
var import_jsx_runtime5 = require("react/jsx-runtime");
var EmailPreviewField = () => {
  const [previewMode, setPreviewMode] = (0, import_react6.useState)("desktop");
  const [isValid, setIsValid] = (0, import_react6.useState)(true);
  const [validationSummary, setValidationSummary] = (0, import_react6.useState)("");
  const fields = (0, import_ui.useFormFields)(([fields2]) => ({
    content: fields2.content,
    subject: fields2.subject,
    preheader: fields2.preheader,
    channel: fields2.channel
  }));
  const handleValidation = (result) => {
    setIsValid(result.valid);
    const errorCount = result.errors.length;
    const warningCount = result.warnings.length;
    if (errorCount > 0) {
      setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
    } else if (warningCount > 0) {
      setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
    } else {
      setValidationSummary("");
    }
  };
  const handleTestEmail = async () => {
    const pathParts = window.location.pathname.split("/");
    const broadcastId = pathParts[pathParts.length - 1];
    if (!broadcastId || broadcastId === "create") {
      alert("Please save the broadcast before sending a test email");
      return;
    }
    try {
      const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        }
      });
      if (!response.ok) {
        const data = await response.json();
        throw new Error(data.error || "Failed to send test email");
      }
      alert("Test email sent successfully! Check your inbox.");
    } catch (error) {
      alert(error instanceof Error ? error.message : "Failed to send test email");
    }
  };
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
    marginTop: "24px",
    border: "1px solid #e5e7eb",
    borderRadius: "8px",
    overflow: "hidden"
  }, children: [
    /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
      display: "flex",
      alignItems: "center",
      justifyContent: "space-between",
      padding: "12px 16px",
      borderBottom: "1px solid #e5e7eb",
      backgroundColor: "#f9fafb"
    }, children: [
      /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
        /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 }, children: "Email Preview" }),
        /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
          /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
            "button",
            {
              type: "button",
              onClick: () => setPreviewMode("desktop"),
              style: {
                padding: "6px 12px",
                backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
                color: previewMode === "desktop" ? "white" : "#374151",
                border: "none",
                borderRadius: "4px 0 0 4px",
                fontSize: "14px",
                cursor: "pointer"
              },
              children: "Desktop"
            }
          ),
          /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
            "button",
            {
              type: "button",
              onClick: () => setPreviewMode("mobile"),
              style: {
                padding: "6px 12px",
                backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
                color: previewMode === "mobile" ? "white" : "#374151",
                border: "none",
                borderRadius: "0 4px 4px 0",
                fontSize: "14px",
                cursor: "pointer"
              },
              children: "Mobile"
            }
          )
        ] }),
        validationSummary && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
          padding: "6px 12px",
          backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
          color: isValid ? "#92400e" : "#991b1b",
          borderRadius: "4px",
          fontSize: "13px"
        }, children: validationSummary })
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
        "button",
        {
          type: "button",
          onClick: handleTestEmail,
          style: {
            padding: "6px 12px",
            backgroundColor: "#10b981",
            color: "white",
            border: "none",
            borderRadius: "4px",
            fontSize: "14px",
            cursor: "pointer"
          },
          children: "Send Test Email"
        }
      )
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { height: "600px" }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
      EmailPreview,
      {
        content: fields.content?.value || null,
        subject: fields.subject?.value || "Email Subject",
        preheader: fields.preheader?.value,
        channel: fields.channel?.value,
        mode: previewMode,
        onValidation: handleValidation
      }
    ) })
  ] });
};

// src/components/Broadcasts/BroadcastEditor.tsx
var import_react7 = require("react");
var import_ui2 = require("@payloadcms/ui");
var import_jsx_runtime6 = require("react/jsx-runtime");
var BroadcastEditor = (props) => {
  const { value } = (0, import_ui2.useField)({ path: props.path });
  const [showPreview, setShowPreview] = (0, import_react7.useState)(true);
  const [previewMode, setPreviewMode] = (0, import_react7.useState)("desktop");
  const [isValid, setIsValid] = (0, import_react7.useState)(true);
  const [validationSummary, setValidationSummary] = (0, import_react7.useState)("");
  const fields = (0, import_ui2.useFormFields)(([fields2]) => ({
    subject: fields2.subject,
    preheader: fields2.preheader,
    channel: fields2.channel
  }));
  const handleValidation = (0, import_react7.useCallback)((result) => {
    setIsValid(result.valid);
    const errorCount = result.errors.length;
    const warningCount = result.warnings.length;
    if (errorCount > 0) {
      setValidationSummary(`${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
    } else if (warningCount > 0) {
      setValidationSummary(`${warningCount} warning${warningCount !== 1 ? "s" : ""}`);
    } else {
      setValidationSummary("");
    }
  }, []);
  const handleTestEmail = async () => {
    const pathParts = window.location.pathname.split("/");
    const broadcastId = pathParts[pathParts.length - 1];
    if (!broadcastId || broadcastId === "create") {
      alert("Please save the broadcast before sending a test email");
      return;
    }
    try {
      const response = await fetch(`/api/broadcasts/${broadcastId}/test`, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        }
      });
      if (!response.ok) {
        const data = await response.json();
        throw new Error(data.error || "Failed to send test email");
      }
      alert("Test email sent successfully! Check your inbox.");
    } catch (error) {
      alert(error instanceof Error ? error.message : "Failed to send test email");
    }
  };
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { height: "600px", display: "flex", flexDirection: "column" }, children: [
    /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
      display: "flex",
      alignItems: "center",
      justifyContent: "space-between",
      padding: "12px 16px",
      borderBottom: "1px solid #e5e7eb",
      backgroundColor: "#f9fafb"
    }, children: [
      /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "16px" }, children: [
        /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
          "button",
          {
            type: "button",
            onClick: () => setShowPreview(!showPreview),
            style: {
              padding: "6px 12px",
              backgroundColor: showPreview ? "#3b82f6" : "#e5e7eb",
              color: showPreview ? "white" : "#374151",
              border: "none",
              borderRadius: "4px",
              fontSize: "14px",
              cursor: "pointer"
            },
            children: showPreview ? "Hide Preview" : "Show Preview"
          }
        ),
        showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
          /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
            "button",
            {
              type: "button",
              onClick: () => setPreviewMode("desktop"),
              style: {
                padding: "6px 12px",
                backgroundColor: previewMode === "desktop" ? "#6366f1" : "#e5e7eb",
                color: previewMode === "desktop" ? "white" : "#374151",
                border: "none",
                borderRadius: "4px 0 0 4px",
                fontSize: "14px",
                cursor: "pointer"
              },
              children: "Desktop"
            }
          ),
          /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
            "button",
            {
              type: "button",
              onClick: () => setPreviewMode("mobile"),
              style: {
                padding: "6px 12px",
                backgroundColor: previewMode === "mobile" ? "#6366f1" : "#e5e7eb",
                color: previewMode === "mobile" ? "white" : "#374151",
                border: "none",
                borderRadius: "0 4px 4px 0",
                fontSize: "14px",
                cursor: "pointer"
              },
              children: "Mobile"
            }
          )
        ] }),
        showPreview && validationSummary && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
          padding: "6px 12px",
          backgroundColor: isValid ? "#fef3c7" : "#fee2e2",
          color: isValid ? "#92400e" : "#991b1b",
          borderRadius: "4px",
          fontSize: "13px"
        }, children: validationSummary })
      ] }),
      showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
        "button",
        {
          type: "button",
          onClick: handleTestEmail,
          style: {
            padding: "6px 12px",
            backgroundColor: "#10b981",
            color: "white",
            border: "none",
            borderRadius: "4px",
            fontSize: "14px",
            cursor: "pointer"
          },
          children: "Send Test Email"
        }
      )
    ] }),
    /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
      /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
        flex: showPreview ? "0 0 50%" : "1",
        overflow: "auto",
        borderRight: showPreview ? "1px solid #e5e7eb" : "none"
      }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { padding: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "rich-text-lexical" }) }) }),
      showPreview && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: "0 0 50%", overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
        EmailPreview,
        {
          content: value,
          subject: fields.subject?.value || "Email Subject",
          preheader: fields.preheader?.value,
          channel: fields.channel?.value,
          mode: previewMode,
          onValidation: handleValidation
        }
      ) })
    ] })
  ] });
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  BroadcastEditor,
  EmailPreview,
  EmailPreviewField,
  MagicLinkVerify,
  NewsletterForm,
  PreferencesForm,
  createMagicLinkVerify,
  createNewsletterForm,
  createPreferencesForm,
  useNewsletterAuth
});
//# sourceMappingURL=components.cjs.map