medusa-plugin-abandoned-cart
Version:
Medusa plugin for abandoned cart tracking and recovery
312 lines (303 loc) • 12.3 kB
JavaScript
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
import { InformationCircleSolid, CircleStack } from '@medusajs/icons';
import { Table, Container, Toaster, Tooltip, Button, toast } from '@medusajs/ui';
import { useAdminCustomQuery, useAdminCustomPost } from 'medusa-react';
import React, { createElement } from 'react';
import { AreaChart, XAxis, YAxis, CartesianGrid, Tooltip as Tooltip$1, Area } from 'recharts';
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
var _excluded = ["cdnSuffix", "cdnUrl", "countryCode", "style", "svg"];
var DEFAULT_CDN_URL = 'https://cdn.jsdelivr.net/gh/lipis/flag-icons/flags/4x3/';
var DEFAULT_CDN_SUFFIX = 'svg';
// offset between uppercase ascii and regional indicator symbols
var OFFSET = 127397;
var ReactCountryFlag = function ReactCountryFlag(_ref) {
var _ref$cdnSuffix = _ref.cdnSuffix,
cdnSuffix = _ref$cdnSuffix === void 0 ? DEFAULT_CDN_SUFFIX : _ref$cdnSuffix,
_ref$cdnUrl = _ref.cdnUrl,
cdnUrl = _ref$cdnUrl === void 0 ? DEFAULT_CDN_URL : _ref$cdnUrl,
countryCode = _ref.countryCode,
style = _ref.style,
_ref$svg = _ref.svg,
svg = _ref$svg === void 0 ? false : _ref$svg,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
if (typeof countryCode !== 'string') {
return null;
}
if (svg) {
var flagUrl = "" + cdnUrl + countryCode.toLowerCase() + "." + cdnSuffix;
return createElement("img", Object.assign({}, props, {
src: flagUrl,
style: _extends({
display: 'inline-block',
width: '1em',
height: '1em',
verticalAlign: 'middle'
}, style)
}));
}
var emoji = countryCode.toUpperCase().replace(/./g, function (_char) {
return String.fromCodePoint(_char.charCodeAt(0) + OFFSET);
});
return createElement("span", Object.assign({
role: "img"
}, props, {
style: _extends({
display: 'inline-block',
fontSize: '1em',
lineHeight: '1em',
verticalAlign: 'middle'
}, style)
}), emoji);
};
const repeat = (times) => {
return Array.from(Array(times).keys());
};
const LineLoading = () => {
return /* @__PURE__ */ jsx(Fragment, { children: repeat(15).map((_, i) => /* @__PURE__ */ jsxs(
Table.Row,
{
className: "animate-pulse bg-gray-100 p-4 [&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap ",
children: [
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, { className: "text-right" }),
/* @__PURE__ */ jsx(Table.Cell, { className: "text-ui-fg-muted" }),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {}),
/* @__PURE__ */ jsx(Table.Cell, {})
]
},
i
)) });
};
const AbandonedCarts = () => {
var _a;
const [pageSize] = React.useState(15);
const [size, setSize] = React.useState(0);
const [currentPage, setCurrentPage] = React.useState(0);
const { data, isLoading, refetch } = useAdminCustomQuery("/abandoned-cart", [], {
take: pageSize,
skip: pageSize * currentPage,
dateLimit: 50
});
React.useEffect(() => {
if (data && (data == null ? void 0 : data.count) !== size && data.count !== void 0) {
setSize(data == null ? void 0 : data.count);
}
}, [data == null ? void 0 : data.count]);
const { mutate, isLoading: PostLoading } = useAdminCustomPost("/abandoned-cart", []);
const canPreviousPage = currentPage > 0;
const canNextPage = currentPage < Math.round(((_a = data == null ? void 0 : data.count) != null ? _a : 0) / pageSize) - 1;
const previousPage = () => {
setCurrentPage(currentPage - 1);
};
const nextPage = () => {
setCurrentPage(currentPage + 1);
};
const handleAction = (id) => {
mutate(
{ id },
{
onSuccess: (data2) => {
console.log(data2);
toast.info("Email sent");
refetch();
},
onError: (error) => {
console.log(error);
console.log(error.message);
toast.error("Error sending email");
}
}
);
};
return /* @__PURE__ */ jsxs(Container, { className: "text-ui-fg-subtle px-0 pt-0 pb-4", children: [
/* @__PURE__ */ jsx(Toaster, {}),
/* @__PURE__ */ jsx("div", { className: "flex justify-between items-center p-5", children: /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold", children: "Abandoned Carts" }) }),
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 flex-col", children: [
/* @__PURE__ */ jsxs(Table, { children: [
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Email" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Qty" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Region" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Date" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Amount" }),
/* @__PURE__ */ jsx(Table.HeaderCell, {}),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Date Sent" }),
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Count" }),
/* @__PURE__ */ jsx(Table.HeaderCell, {})
] }) }),
/* @__PURE__ */ jsx(Table.Body, { children: !isLoading && data ? data.carts.map((cart) => {
var _a2, _b;
return /* @__PURE__ */ jsxs(
Table.Row,
{
className: "[&_td:last-child]:w-[1%] [&_td:last-child]:whitespace-nowrap",
children: [
/* @__PURE__ */ jsx(Table.Cell, { children: (cart == null ? void 0 : cart.first_name) + " " + (cart == null ? void 0 : cart.last_name) }),
/* @__PURE__ */ jsx(Table.Cell, { children: cart == null ? void 0 : cart.email }),
/* @__PURE__ */ jsx(
Table.Cell,
{
"aria-label": (_a2 = cart == null ? void 0 : cart.items) == null ? void 0 : _a2.map((item) => {
return item.title + " x" + item.quantity + " ";
}).join(","),
children: /* @__PURE__ */ jsx(
Tooltip,
{
content: (_b = cart == null ? void 0 : cart.items) == null ? void 0 : _b.map((item) => {
return item.title + " x" + item.quantity + " ";
}).join(" | \n"),
children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
cart.items.length,
/* @__PURE__ */ jsx(InformationCircleSolid, {})
] })
}
)
}
),
/* @__PURE__ */ jsx(Table.Cell, { children: cart == null ? void 0 : cart.region_name }),
/* @__PURE__ */ jsx(Table.Cell, { children: new Date(cart.created_at).toLocaleDateString() }),
/* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: (cart == null ? void 0 : cart.currency) ? new Intl.NumberFormat("en-US", {
style: "currency",
currency: (cart == null ? void 0 : cart.currency) || "USD"
}).format((cart == null ? void 0 : cart.totalPrice) / 100) : cart == null ? void 0 : cart.totalPrice }),
/* @__PURE__ */ jsx(Table.Cell, { className: "text-ui-fg-muted", children: /* @__PURE__ */ jsx(
ReactCountryFlag,
{
countryCode: cart.country_code,
style: {
fontSize: "1.5em",
lineHeight: "1.5em"
},
title: cart == null ? void 0 : cart.country_code,
"aria-label": cart == null ? void 0 : cart.country_code
}
) }),
/* @__PURE__ */ jsx(Table.Cell, { children: (cart == null ? void 0 : cart.abandoned_count) ? new Date(
cart == null ? void 0 : cart.abandoned_lastdate
).toLocaleDateString() : "Not Sent" }),
/* @__PURE__ */ jsx(Table.Cell, { children: (cart == null ? void 0 : cart.abandoned_count) ? cart == null ? void 0 : cart.abandoned_count : 0 }),
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
Button,
{
disabled: PostLoading,
variant: "transparent",
onClick: () => {
handleAction(cart == null ? void 0 : cart.id);
},
children: "Send Email"
}
) })
]
},
cart == null ? void 0 : cart.id
);
}) : /* @__PURE__ */ jsx(LineLoading, {}) })
] }),
/* @__PURE__ */ jsx(
Table.Pagination,
{
count: size,
pageSize,
pageIndex: currentPage,
pageCount: Math.round(size / pageSize),
canPreviousPage,
canNextPage,
previousPage,
nextPage
}
)
] })
] });
};
const CustomPage = () => {
const { data, isLoading, refetch } = useAdminCustomQuery("/abandoned-cart", [], {
take: 15,
skip: 15 * 1,
dateLimit: 50
});
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
AreaChart,
{
width: 730,
height: 250,
data: data == null ? void 0 : data.carts,
margin: { top: 10, right: 30, left: 0, bottom: 0 },
children: [
/* @__PURE__ */ jsxs("defs", { children: [
/* @__PURE__ */ jsxs("linearGradient", { id: "colorUv", x1: "0", y1: "0", x2: "0", y2: "1", children: [
/* @__PURE__ */ jsx("stop", { offset: "5%", stopColor: "#8884d8", stopOpacity: 0.8 }),
/* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: "#8884d8", stopOpacity: 0 })
] }),
/* @__PURE__ */ jsxs("linearGradient", { id: "colorPv", x1: "0", y1: "0", x2: "0", y2: "1", children: [
/* @__PURE__ */ jsx("stop", { offset: "5%", stopColor: "#82ca9d", stopOpacity: 0.8 }),
/* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: "#82ca9d", stopOpacity: 0 })
] })
] }),
/* @__PURE__ */ jsx(XAxis, { dataKey: "created_at" }),
/* @__PURE__ */ jsx(YAxis, {}),
/* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
/* @__PURE__ */ jsx(Tooltip$1, {}),
/* @__PURE__ */ jsx(
Area,
{
type: "monotone",
dataKey: "totalPrice",
stroke: "#8884d8",
fillOpacity: 1,
fill: "url(#colorUv)"
}
)
]
}
) }),
/* @__PURE__ */ jsx(AbandonedCarts, {})
] });
};
const config = {
link: {
label: "Abandoned Carts",
icon: CircleStack
}
};
const entry = {
identifier: "medusa-plugin-abandoned-cart",
extensions: [
{
Component: CustomPage,
config: { path: "/abandoned-carts", type: "route", ...config }
}
],
};
export { entry as default };