@contentgrowth/content-emailing 0.6.1 → 0.7.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.
@@ -348,7 +348,7 @@ var TemplateManager = ({
348
348
  },
349
349
  /* @__PURE__ */ React3.createElement("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" }, /* @__PURE__ */ React3.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 4v16m8-8H4" })),
350
350
  "Create Template"
351
- )), error && /* @__PURE__ */ React3.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ React3.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ React3.createElement("div", { className: "grid grid-cols-4 gap-4 mb-6" }, /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-gray-900" }, templates.length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-green-600" }, templates.filter((t) => t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Active")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, templates.filter((t) => !t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Inactive")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-blue-600" }, new Set(templates.map((t) => t.template_type)).size), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Types"))), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2 mb-6 border-b border-gray-200 pb-3 flex-wrap" }, uniqueTypes.map((type) => /* @__PURE__ */ React3.createElement(
351
+ )), error && /* @__PURE__ */ React3.createElement("div", { className: "mb-4 p-3 rounded-lg bg-red-50 text-red-800 border border-red-200" }, error, /* @__PURE__ */ React3.createElement("button", { onClick: () => setError(null), className: "ml-2 underline" }, "Dismiss")), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-4 mb-6" }, /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-gray-900" }, templates.length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-green-600" }, templates.filter((t) => t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Active")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, templates.filter((t) => !t.is_active).length), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Inactive")), /* @__PURE__ */ React3.createElement("div", { className: "bg-white rounded-lg border border-gray-200 p-4 flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "text-2xl font-bold text-blue-600" }, new Set(templates.map((t) => t.template_type)).size), /* @__PURE__ */ React3.createElement("div", { className: "text-sm text-gray-500" }, "Types"))), /* @__PURE__ */ React3.createElement("div", { className: "flex gap-2 mb-6 border-b border-gray-200 pb-3 flex-wrap" }, uniqueTypes.map((type) => /* @__PURE__ */ React3.createElement(
352
352
  "button",
353
353
  {
354
354
  key: type,
@@ -617,7 +617,149 @@ var EmailSettings = ({
617
617
  saving ? "Saving..." : "Save Changes"
618
618
  )));
619
619
  };
620
+
621
+ // src/frontend/EmailLogsPanel.tsx
622
+ import React5, { useState as useState5, useEffect as useEffect4 } from "react";
623
+ var statusColors = {
624
+ pending: "bg-yellow-100 text-yellow-800",
625
+ sent: "bg-green-100 text-green-800",
626
+ failed: "bg-red-100 text-red-800",
627
+ bounced: "bg-orange-100 text-orange-800",
628
+ complained: "bg-purple-100 text-purple-800"
629
+ };
630
+ function EmailLogsPanel({
631
+ fetchLogs,
632
+ fetchStats,
633
+ title = "Email Logs",
634
+ pageSize = 20,
635
+ className = ""
636
+ }) {
637
+ const [logs, setLogs] = useState5([]);
638
+ const [stats, setStats] = useState5(null);
639
+ const [loading, setLoading] = useState5(true);
640
+ const [page, setPage] = useState5(1);
641
+ const [totalPages, setTotalPages] = useState5(1);
642
+ const [statusFilter, setStatusFilter] = useState5("");
643
+ const [emailFilter, setEmailFilter] = useState5("");
644
+ const [templateFilter, setTemplateFilter] = useState5("");
645
+ const [showStats, setShowStats] = useState5(false);
646
+ const [error, setError] = useState5(null);
647
+ useEffect4(() => {
648
+ loadLogs();
649
+ }, [page, statusFilter]);
650
+ useEffect4(() => {
651
+ if (fetchStats && showStats) {
652
+ loadStats();
653
+ }
654
+ }, [showStats]);
655
+ const loadLogs = async () => {
656
+ setLoading(true);
657
+ setError(null);
658
+ try {
659
+ const result = await fetchLogs({
660
+ page,
661
+ limit: pageSize,
662
+ status: statusFilter || void 0,
663
+ email: emailFilter || void 0,
664
+ template: templateFilter || void 0
665
+ });
666
+ setLogs(result.logs);
667
+ setTotalPages(Math.ceil(result.total / pageSize));
668
+ } catch (err) {
669
+ setError(err.message || "Failed to load logs");
670
+ } finally {
671
+ setLoading(false);
672
+ }
673
+ };
674
+ const loadStats = async () => {
675
+ if (!fetchStats) return;
676
+ try {
677
+ const result = await fetchStats(7);
678
+ setStats(result);
679
+ } catch (err) {
680
+ console.error("Failed to load stats:", err);
681
+ }
682
+ };
683
+ const formatDate = (timestamp) => {
684
+ if (!timestamp) return "-";
685
+ return new Date(timestamp * 1e3).toLocaleString();
686
+ };
687
+ const handleSearch = (e) => {
688
+ e.preventDefault();
689
+ setPage(1);
690
+ loadLogs();
691
+ };
692
+ return /* @__PURE__ */ React5.createElement("div", { className: `email-logs-panel ${className}` }, /* @__PURE__ */ React5.createElement("div", { className: "flex justify-between items-center mb-4" }, /* @__PURE__ */ React5.createElement("h2", { className: "text-xl font-semibold text-gray-900" }, title), /* @__PURE__ */ React5.createElement("div", { className: "flex gap-2" }, fetchStats && /* @__PURE__ */ React5.createElement(
693
+ "button",
694
+ {
695
+ onClick: () => setShowStats(!showStats),
696
+ className: "px-3 py-1.5 text-sm border border-gray-300 rounded-md hover:bg-gray-50"
697
+ },
698
+ showStats ? "Hide Stats" : "Show Stats"
699
+ ), /* @__PURE__ */ React5.createElement(
700
+ "button",
701
+ {
702
+ onClick: loadLogs,
703
+ className: "px-3 py-1.5 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
704
+ },
705
+ "Refresh"
706
+ ))), showStats && stats && /* @__PURE__ */ React5.createElement("div", { className: "mb-4 p-4 bg-gray-50 rounded-lg" }, /* @__PURE__ */ React5.createElement("h3", { className: "font-medium text-gray-700 mb-3" }, "Last 7 Days"), /* @__PURE__ */ React5.createElement("div", { className: "grid grid-cols-4 gap-4" }, /* @__PURE__ */ React5.createElement("div", { className: "text-center" }, /* @__PURE__ */ React5.createElement("div", { className: "text-2xl font-bold text-gray-900" }, stats.total), /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-500" }, "Total")), /* @__PURE__ */ React5.createElement("div", { className: "text-center" }, /* @__PURE__ */ React5.createElement("div", { className: "text-2xl font-bold text-green-600" }, stats.sent), /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-500" }, "Sent")), /* @__PURE__ */ React5.createElement("div", { className: "text-center" }, /* @__PURE__ */ React5.createElement("div", { className: "text-2xl font-bold text-red-600" }, stats.failed), /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-500" }, "Failed")), /* @__PURE__ */ React5.createElement("div", { className: "text-center" }, /* @__PURE__ */ React5.createElement("div", { className: "text-2xl font-bold text-yellow-600" }, stats.pending), /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-500" }, "Pending"))), Object.keys(stats.byTemplate).length > 0 && /* @__PURE__ */ React5.createElement("div", { className: "mt-3 pt-3 border-t" }, /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-600" }, /* @__PURE__ */ React5.createElement("strong", null, "By Template:"), " ", Object.entries(stats.byTemplate).map(([template, count]) => /* @__PURE__ */ React5.createElement("span", { key: template, className: "mr-3" }, template, ": ", count))))), /* @__PURE__ */ React5.createElement("form", { onSubmit: handleSearch, className: "mb-4 flex gap-2 flex-wrap" }, /* @__PURE__ */ React5.createElement(
707
+ "select",
708
+ {
709
+ value: statusFilter,
710
+ onChange: (e) => setStatusFilter(e.target.value),
711
+ className: "px-3 py-2 border border-gray-300 rounded-md text-sm"
712
+ },
713
+ /* @__PURE__ */ React5.createElement("option", { value: "" }, "All Statuses"),
714
+ /* @__PURE__ */ React5.createElement("option", { value: "sent" }, "Sent"),
715
+ /* @__PURE__ */ React5.createElement("option", { value: "failed" }, "Failed"),
716
+ /* @__PURE__ */ React5.createElement("option", { value: "pending" }, "Pending"),
717
+ /* @__PURE__ */ React5.createElement("option", { value: "bounced" }, "Bounced")
718
+ ), /* @__PURE__ */ React5.createElement(
719
+ "input",
720
+ {
721
+ type: "text",
722
+ value: emailFilter,
723
+ onChange: (e) => setEmailFilter(e.target.value),
724
+ placeholder: "Filter by email...",
725
+ className: "px-3 py-2 border border-gray-300 rounded-md text-sm"
726
+ }
727
+ ), /* @__PURE__ */ React5.createElement(
728
+ "input",
729
+ {
730
+ type: "text",
731
+ value: templateFilter,
732
+ onChange: (e) => setTemplateFilter(e.target.value),
733
+ placeholder: "Filter by template...",
734
+ className: "px-3 py-2 border border-gray-300 rounded-md text-sm"
735
+ }
736
+ ), /* @__PURE__ */ React5.createElement(
737
+ "button",
738
+ {
739
+ type: "submit",
740
+ className: "px-4 py-2 bg-gray-600 text-white rounded-md text-sm hover:bg-gray-700"
741
+ },
742
+ "Search"
743
+ )), error && /* @__PURE__ */ React5.createElement("div", { className: "mb-4 p-3 bg-red-50 text-red-700 rounded-md" }, error), loading ? /* @__PURE__ */ React5.createElement("div", { className: "text-center py-8 text-gray-500" }, "Loading...") : logs.length === 0 ? /* @__PURE__ */ React5.createElement("div", { className: "text-center py-8 text-gray-500" }, "No email logs found") : /* @__PURE__ */ React5.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React5.createElement("table", { className: "min-w-full divide-y divide-gray-200" }, /* @__PURE__ */ React5.createElement("thead", { className: "bg-gray-50" }, /* @__PURE__ */ React5.createElement("tr", null, /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Recipient"), /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Template"), /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Subject"), /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Status"), /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Provider"), /* @__PURE__ */ React5.createElement("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase" }, "Time"))), /* @__PURE__ */ React5.createElement("tbody", { className: "bg-white divide-y divide-gray-200" }, logs.map((log) => /* @__PURE__ */ React5.createElement("tr", { key: log.id, className: log.status === "failed" ? "bg-red-50" : "" }, /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3 text-sm" }, /* @__PURE__ */ React5.createElement("div", { className: "text-gray-900" }, log.recipientEmail), log.recipientUserId && /* @__PURE__ */ React5.createElement("div", { className: "text-xs text-gray-400" }, "User: ", log.recipientUserId.substring(0, 8), "...")), /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3 text-sm text-gray-600" }, log.templateId), /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3 text-sm text-gray-600 max-w-xs truncate", title: log.subject }, log.subject || "-"), /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3" }, /* @__PURE__ */ React5.createElement("span", { className: `px-2 py-1 text-xs font-medium rounded-full ${statusColors[log.status] || "bg-gray-100 text-gray-800"}` }, log.status), log.errorMessage && /* @__PURE__ */ React5.createElement("div", { className: "text-xs text-red-600 mt-1", title: log.errorMessage }, log.errorMessage.substring(0, 30), "...")), /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3 text-sm text-gray-600" }, log.provider || "-"), /* @__PURE__ */ React5.createElement("td", { className: "px-4 py-3 text-sm text-gray-500" }, formatDate(log.createdAt))))))), totalPages > 1 && /* @__PURE__ */ React5.createElement("div", { className: "mt-4 flex justify-between items-center" }, /* @__PURE__ */ React5.createElement("div", { className: "text-sm text-gray-500" }, "Page ", page, " of ", totalPages), /* @__PURE__ */ React5.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React5.createElement(
744
+ "button",
745
+ {
746
+ onClick: () => setPage((p) => Math.max(1, p - 1)),
747
+ disabled: page <= 1,
748
+ className: "px-3 py-1.5 border border-gray-300 rounded-md text-sm disabled:opacity-50"
749
+ },
750
+ "Previous"
751
+ ), /* @__PURE__ */ React5.createElement(
752
+ "button",
753
+ {
754
+ onClick: () => setPage((p) => Math.min(totalPages, p + 1)),
755
+ disabled: page >= totalPages,
756
+ className: "px-3 py-1.5 border border-gray-300 rounded-md text-sm disabled:opacity-50"
757
+ },
758
+ "Next"
759
+ ))));
760
+ }
620
761
  export {
762
+ EmailLogsPanel,
621
763
  EmailSettings,
622
764
  TemplateEditor,
623
765
  TemplateManager,