@kb0912/notification-brevo 2.1.1 → 2.1.2
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.
- package/.medusa/server/src/admin/index.js +150 -150
- package/.medusa/server/src/admin/index.mjs +152 -152
- package/package.json +9 -9
|
@@ -4,147 +4,6 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
4
4
|
const ui = require("@medusajs/ui");
|
|
5
5
|
const icons = require("@medusajs/icons");
|
|
6
6
|
const react = require("react");
|
|
7
|
-
const EVENT_LABELS = {
|
|
8
|
-
"order.placed": "Order Placed",
|
|
9
|
-
"order.canceled": "Order Canceled",
|
|
10
|
-
"order.delivered": "Order Delivered",
|
|
11
|
-
"customer.created": "Customer Created",
|
|
12
|
-
"promotion-new-customer": "Promotion (New Customer)",
|
|
13
|
-
"promotion-expiry-reminder": "Promotion Expiry Reminder",
|
|
14
|
-
"shipment.confirmed": "Shipment Confirmed",
|
|
15
|
-
"cart.abandoned": "Abandoned Cart",
|
|
16
|
-
"cart.abandoned.discount": "Abandoned Cart (Discount)",
|
|
17
|
-
"review.request": "Review Request",
|
|
18
|
-
"winback": "Win-back"
|
|
19
|
-
};
|
|
20
|
-
const BrevoAnalyticsPage = () => {
|
|
21
|
-
const [data, setData] = react.useState(null);
|
|
22
|
-
const [loading, setLoading] = react.useState(true);
|
|
23
|
-
const [error, setError] = react.useState(null);
|
|
24
|
-
react.useEffect(() => {
|
|
25
|
-
fetch("/admin/brevo-plugin-settings/analytics", { credentials: "include" }).then((res) => res.json()).then((d) => {
|
|
26
|
-
setData(d);
|
|
27
|
-
setLoading(false);
|
|
28
|
-
}).catch(() => {
|
|
29
|
-
setError("Failed to load analytics. Make sure Brevo API key is configured.");
|
|
30
|
-
setLoading(false);
|
|
31
|
-
});
|
|
32
|
-
}, []);
|
|
33
|
-
if (loading) {
|
|
34
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading analytics..." }) }) });
|
|
35
|
-
}
|
|
36
|
-
if (error || !(data == null ? void 0 : data.stats)) {
|
|
37
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: error || (data == null ? void 0 : data.error) || "No data available" }) }) });
|
|
38
|
-
}
|
|
39
|
-
const { stats, perEvent } = data;
|
|
40
|
-
const statItems = [
|
|
41
|
-
{ label: "Delivered", value: stats.delivered, color: "green" },
|
|
42
|
-
{ label: "Opened", value: stats.opened, color: "blue" },
|
|
43
|
-
{ label: "Clicked", value: stats.clicked, color: "purple" },
|
|
44
|
-
{ label: "Bounced", value: stats.bounced, color: "orange" },
|
|
45
|
-
{ label: "Blocked", value: stats.blocked, color: "red" },
|
|
46
|
-
{ label: "Unsubscribed", value: stats.unsubscribed, color: "grey" }
|
|
47
|
-
];
|
|
48
|
-
const openRate = stats.delivered > 0 ? (stats.opened / stats.delivered * 100).toFixed(1) : "0";
|
|
49
|
-
const clickRate = stats.opened > 0 ? (stats.clicked / stats.opened * 100).toFixed(1) : "0";
|
|
50
|
-
const perEventEntries = Object.entries(perEvent || {}).sort((a, b) => b[1].delivered - a[1].delivered);
|
|
51
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
52
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
53
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Email Analytics" }),
|
|
54
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mt-1", children: "Transactional email performance from Brevo (last 30 days)" })
|
|
55
|
-
] }) }),
|
|
56
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mt-4", children: statItems.map(({ label, value, color }) => /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 text-center", children: [
|
|
57
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs uppercase", children: label }),
|
|
58
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mt-1", children: value.toLocaleString() }),
|
|
59
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color, className: "mt-2", children: label })
|
|
60
|
-
] }) }, label)) }),
|
|
61
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
62
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4", children: "Conversion Rates" }),
|
|
63
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
64
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
65
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Metric" }),
|
|
66
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Rate" }),
|
|
67
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Details" })
|
|
68
|
-
] }) }),
|
|
69
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
70
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
71
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Open Rate" }) }),
|
|
72
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "blue", children: [
|
|
73
|
-
openRate,
|
|
74
|
-
"%"
|
|
75
|
-
] }) }),
|
|
76
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
77
|
-
stats.opened,
|
|
78
|
-
" / ",
|
|
79
|
-
stats.delivered,
|
|
80
|
-
" delivered"
|
|
81
|
-
] }) })
|
|
82
|
-
] }),
|
|
83
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
84
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Click Rate" }) }),
|
|
85
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "purple", children: [
|
|
86
|
-
clickRate,
|
|
87
|
-
"%"
|
|
88
|
-
] }) }),
|
|
89
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
90
|
-
stats.clicked,
|
|
91
|
-
" / ",
|
|
92
|
-
stats.opened,
|
|
93
|
-
" opened"
|
|
94
|
-
] }) })
|
|
95
|
-
] }),
|
|
96
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
97
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Bounce Rate" }) }),
|
|
98
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "orange", children: [
|
|
99
|
-
stats.delivered > 0 ? (stats.bounced / (stats.delivered + stats.bounced) * 100).toFixed(1) : "0",
|
|
100
|
-
"%"
|
|
101
|
-
] }) }),
|
|
102
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
103
|
-
stats.bounced,
|
|
104
|
-
" bounced"
|
|
105
|
-
] }) })
|
|
106
|
-
] })
|
|
107
|
-
] })
|
|
108
|
-
] })
|
|
109
|
-
] }) }),
|
|
110
|
-
perEventEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
111
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
112
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mb-4", children: "Performance by email event type (only events with activity shown)" }),
|
|
113
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
114
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
115
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Event" }),
|
|
116
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Sent" }),
|
|
117
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Opened" }),
|
|
118
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Clicked" }),
|
|
119
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Open Rate" }),
|
|
120
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Bounced" })
|
|
121
|
-
] }) }),
|
|
122
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: perEventEntries.map(([event, eventStats]) => {
|
|
123
|
-
const evOpenRate = eventStats.delivered > 0 ? (eventStats.opened / eventStats.delivered * 100).toFixed(1) : "0";
|
|
124
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
125
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: EVENT_LABELS[event] || event }) }),
|
|
126
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.delivered.toLocaleString() }),
|
|
127
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.opened.toLocaleString() }),
|
|
128
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.clicked.toLocaleString() }),
|
|
129
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "blue", children: [
|
|
130
|
-
evOpenRate,
|
|
131
|
-
"%"
|
|
132
|
-
] }) }),
|
|
133
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.bounced > 0 ? eventStats.bounced.toLocaleString() : "—" })
|
|
134
|
-
] }, event);
|
|
135
|
-
}) })
|
|
136
|
-
] })
|
|
137
|
-
] }) }),
|
|
138
|
-
perEventEntries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
139
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
140
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "No per-event data yet. Emails sent after this update will be tagged automatically and stats will appear here." })
|
|
141
|
-
] }) })
|
|
142
|
-
] });
|
|
143
|
-
};
|
|
144
|
-
const config$1 = adminSdk.defineRouteConfig({
|
|
145
|
-
label: "Email Analytics",
|
|
146
|
-
icon: icons.ChartBar
|
|
147
|
-
});
|
|
148
7
|
const BrevoSettingsPage = () => {
|
|
149
8
|
const [settings, setSettings] = react.useState(null);
|
|
150
9
|
const [loading, setLoading] = react.useState(true);
|
|
@@ -1040,36 +899,177 @@ const BrevoSettingsPage = () => {
|
|
|
1040
899
|
] }) })
|
|
1041
900
|
] });
|
|
1042
901
|
};
|
|
1043
|
-
const config = adminSdk.defineRouteConfig({
|
|
902
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
1044
903
|
label: "Brevo",
|
|
1045
904
|
icon: icons.EnvelopeSolid
|
|
1046
905
|
});
|
|
906
|
+
const EVENT_LABELS = {
|
|
907
|
+
"order.placed": "Order Placed",
|
|
908
|
+
"order.canceled": "Order Canceled",
|
|
909
|
+
"order.delivered": "Order Delivered",
|
|
910
|
+
"customer.created": "Customer Created",
|
|
911
|
+
"promotion-new-customer": "Promotion (New Customer)",
|
|
912
|
+
"promotion-expiry-reminder": "Promotion Expiry Reminder",
|
|
913
|
+
"shipment.confirmed": "Shipment Confirmed",
|
|
914
|
+
"cart.abandoned": "Abandoned Cart",
|
|
915
|
+
"cart.abandoned.discount": "Abandoned Cart (Discount)",
|
|
916
|
+
"review.request": "Review Request",
|
|
917
|
+
"winback": "Win-back"
|
|
918
|
+
};
|
|
919
|
+
const BrevoAnalyticsPage = () => {
|
|
920
|
+
const [data, setData] = react.useState(null);
|
|
921
|
+
const [loading, setLoading] = react.useState(true);
|
|
922
|
+
const [error, setError] = react.useState(null);
|
|
923
|
+
react.useEffect(() => {
|
|
924
|
+
fetch("/admin/brevo-plugin-settings/analytics", { credentials: "include" }).then((res) => res.json()).then((d) => {
|
|
925
|
+
setData(d);
|
|
926
|
+
setLoading(false);
|
|
927
|
+
}).catch(() => {
|
|
928
|
+
setError("Failed to load analytics. Make sure Brevo API key is configured.");
|
|
929
|
+
setLoading(false);
|
|
930
|
+
});
|
|
931
|
+
}, []);
|
|
932
|
+
if (loading) {
|
|
933
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading analytics..." }) }) });
|
|
934
|
+
}
|
|
935
|
+
if (error || !(data == null ? void 0 : data.stats)) {
|
|
936
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: error || (data == null ? void 0 : data.error) || "No data available" }) }) });
|
|
937
|
+
}
|
|
938
|
+
const { stats, perEvent } = data;
|
|
939
|
+
const statItems = [
|
|
940
|
+
{ label: "Delivered", value: stats.delivered, color: "green" },
|
|
941
|
+
{ label: "Opened", value: stats.opened, color: "blue" },
|
|
942
|
+
{ label: "Clicked", value: stats.clicked, color: "purple" },
|
|
943
|
+
{ label: "Bounced", value: stats.bounced, color: "orange" },
|
|
944
|
+
{ label: "Blocked", value: stats.blocked, color: "red" },
|
|
945
|
+
{ label: "Unsubscribed", value: stats.unsubscribed, color: "grey" }
|
|
946
|
+
];
|
|
947
|
+
const openRate = stats.delivered > 0 ? (stats.opened / stats.delivered * 100).toFixed(1) : "0";
|
|
948
|
+
const clickRate = stats.opened > 0 ? (stats.clicked / stats.opened * 100).toFixed(1) : "0";
|
|
949
|
+
const perEventEntries = Object.entries(perEvent || {}).sort((a, b) => b[1].delivered - a[1].delivered);
|
|
950
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
951
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
952
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Email Analytics" }),
|
|
953
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mt-1", children: "Transactional email performance from Brevo (last 30 days)" })
|
|
954
|
+
] }) }),
|
|
955
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mt-4", children: statItems.map(({ label, value, color }) => /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 text-center", children: [
|
|
956
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs uppercase", children: label }),
|
|
957
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mt-1", children: value.toLocaleString() }),
|
|
958
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color, className: "mt-2", children: label })
|
|
959
|
+
] }) }, label)) }),
|
|
960
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
961
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-4", children: "Conversion Rates" }),
|
|
962
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
963
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
964
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Metric" }),
|
|
965
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Rate" }),
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Details" })
|
|
967
|
+
] }) }),
|
|
968
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
970
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Open Rate" }) }),
|
|
971
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "blue", children: [
|
|
972
|
+
openRate,
|
|
973
|
+
"%"
|
|
974
|
+
] }) }),
|
|
975
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
976
|
+
stats.opened,
|
|
977
|
+
" / ",
|
|
978
|
+
stats.delivered,
|
|
979
|
+
" delivered"
|
|
980
|
+
] }) })
|
|
981
|
+
] }),
|
|
982
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
983
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Click Rate" }) }),
|
|
984
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "purple", children: [
|
|
985
|
+
clickRate,
|
|
986
|
+
"%"
|
|
987
|
+
] }) }),
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
989
|
+
stats.clicked,
|
|
990
|
+
" / ",
|
|
991
|
+
stats.opened,
|
|
992
|
+
" opened"
|
|
993
|
+
] }) })
|
|
994
|
+
] }),
|
|
995
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: "Bounce Rate" }) }),
|
|
997
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "orange", children: [
|
|
998
|
+
stats.delivered > 0 ? (stats.bounced / (stats.delivered + stats.bounced) * 100).toFixed(1) : "0",
|
|
999
|
+
"%"
|
|
1000
|
+
] }) }),
|
|
1001
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
|
|
1002
|
+
stats.bounced,
|
|
1003
|
+
" bounced"
|
|
1004
|
+
] }) })
|
|
1005
|
+
] })
|
|
1006
|
+
] })
|
|
1007
|
+
] })
|
|
1008
|
+
] }) }),
|
|
1009
|
+
perEventEntries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mb-4", children: "Performance by email event type (only events with activity shown)" }),
|
|
1012
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1013
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1014
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Event" }),
|
|
1015
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Sent" }),
|
|
1016
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Opened" }),
|
|
1017
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Clicked" }),
|
|
1018
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Open Rate" }),
|
|
1019
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Bounced" })
|
|
1020
|
+
] }) }),
|
|
1021
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: perEventEntries.map(([event, eventStats]) => {
|
|
1022
|
+
const evOpenRate = eventStats.delivered > 0 ? (eventStats.opened / eventStats.delivered * 100).toFixed(1) : "0";
|
|
1023
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: EVENT_LABELS[event] || event }) }),
|
|
1025
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.delivered.toLocaleString() }),
|
|
1026
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.opened.toLocaleString() }),
|
|
1027
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.clicked.toLocaleString() }),
|
|
1028
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "blue", children: [
|
|
1029
|
+
evOpenRate,
|
|
1030
|
+
"%"
|
|
1031
|
+
] }) }),
|
|
1032
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: eventStats.bounced > 0 ? eventStats.bounced.toLocaleString() : "—" })
|
|
1033
|
+
] }, event);
|
|
1034
|
+
}) })
|
|
1035
|
+
] })
|
|
1036
|
+
] }) }),
|
|
1037
|
+
perEventEntries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
1038
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "No per-event data yet. Emails sent after this update will be tagged automatically and stats will appear here." })
|
|
1040
|
+
] }) })
|
|
1041
|
+
] });
|
|
1042
|
+
};
|
|
1043
|
+
const config = adminSdk.defineRouteConfig({
|
|
1044
|
+
label: "Email Analytics",
|
|
1045
|
+
icon: icons.ChartBar
|
|
1046
|
+
});
|
|
1047
1047
|
const widgetModule = { widgets: [] };
|
|
1048
1048
|
const routeModule = {
|
|
1049
1049
|
routes: [
|
|
1050
|
-
{
|
|
1051
|
-
Component: BrevoAnalyticsPage,
|
|
1052
|
-
path: "/brevo-analytics"
|
|
1053
|
-
},
|
|
1054
1050
|
{
|
|
1055
1051
|
Component: BrevoSettingsPage,
|
|
1056
1052
|
path: "/settings/brevo"
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
Component: BrevoAnalyticsPage,
|
|
1056
|
+
path: "/brevo-analytics"
|
|
1057
1057
|
}
|
|
1058
1058
|
]
|
|
1059
1059
|
};
|
|
1060
1060
|
const menuItemModule = {
|
|
1061
1061
|
menuItems: [
|
|
1062
1062
|
{
|
|
1063
|
-
label: config
|
|
1064
|
-
icon: config
|
|
1063
|
+
label: config.label,
|
|
1064
|
+
icon: config.icon,
|
|
1065
1065
|
path: "/brevo-analytics",
|
|
1066
1066
|
nested: void 0,
|
|
1067
1067
|
rank: void 0,
|
|
1068
1068
|
translationNs: void 0
|
|
1069
1069
|
},
|
|
1070
1070
|
{
|
|
1071
|
-
label: config.label,
|
|
1072
|
-
icon: config.icon,
|
|
1071
|
+
label: config$1.label,
|
|
1072
|
+
icon: config$1.icon,
|
|
1073
1073
|
path: "/settings/brevo",
|
|
1074
1074
|
nested: void 0,
|
|
1075
1075
|
rank: void 0,
|
|
@@ -1,149 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
|
-
import { Container, Text,
|
|
4
|
-
import {
|
|
3
|
+
import { toast, Container, Text, Toaster, Heading, Button, Label, Input, Switch, Select, Badge, Drawer, Checkbox, Table } from "@medusajs/ui";
|
|
4
|
+
import { EnvelopeSolid, ChartBar } from "@medusajs/icons";
|
|
5
5
|
import { useState, useEffect } from "react";
|
|
6
|
-
const EVENT_LABELS = {
|
|
7
|
-
"order.placed": "Order Placed",
|
|
8
|
-
"order.canceled": "Order Canceled",
|
|
9
|
-
"order.delivered": "Order Delivered",
|
|
10
|
-
"customer.created": "Customer Created",
|
|
11
|
-
"promotion-new-customer": "Promotion (New Customer)",
|
|
12
|
-
"promotion-expiry-reminder": "Promotion Expiry Reminder",
|
|
13
|
-
"shipment.confirmed": "Shipment Confirmed",
|
|
14
|
-
"cart.abandoned": "Abandoned Cart",
|
|
15
|
-
"cart.abandoned.discount": "Abandoned Cart (Discount)",
|
|
16
|
-
"review.request": "Review Request",
|
|
17
|
-
"winback": "Win-back"
|
|
18
|
-
};
|
|
19
|
-
const BrevoAnalyticsPage = () => {
|
|
20
|
-
const [data, setData] = useState(null);
|
|
21
|
-
const [loading, setLoading] = useState(true);
|
|
22
|
-
const [error, setError] = useState(null);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
fetch("/admin/brevo-plugin-settings/analytics", { credentials: "include" }).then((res) => res.json()).then((d) => {
|
|
25
|
-
setData(d);
|
|
26
|
-
setLoading(false);
|
|
27
|
-
}).catch(() => {
|
|
28
|
-
setError("Failed to load analytics. Make sure Brevo API key is configured.");
|
|
29
|
-
setLoading(false);
|
|
30
|
-
});
|
|
31
|
-
}, []);
|
|
32
|
-
if (loading) {
|
|
33
|
-
return /* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { children: "Loading analytics..." }) }) });
|
|
34
|
-
}
|
|
35
|
-
if (error || !(data == null ? void 0 : data.stats)) {
|
|
36
|
-
return /* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: error || (data == null ? void 0 : data.error) || "No data available" }) }) });
|
|
37
|
-
}
|
|
38
|
-
const { stats, perEvent } = data;
|
|
39
|
-
const statItems = [
|
|
40
|
-
{ label: "Delivered", value: stats.delivered, color: "green" },
|
|
41
|
-
{ label: "Opened", value: stats.opened, color: "blue" },
|
|
42
|
-
{ label: "Clicked", value: stats.clicked, color: "purple" },
|
|
43
|
-
{ label: "Bounced", value: stats.bounced, color: "orange" },
|
|
44
|
-
{ label: "Blocked", value: stats.blocked, color: "red" },
|
|
45
|
-
{ label: "Unsubscribed", value: stats.unsubscribed, color: "grey" }
|
|
46
|
-
];
|
|
47
|
-
const openRate = stats.delivered > 0 ? (stats.opened / stats.delivered * 100).toFixed(1) : "0";
|
|
48
|
-
const clickRate = stats.opened > 0 ? (stats.clicked / stats.opened * 100).toFixed(1) : "0";
|
|
49
|
-
const perEventEntries = Object.entries(perEvent || {}).sort((a, b) => b[1].delivered - a[1].delivered);
|
|
50
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
-
/* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
52
|
-
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Email Analytics" }),
|
|
53
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mt-1", children: "Transactional email performance from Brevo (last 30 days)" })
|
|
54
|
-
] }) }),
|
|
55
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mt-4", children: statItems.map(({ label, value, color }) => /* @__PURE__ */ jsx(Container, { className: "p-0", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 text-center", children: [
|
|
56
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs uppercase", children: label }),
|
|
57
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mt-1", children: value.toLocaleString() }),
|
|
58
|
-
/* @__PURE__ */ jsx(Badge, { color, className: "mt-2", children: label })
|
|
59
|
-
] }) }, label)) }),
|
|
60
|
-
/* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
61
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4", children: "Conversion Rates" }),
|
|
62
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
63
|
-
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
64
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Metric" }),
|
|
65
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Rate" }),
|
|
66
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Details" })
|
|
67
|
-
] }) }),
|
|
68
|
-
/* @__PURE__ */ jsxs(Table.Body, { children: [
|
|
69
|
-
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
70
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Open Rate" }) }),
|
|
71
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "blue", children: [
|
|
72
|
-
openRate,
|
|
73
|
-
"%"
|
|
74
|
-
] }) }),
|
|
75
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
76
|
-
stats.opened,
|
|
77
|
-
" / ",
|
|
78
|
-
stats.delivered,
|
|
79
|
-
" delivered"
|
|
80
|
-
] }) })
|
|
81
|
-
] }),
|
|
82
|
-
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
83
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Click Rate" }) }),
|
|
84
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "purple", children: [
|
|
85
|
-
clickRate,
|
|
86
|
-
"%"
|
|
87
|
-
] }) }),
|
|
88
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
89
|
-
stats.clicked,
|
|
90
|
-
" / ",
|
|
91
|
-
stats.opened,
|
|
92
|
-
" opened"
|
|
93
|
-
] }) })
|
|
94
|
-
] }),
|
|
95
|
-
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
96
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Bounce Rate" }) }),
|
|
97
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "orange", children: [
|
|
98
|
-
stats.delivered > 0 ? (stats.bounced / (stats.delivered + stats.bounced) * 100).toFixed(1) : "0",
|
|
99
|
-
"%"
|
|
100
|
-
] }) }),
|
|
101
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
102
|
-
stats.bounced,
|
|
103
|
-
" bounced"
|
|
104
|
-
] }) })
|
|
105
|
-
] })
|
|
106
|
-
] })
|
|
107
|
-
] })
|
|
108
|
-
] }) }),
|
|
109
|
-
perEventEntries.length > 0 && /* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
110
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
111
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mb-4", children: "Performance by email event type (only events with activity shown)" }),
|
|
112
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
113
|
-
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
114
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Event" }),
|
|
115
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Sent" }),
|
|
116
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Opened" }),
|
|
117
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Clicked" }),
|
|
118
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Open Rate" }),
|
|
119
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Bounced" })
|
|
120
|
-
] }) }),
|
|
121
|
-
/* @__PURE__ */ jsx(Table.Body, { children: perEventEntries.map(([event, eventStats]) => {
|
|
122
|
-
const evOpenRate = eventStats.delivered > 0 ? (eventStats.opened / eventStats.delivered * 100).toFixed(1) : "0";
|
|
123
|
-
return /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
124
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: EVENT_LABELS[event] || event }) }),
|
|
125
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.delivered.toLocaleString() }),
|
|
126
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.opened.toLocaleString() }),
|
|
127
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.clicked.toLocaleString() }),
|
|
128
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "blue", children: [
|
|
129
|
-
evOpenRate,
|
|
130
|
-
"%"
|
|
131
|
-
] }) }),
|
|
132
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.bounced > 0 ? eventStats.bounced.toLocaleString() : "—" })
|
|
133
|
-
] }, event);
|
|
134
|
-
}) })
|
|
135
|
-
] })
|
|
136
|
-
] }) }),
|
|
137
|
-
perEventEntries.length === 0 && /* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
138
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
139
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "No per-event data yet. Emails sent after this update will be tagged automatically and stats will appear here." })
|
|
140
|
-
] }) })
|
|
141
|
-
] });
|
|
142
|
-
};
|
|
143
|
-
const config$1 = defineRouteConfig({
|
|
144
|
-
label: "Email Analytics",
|
|
145
|
-
icon: ChartBar
|
|
146
|
-
});
|
|
147
6
|
const BrevoSettingsPage = () => {
|
|
148
7
|
const [settings, setSettings] = useState(null);
|
|
149
8
|
const [loading, setLoading] = useState(true);
|
|
@@ -1039,36 +898,177 @@ const BrevoSettingsPage = () => {
|
|
|
1039
898
|
] }) })
|
|
1040
899
|
] });
|
|
1041
900
|
};
|
|
1042
|
-
const config = defineRouteConfig({
|
|
901
|
+
const config$1 = defineRouteConfig({
|
|
1043
902
|
label: "Brevo",
|
|
1044
903
|
icon: EnvelopeSolid
|
|
1045
904
|
});
|
|
905
|
+
const EVENT_LABELS = {
|
|
906
|
+
"order.placed": "Order Placed",
|
|
907
|
+
"order.canceled": "Order Canceled",
|
|
908
|
+
"order.delivered": "Order Delivered",
|
|
909
|
+
"customer.created": "Customer Created",
|
|
910
|
+
"promotion-new-customer": "Promotion (New Customer)",
|
|
911
|
+
"promotion-expiry-reminder": "Promotion Expiry Reminder",
|
|
912
|
+
"shipment.confirmed": "Shipment Confirmed",
|
|
913
|
+
"cart.abandoned": "Abandoned Cart",
|
|
914
|
+
"cart.abandoned.discount": "Abandoned Cart (Discount)",
|
|
915
|
+
"review.request": "Review Request",
|
|
916
|
+
"winback": "Win-back"
|
|
917
|
+
};
|
|
918
|
+
const BrevoAnalyticsPage = () => {
|
|
919
|
+
const [data, setData] = useState(null);
|
|
920
|
+
const [loading, setLoading] = useState(true);
|
|
921
|
+
const [error, setError] = useState(null);
|
|
922
|
+
useEffect(() => {
|
|
923
|
+
fetch("/admin/brevo-plugin-settings/analytics", { credentials: "include" }).then((res) => res.json()).then((d) => {
|
|
924
|
+
setData(d);
|
|
925
|
+
setLoading(false);
|
|
926
|
+
}).catch(() => {
|
|
927
|
+
setError("Failed to load analytics. Make sure Brevo API key is configured.");
|
|
928
|
+
setLoading(false);
|
|
929
|
+
});
|
|
930
|
+
}, []);
|
|
931
|
+
if (loading) {
|
|
932
|
+
return /* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { children: "Loading analytics..." }) }) });
|
|
933
|
+
}
|
|
934
|
+
if (error || !(data == null ? void 0 : data.stats)) {
|
|
935
|
+
return /* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: error || (data == null ? void 0 : data.error) || "No data available" }) }) });
|
|
936
|
+
}
|
|
937
|
+
const { stats, perEvent } = data;
|
|
938
|
+
const statItems = [
|
|
939
|
+
{ label: "Delivered", value: stats.delivered, color: "green" },
|
|
940
|
+
{ label: "Opened", value: stats.opened, color: "blue" },
|
|
941
|
+
{ label: "Clicked", value: stats.clicked, color: "purple" },
|
|
942
|
+
{ label: "Bounced", value: stats.bounced, color: "orange" },
|
|
943
|
+
{ label: "Blocked", value: stats.blocked, color: "red" },
|
|
944
|
+
{ label: "Unsubscribed", value: stats.unsubscribed, color: "grey" }
|
|
945
|
+
];
|
|
946
|
+
const openRate = stats.delivered > 0 ? (stats.opened / stats.delivered * 100).toFixed(1) : "0";
|
|
947
|
+
const clickRate = stats.opened > 0 ? (stats.clicked / stats.opened * 100).toFixed(1) : "0";
|
|
948
|
+
const perEventEntries = Object.entries(perEvent || {}).sort((a, b) => b[1].delivered - a[1].delivered);
|
|
949
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
950
|
+
/* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
951
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Email Analytics" }),
|
|
952
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mt-1", children: "Transactional email performance from Brevo (last 30 days)" })
|
|
953
|
+
] }) }),
|
|
954
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4 mt-4", children: statItems.map(({ label, value, color }) => /* @__PURE__ */ jsx(Container, { className: "p-0", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 text-center", children: [
|
|
955
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs uppercase", children: label }),
|
|
956
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mt-1", children: value.toLocaleString() }),
|
|
957
|
+
/* @__PURE__ */ jsx(Badge, { color, className: "mt-2", children: label })
|
|
958
|
+
] }) }, label)) }),
|
|
959
|
+
/* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
960
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4", children: "Conversion Rates" }),
|
|
961
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
962
|
+
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
963
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Metric" }),
|
|
964
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Rate" }),
|
|
965
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Details" })
|
|
966
|
+
] }) }),
|
|
967
|
+
/* @__PURE__ */ jsxs(Table.Body, { children: [
|
|
968
|
+
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
969
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Open Rate" }) }),
|
|
970
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "blue", children: [
|
|
971
|
+
openRate,
|
|
972
|
+
"%"
|
|
973
|
+
] }) }),
|
|
974
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
975
|
+
stats.opened,
|
|
976
|
+
" / ",
|
|
977
|
+
stats.delivered,
|
|
978
|
+
" delivered"
|
|
979
|
+
] }) })
|
|
980
|
+
] }),
|
|
981
|
+
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
982
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Click Rate" }) }),
|
|
983
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "purple", children: [
|
|
984
|
+
clickRate,
|
|
985
|
+
"%"
|
|
986
|
+
] }) }),
|
|
987
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
988
|
+
stats.clicked,
|
|
989
|
+
" / ",
|
|
990
|
+
stats.opened,
|
|
991
|
+
" opened"
|
|
992
|
+
] }) })
|
|
993
|
+
] }),
|
|
994
|
+
/* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
995
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: "Bounce Rate" }) }),
|
|
996
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "orange", children: [
|
|
997
|
+
stats.delivered > 0 ? (stats.bounced / (stats.delivered + stats.bounced) * 100).toFixed(1) : "0",
|
|
998
|
+
"%"
|
|
999
|
+
] }) }),
|
|
1000
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
1001
|
+
stats.bounced,
|
|
1002
|
+
" bounced"
|
|
1003
|
+
] }) })
|
|
1004
|
+
] })
|
|
1005
|
+
] })
|
|
1006
|
+
] })
|
|
1007
|
+
] }) }),
|
|
1008
|
+
perEventEntries.length > 0 && /* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
1009
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
1010
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mb-4", children: "Performance by email event type (only events with activity shown)" }),
|
|
1011
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
1012
|
+
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
1013
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Event" }),
|
|
1014
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Sent" }),
|
|
1015
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Opened" }),
|
|
1016
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Clicked" }),
|
|
1017
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Open Rate" }),
|
|
1018
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Bounced" })
|
|
1019
|
+
] }) }),
|
|
1020
|
+
/* @__PURE__ */ jsx(Table.Body, { children: perEventEntries.map(([event, eventStats]) => {
|
|
1021
|
+
const evOpenRate = eventStats.delivered > 0 ? (eventStats.opened / eventStats.delivered * 100).toFixed(1) : "0";
|
|
1022
|
+
return /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
1023
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { className: "font-medium", children: EVENT_LABELS[event] || event }) }),
|
|
1024
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.delivered.toLocaleString() }),
|
|
1025
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.opened.toLocaleString() }),
|
|
1026
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.clicked.toLocaleString() }),
|
|
1027
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Badge, { color: "blue", children: [
|
|
1028
|
+
evOpenRate,
|
|
1029
|
+
"%"
|
|
1030
|
+
] }) }),
|
|
1031
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: eventStats.bounced > 0 ? eventStats.bounced.toLocaleString() : "—" })
|
|
1032
|
+
] }, event);
|
|
1033
|
+
}) })
|
|
1034
|
+
] })
|
|
1035
|
+
] }) }),
|
|
1036
|
+
perEventEntries.length === 0 && /* @__PURE__ */ jsx(Container, { className: "divide-y p-0 mt-4", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
1037
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-2", children: "Per-Event Breakdown" }),
|
|
1038
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "No per-event data yet. Emails sent after this update will be tagged automatically and stats will appear here." })
|
|
1039
|
+
] }) })
|
|
1040
|
+
] });
|
|
1041
|
+
};
|
|
1042
|
+
const config = defineRouteConfig({
|
|
1043
|
+
label: "Email Analytics",
|
|
1044
|
+
icon: ChartBar
|
|
1045
|
+
});
|
|
1046
1046
|
const widgetModule = { widgets: [] };
|
|
1047
1047
|
const routeModule = {
|
|
1048
1048
|
routes: [
|
|
1049
|
-
{
|
|
1050
|
-
Component: BrevoAnalyticsPage,
|
|
1051
|
-
path: "/brevo-analytics"
|
|
1052
|
-
},
|
|
1053
1049
|
{
|
|
1054
1050
|
Component: BrevoSettingsPage,
|
|
1055
1051
|
path: "/settings/brevo"
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
Component: BrevoAnalyticsPage,
|
|
1055
|
+
path: "/brevo-analytics"
|
|
1056
1056
|
}
|
|
1057
1057
|
]
|
|
1058
1058
|
};
|
|
1059
1059
|
const menuItemModule = {
|
|
1060
1060
|
menuItems: [
|
|
1061
1061
|
{
|
|
1062
|
-
label: config
|
|
1063
|
-
icon: config
|
|
1062
|
+
label: config.label,
|
|
1063
|
+
icon: config.icon,
|
|
1064
1064
|
path: "/brevo-analytics",
|
|
1065
1065
|
nested: void 0,
|
|
1066
1066
|
rank: void 0,
|
|
1067
1067
|
translationNs: void 0
|
|
1068
1068
|
},
|
|
1069
1069
|
{
|
|
1070
|
-
label: config.label,
|
|
1071
|
-
icon: config.icon,
|
|
1070
|
+
label: config$1.label,
|
|
1071
|
+
icon: config$1.icon,
|
|
1072
1072
|
path: "/settings/brevo",
|
|
1073
1073
|
nested: void 0,
|
|
1074
1074
|
rank: void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kb0912/notification-brevo",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Sending email notification using Brevo.",
|
|
5
5
|
"author": "KB (https://medusajs.com)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"prepublishOnly": "medusa plugin:build"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@medusajs/admin-sdk": "^2.
|
|
40
|
-
"@medusajs/cli": "^2.
|
|
41
|
-
"@medusajs/framework": "^2.
|
|
42
|
-
"@medusajs/icons": "^2.
|
|
43
|
-
"@medusajs/medusa": "^2.
|
|
44
|
-
"@medusajs/test-utils": "^2.
|
|
45
|
-
"@medusajs/ui": "^4.1.
|
|
39
|
+
"@medusajs/admin-sdk": "^2.15.5",
|
|
40
|
+
"@medusajs/cli": "^2.15.5",
|
|
41
|
+
"@medusajs/framework": "^2.15.5",
|
|
42
|
+
"@medusajs/icons": "^2.15.5",
|
|
43
|
+
"@medusajs/medusa": "^2.15.5",
|
|
44
|
+
"@medusajs/test-utils": "^2.15.5",
|
|
45
|
+
"@medusajs/ui": "^4.1.15",
|
|
46
46
|
"@swc/core": "1.5.7",
|
|
47
47
|
"@types/node": "^20.0.0",
|
|
48
48
|
"@types/react": "^18.3.2",
|
|
@@ -81,4 +81,4 @@
|
|
|
81
81
|
"@getbrevo/brevo": "^4.0.1"
|
|
82
82
|
},
|
|
83
83
|
"main": "index.js"
|
|
84
|
-
}
|
|
84
|
+
}
|