payload-plugin-newsletter
Version:
Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration
341 lines (336 loc) • 11.9 kB
JavaScript
"use client";
"use client";
// src/admin/components/BroadcastInlinePreview.tsx
import { useState, useCallback } from "react";
import { useFormFields } from "@payloadcms/ui";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var BroadcastInlinePreview = () => {
const [preview, setPreview] = useState("");
const [loading, setLoading] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const [error, setError] = useState(null);
const fields = useFormFields(([fields2]) => fields2);
const generatePreview = useCallback(async () => {
try {
setLoading(true);
setError(null);
const contentField = fields?.["contentSection.content"];
const contentValue = contentField?.value;
if (!contentValue) {
setError("No content available to preview");
setLoading(false);
return;
}
const documentData = {};
Object.entries(fields || {}).forEach(([key, field]) => {
if (field && typeof field === "object" && "value" in field) {
documentData[key] = field.value;
}
});
const response = await fetch("/api/broadcasts/preview", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: contentValue,
documentData
// Pass all form data
})
});
if (!response.ok) {
throw new Error(`Preview failed: ${response.statusText}`);
}
const data = await response.json();
setPreview(data.html || "");
setShowPreview(true);
} catch (err) {
console.error("Preview generation error:", err);
setError(err instanceof Error ? err.message : "Failed to generate preview");
} finally {
setLoading(false);
}
}, [fields]);
return /* @__PURE__ */ jsxs("div", { className: "field-type", children: [
/* @__PURE__ */ jsx("div", { className: "field-label", children: "Email Preview" }),
!showPreview ? /* @__PURE__ */ jsxs("div", { className: "preview-controls", children: [
/* @__PURE__ */ jsx(
"button",
{
type: "button",
onClick: generatePreview,
disabled: loading,
className: "btn btn--style-primary btn--icon-style-without-border btn--size-small",
style: { marginBottom: "1rem" },
children: loading ? "Generating Preview..." : "Generate Preview"
}
),
error && /* @__PURE__ */ jsx("div", { className: "error-message", style: { color: "#dc2626", marginTop: "0.5rem" }, children: error })
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("div", { className: "preview-controls", style: { marginBottom: "1rem" }, children: [
/* @__PURE__ */ jsx(
"button",
{
type: "button",
onClick: () => setShowPreview(false),
className: "btn btn--style-secondary btn--icon-style-without-border btn--size-small",
style: { marginRight: "0.5rem" },
children: "Hide Preview"
}
),
/* @__PURE__ */ jsx(
"button",
{
type: "button",
onClick: generatePreview,
disabled: loading,
className: "btn btn--style-primary btn--icon-style-without-border btn--size-small",
children: loading ? "Regenerating..." : "Refresh Preview"
}
)
] }),
/* @__PURE__ */ jsx("div", { className: "email-preview-container", children: /* @__PURE__ */ jsx(
"iframe",
{
srcDoc: preview,
style: {
width: "100%",
height: "600px",
border: "1px solid #e5e7eb",
borderRadius: "0.375rem",
backgroundColor: "white"
},
title: "Email Preview"
}
) })
] })
] });
};
// src/admin/components/StatusBadge.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
var StatusBadge = (props) => {
const status = props.cellData || "draft";
const getStatusColor = (status2) => {
switch (status2) {
case "sent":
return "#22c55e";
case "scheduled":
return "#3b82f6";
case "draft":
return "#6b7280";
case "failed":
return "#ef4444";
default:
return "#6b7280";
}
};
const getStatusLabel = (status2) => {
switch (status2) {
case "sent":
return "Sent";
case "scheduled":
return "Scheduled";
case "draft":
return "Draft";
case "failed":
return "Failed";
default:
return status2;
}
};
return /* @__PURE__ */ jsx2(
"span",
{
style: {
display: "inline-block",
padding: "4px 8px",
borderRadius: "12px",
fontSize: "12px",
fontWeight: "500",
color: "#fff",
backgroundColor: getStatusColor(status),
textTransform: "capitalize"
},
children: getStatusLabel(status)
}
);
};
// src/admin/components/EmailPreview.tsx
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
var EmailPreview = ({
content,
subject,
preheader
}) => {
return /* @__PURE__ */ jsxs2("div", { className: "email-preview", style: { padding: "1rem" }, children: [
/* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem" }, children: [
/* @__PURE__ */ jsx3("strong", { children: "Subject:" }),
" ",
subject || "No subject"
] }),
preheader && /* @__PURE__ */ jsxs2("div", { style: { marginBottom: "1rem", color: "#666" }, children: [
/* @__PURE__ */ jsx3("strong", { children: "Preheader:" }),
" ",
preheader
] }),
/* @__PURE__ */ jsxs2("div", { style: {
border: "1px solid #e0e0e0",
borderRadius: "4px",
padding: "1rem",
backgroundColor: "#f9f9f9"
}, children: [
/* @__PURE__ */ jsx3("div", { children: "Email content will be rendered here" }),
content && /* @__PURE__ */ jsxs2("div", { style: { marginTop: "1rem", fontSize: "14px", color: "#666" }, children: [
"Content type: ",
typeof content
] })
] })
] });
};
// src/admin/components/WebhookConfiguration.tsx
import { useState as useState2 } from "react";
import { useFormFields as useFormFields2 } from "@payloadcms/ui";
import { Button } from "@payloadcms/ui";
import { toast } from "@payloadcms/ui";
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
var WebhookConfiguration = () => {
const [showInstructions, setShowInstructions] = useState2(false);
const [verifying, setVerifying] = useState2(false);
const fields = useFormFields2(([fields2]) => ({
webhookUrl: fields2?.broadcastSettings?.webhookUrl,
webhookStatus: fields2?.broadcastSettings?.webhookStatus,
lastWebhookReceived: fields2?.broadcastSettings?.lastWebhookReceived
}));
const handleVerify = async () => {
setVerifying(true);
try {
const response = await fetch("/api/newsletter/webhooks/verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
});
const data = await response.json();
if (data.success) {
toast.success(data.message || "Webhook verified");
} else {
toast.error(data.error || "Verification failed");
}
} catch {
toast.error("Failed to verify webhook");
} finally {
setVerifying(false);
}
};
const getStatusColor = (status) => {
switch (status) {
case "verified":
return "green";
case "configured":
return "yellow";
case "error":
return "red";
default:
return "gray";
}
};
const getStatusLabel = (status) => {
switch (status) {
case "verified":
return "Verified \u2713";
case "configured":
return "Configured";
case "error":
return "Error";
default:
return "Not Configured";
}
};
return /* @__PURE__ */ jsx4("div", { className: "field-type", children: /* @__PURE__ */ jsxs3("div", { style: { marginBottom: "1rem" }, children: [
/* @__PURE__ */ jsx4("label", { className: "field-label", children: "Webhook Configuration" }),
/* @__PURE__ */ jsxs3("div", { style: {
background: "#f5f5f5",
padding: "1rem",
borderRadius: "4px",
marginTop: "0.5rem"
}, children: [
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "0.5rem" }, children: [
/* @__PURE__ */ jsx4("strong", { children: "Webhook URL:" }),
/* @__PURE__ */ jsx4("code", { style: {
display: "block",
padding: "0.5rem",
background: "#fff",
border: "1px solid #ddd",
borderRadius: "4px",
marginTop: "0.25rem",
fontSize: "0.875rem"
}, children: fields.webhookUrl || "Save settings to generate URL" })
] }),
/* @__PURE__ */ jsxs3("div", { style: { marginBottom: "0.5rem" }, children: [
/* @__PURE__ */ jsx4("strong", { children: "Status:" }),
" ",
/* @__PURE__ */ jsx4("span", { style: { color: getStatusColor(fields.webhookStatus || "not_configured") }, children: getStatusLabel(fields.webhookStatus || "not_configured") })
] }),
fields.lastWebhookReceived && /* @__PURE__ */ jsxs3("div", { style: { fontSize: "0.875rem", color: "#666" }, children: [
"Last event: ",
new Date(fields.lastWebhookReceived).toLocaleString()
] })
] }),
/* @__PURE__ */ jsxs3("div", { style: { marginTop: "1rem", display: "flex", gap: "0.5rem" }, children: [
/* @__PURE__ */ jsxs3(
Button,
{
onClick: () => setShowInstructions(!showInstructions),
buttonStyle: "secondary",
size: "small",
children: [
showInstructions ? "Hide" : "Show",
" Instructions"
]
}
),
/* @__PURE__ */ jsx4(
Button,
{
onClick: handleVerify,
buttonStyle: "primary",
size: "small",
disabled: verifying || !fields.webhookUrl,
children: verifying ? "Verifying..." : "Verify Webhook"
}
)
] }),
showInstructions && /* @__PURE__ */ jsxs3("div", { style: {
marginTop: "1rem",
padding: "1rem",
background: "#f0f8ff",
border: "1px solid #b0d4ff",
borderRadius: "4px"
}, children: [
/* @__PURE__ */ jsx4("h4", { style: { marginTop: 0 }, children: "Configure Broadcast Webhook" }),
/* @__PURE__ */ jsxs3("ol", { children: [
/* @__PURE__ */ jsx4("li", { children: "Copy the webhook URL above" }),
/* @__PURE__ */ jsx4("li", { children: "Go to your Broadcast dashboard" }),
/* @__PURE__ */ jsx4("li", { children: 'Navigate to "Webhook Endpoints"' }),
/* @__PURE__ */ jsx4("li", { children: 'Click "Add Webhook Endpoint"' }),
/* @__PURE__ */ jsx4("li", { children: "Paste the URL" }),
/* @__PURE__ */ jsxs3("li", { children: [
"Select these events:",
/* @__PURE__ */ jsxs3("ul", { children: [
/* @__PURE__ */ jsx4("li", { children: "Subscriber Events: subscribed, unsubscribed" }),
/* @__PURE__ */ jsx4("li", { children: "Broadcast Events: All" })
] })
] }),
/* @__PURE__ */ jsx4("li", { children: 'Click "Create Webhook"' }),
/* @__PURE__ */ jsx4("li", { children: "Copy the webhook secret shown" }),
/* @__PURE__ */ jsx4("li", { children: "Paste it in the Webhook Secret field below" }),
/* @__PURE__ */ jsx4("li", { children: "Save these settings" }),
/* @__PURE__ */ jsx4("li", { children: 'Click "Verify Webhook" to test the connection' })
] })
] })
] }) });
};
export {
BroadcastInlinePreview,
EmailPreview,
StatusBadge,
WebhookConfiguration
};