UNPKG

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
"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 };