medusa-product-helper 0.0.5 → 0.0.7
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 +127 -0
- package/.medusa/server/src/admin/index.mjs +127 -0
- package/README.md +12 -0
- package/package.json +1 -1
|
@@ -988,6 +988,129 @@ const ValueField = ({
|
|
|
988
988
|
adminSdk.defineWidgetConfig({
|
|
989
989
|
zone: "product.details.after"
|
|
990
990
|
});
|
|
991
|
+
const fetchJson = async (path) => {
|
|
992
|
+
const response = await fetch(path, {
|
|
993
|
+
credentials: "include"
|
|
994
|
+
});
|
|
995
|
+
const payload = await response.json().catch(() => null);
|
|
996
|
+
if (!response.ok) {
|
|
997
|
+
throw new Error(
|
|
998
|
+
(payload == null ? void 0 : payload.message) ?? "Unable to load wishlist statistics from the server"
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
return payload;
|
|
1002
|
+
};
|
|
1003
|
+
const ProductWishlistStatsWidget = ({ data }) => {
|
|
1004
|
+
const productId = data == null ? void 0 : data.id;
|
|
1005
|
+
const {
|
|
1006
|
+
data: productStats,
|
|
1007
|
+
isPending: isProductStatsPending,
|
|
1008
|
+
isError: isProductStatsError,
|
|
1009
|
+
error: productStatsError
|
|
1010
|
+
} = reactQuery.useQuery({
|
|
1011
|
+
queryKey: ["wishlist", "product", productId],
|
|
1012
|
+
enabled: Boolean(productId),
|
|
1013
|
+
queryFn: () => fetchJson(
|
|
1014
|
+
`/admin/wishlist/stats?product_id=${productId}`
|
|
1015
|
+
),
|
|
1016
|
+
refetchInterval: 6e4
|
|
1017
|
+
});
|
|
1018
|
+
const {
|
|
1019
|
+
data: allStats,
|
|
1020
|
+
isPending: isAllStatsPending,
|
|
1021
|
+
isError: isAllStatsError,
|
|
1022
|
+
error: allStatsError
|
|
1023
|
+
} = reactQuery.useQuery({
|
|
1024
|
+
queryKey: ["wishlist", "stats"],
|
|
1025
|
+
queryFn: () => fetchJson("/admin/wishlist/stats"),
|
|
1026
|
+
staleTime: 6e4
|
|
1027
|
+
});
|
|
1028
|
+
const topFive = react.useMemo(() => {
|
|
1029
|
+
var _a;
|
|
1030
|
+
if (!((_a = allStats == null ? void 0 : allStats.stats) == null ? void 0 : _a.length)) {
|
|
1031
|
+
return [];
|
|
1032
|
+
}
|
|
1033
|
+
return [...allStats.stats].sort((a, b) => b.wishlist_count - a.wishlist_count).slice(0, 5);
|
|
1034
|
+
}, [allStats]);
|
|
1035
|
+
const productWishlistCount = (productStats == null ? void 0 : productStats.wishlist_count) ?? 0;
|
|
1036
|
+
const productIsTopWishlisted = topFive.some(
|
|
1037
|
+
(stat) => stat.product_id === productId
|
|
1038
|
+
);
|
|
1039
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4", children: [
|
|
1040
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
1041
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Wishlist performance" }),
|
|
1042
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Track how often this product appears in customer wishlists and see the current top performers." })
|
|
1043
|
+
] }),
|
|
1044
|
+
!productId ? /* @__PURE__ */ jsxRuntime.jsx(ui.InlineTip, { variant: "info", label: "Product not loaded yet", children: "Open a product detail record to view wishlist insights." }) : isProductStatsPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-[96px] w-full rounded-lg" }) : isProductStatsError ? /* @__PURE__ */ jsxRuntime.jsx(ui.InlineTip, { variant: "error", label: "Unable to load product stats", children: productStatsError instanceof Error ? productStatsError.message : "Unknown error" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2 rounded-lg border border-ui-border-base bg-ui-bg-base p-4", children: [
|
|
1045
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1046
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "txt-compact-xsmall-plus uppercase tracking-wide text-ui-fg-muted", children: "This product" }),
|
|
1047
|
+
productIsTopWishlisted && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", rounded: "full", color: "green", children: "Top 5" })
|
|
1048
|
+
] }),
|
|
1049
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1050
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "text-[32px] leading-none", children: productWishlistCount }),
|
|
1051
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: productWishlistCount === 1 ? "customer has saved this product." : "customers have saved this product." })
|
|
1052
|
+
] })
|
|
1053
|
+
] }),
|
|
1054
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4", children: [
|
|
1055
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1057
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "txt-compact-xsmall-plus uppercase tracking-wide text-ui-fg-muted", children: "Storewide insights" }),
|
|
1058
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Top wishlisted products" })
|
|
1059
|
+
] }),
|
|
1060
|
+
productId && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: productIsTopWishlisted ? "In top 5" : "Not in top 5" })
|
|
1061
|
+
] }),
|
|
1062
|
+
isAllStatsPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-[160px] w-full rounded-lg" }) : isAllStatsError ? /* @__PURE__ */ jsxRuntime.jsx(ui.InlineTip, { variant: "error", label: "Unable to load storewide stats", children: allStatsError instanceof Error ? allStatsError.message : "Unknown error" }) : !topFive.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.InlineTip, { variant: "info", label: "No wishlist activity yet", children: "Customers have not saved any products to their wishlists yet. Once they do, the most popular products will show up here." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-base", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
|
|
1063
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-ui-bg-field", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
1064
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1065
|
+
"th",
|
|
1066
|
+
{
|
|
1067
|
+
scope: "col",
|
|
1068
|
+
className: "px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1069
|
+
children: "#"
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
+
"th",
|
|
1074
|
+
{
|
|
1075
|
+
scope: "col",
|
|
1076
|
+
className: "px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1077
|
+
children: "Product ID"
|
|
1078
|
+
}
|
|
1079
|
+
),
|
|
1080
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1081
|
+
"th",
|
|
1082
|
+
{
|
|
1083
|
+
scope: "col",
|
|
1084
|
+
className: "px-4 py-2 text-right text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1085
|
+
children: "Wishlists"
|
|
1086
|
+
}
|
|
1087
|
+
)
|
|
1088
|
+
] }) }),
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-ui-border-subtle", children: topFive.map((stat, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1090
|
+
"tr",
|
|
1091
|
+
{
|
|
1092
|
+
className: stat.product_id === productId ? "bg-ui-bg-subtle" : "bg-ui-bg-base",
|
|
1093
|
+
children: [
|
|
1094
|
+
/* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-4 py-3 text-sm font-medium text-ui-fg-subtle", children: [
|
|
1095
|
+
"#",
|
|
1096
|
+
index + 1
|
|
1097
|
+
] }),
|
|
1098
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1099
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-mono text-sm", children: stat.product_id }),
|
|
1100
|
+
stat.product_id === productId && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "green", children: "Current" })
|
|
1101
|
+
] }) }),
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right text-sm font-semibold", children: stat.wishlist_count.toLocaleString() })
|
|
1103
|
+
]
|
|
1104
|
+
},
|
|
1105
|
+
stat.product_id
|
|
1106
|
+
)) })
|
|
1107
|
+
] }) })
|
|
1108
|
+
] })
|
|
1109
|
+
] });
|
|
1110
|
+
};
|
|
1111
|
+
adminSdk.defineWidgetConfig({
|
|
1112
|
+
zone: "product.details.side.after"
|
|
1113
|
+
});
|
|
991
1114
|
const i18nTranslations0 = {};
|
|
992
1115
|
const widgetModule = { widgets: [
|
|
993
1116
|
{
|
|
@@ -1005,6 +1128,10 @@ const widgetModule = { widgets: [
|
|
|
1005
1128
|
{
|
|
1006
1129
|
Component: ProductMetadataTableWidget,
|
|
1007
1130
|
zone: ["product.details.after"]
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
Component: ProductWishlistStatsWidget,
|
|
1134
|
+
zone: ["product.details.side.after"]
|
|
1008
1135
|
}
|
|
1009
1136
|
] };
|
|
1010
1137
|
const routeModule = {
|
|
@@ -987,6 +987,129 @@ const ValueField = ({
|
|
|
987
987
|
defineWidgetConfig({
|
|
988
988
|
zone: "product.details.after"
|
|
989
989
|
});
|
|
990
|
+
const fetchJson = async (path) => {
|
|
991
|
+
const response = await fetch(path, {
|
|
992
|
+
credentials: "include"
|
|
993
|
+
});
|
|
994
|
+
const payload = await response.json().catch(() => null);
|
|
995
|
+
if (!response.ok) {
|
|
996
|
+
throw new Error(
|
|
997
|
+
(payload == null ? void 0 : payload.message) ?? "Unable to load wishlist statistics from the server"
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
return payload;
|
|
1001
|
+
};
|
|
1002
|
+
const ProductWishlistStatsWidget = ({ data }) => {
|
|
1003
|
+
const productId = data == null ? void 0 : data.id;
|
|
1004
|
+
const {
|
|
1005
|
+
data: productStats,
|
|
1006
|
+
isPending: isProductStatsPending,
|
|
1007
|
+
isError: isProductStatsError,
|
|
1008
|
+
error: productStatsError
|
|
1009
|
+
} = useQuery({
|
|
1010
|
+
queryKey: ["wishlist", "product", productId],
|
|
1011
|
+
enabled: Boolean(productId),
|
|
1012
|
+
queryFn: () => fetchJson(
|
|
1013
|
+
`/admin/wishlist/stats?product_id=${productId}`
|
|
1014
|
+
),
|
|
1015
|
+
refetchInterval: 6e4
|
|
1016
|
+
});
|
|
1017
|
+
const {
|
|
1018
|
+
data: allStats,
|
|
1019
|
+
isPending: isAllStatsPending,
|
|
1020
|
+
isError: isAllStatsError,
|
|
1021
|
+
error: allStatsError
|
|
1022
|
+
} = useQuery({
|
|
1023
|
+
queryKey: ["wishlist", "stats"],
|
|
1024
|
+
queryFn: () => fetchJson("/admin/wishlist/stats"),
|
|
1025
|
+
staleTime: 6e4
|
|
1026
|
+
});
|
|
1027
|
+
const topFive = useMemo(() => {
|
|
1028
|
+
var _a;
|
|
1029
|
+
if (!((_a = allStats == null ? void 0 : allStats.stats) == null ? void 0 : _a.length)) {
|
|
1030
|
+
return [];
|
|
1031
|
+
}
|
|
1032
|
+
return [...allStats.stats].sort((a, b) => b.wishlist_count - a.wishlist_count).slice(0, 5);
|
|
1033
|
+
}, [allStats]);
|
|
1034
|
+
const productWishlistCount = (productStats == null ? void 0 : productStats.wishlist_count) ?? 0;
|
|
1035
|
+
const productIsTopWishlisted = topFive.some(
|
|
1036
|
+
(stat) => stat.product_id === productId
|
|
1037
|
+
);
|
|
1038
|
+
return /* @__PURE__ */ jsxs(Container, { className: "flex flex-col gap-y-4", children: [
|
|
1039
|
+
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-y-1", children: [
|
|
1040
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Wishlist performance" }),
|
|
1041
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Track how often this product appears in customer wishlists and see the current top performers." })
|
|
1042
|
+
] }),
|
|
1043
|
+
!productId ? /* @__PURE__ */ jsx(InlineTip, { variant: "info", label: "Product not loaded yet", children: "Open a product detail record to view wishlist insights." }) : isProductStatsPending ? /* @__PURE__ */ jsx(Skeleton, { className: "h-[96px] w-full rounded-lg" }) : isProductStatsError ? /* @__PURE__ */ jsx(InlineTip, { variant: "error", label: "Unable to load product stats", children: productStatsError instanceof Error ? productStatsError.message : "Unknown error" }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-2 rounded-lg border border-ui-border-base bg-ui-bg-base p-4", children: [
|
|
1044
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1045
|
+
/* @__PURE__ */ jsx(Text, { className: "txt-compact-xsmall-plus uppercase tracking-wide text-ui-fg-muted", children: "This product" }),
|
|
1046
|
+
productIsTopWishlisted && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", rounded: "full", color: "green", children: "Top 5" })
|
|
1047
|
+
] }),
|
|
1048
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1049
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", className: "text-[32px] leading-none", children: productWishlistCount }),
|
|
1050
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: productWishlistCount === 1 ? "customer has saved this product." : "customers have saved this product." })
|
|
1051
|
+
] })
|
|
1052
|
+
] }),
|
|
1053
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3 rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4", children: [
|
|
1054
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-4", children: [
|
|
1055
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1056
|
+
/* @__PURE__ */ jsx(Text, { className: "txt-compact-xsmall-plus uppercase tracking-wide text-ui-fg-muted", children: "Storewide insights" }),
|
|
1057
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Top wishlisted products" })
|
|
1058
|
+
] }),
|
|
1059
|
+
productId && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: productIsTopWishlisted ? "In top 5" : "Not in top 5" })
|
|
1060
|
+
] }),
|
|
1061
|
+
isAllStatsPending ? /* @__PURE__ */ jsx(Skeleton, { className: "h-[160px] w-full rounded-lg" }) : isAllStatsError ? /* @__PURE__ */ jsx(InlineTip, { variant: "error", label: "Unable to load storewide stats", children: allStatsError instanceof Error ? allStatsError.message : "Unknown error" }) : !topFive.length ? /* @__PURE__ */ jsx(InlineTip, { variant: "info", label: "No wishlist activity yet", children: "Customers have not saved any products to their wishlists yet. Once they do, the most popular products will show up here." }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-base", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full divide-y divide-ui-border-base", children: [
|
|
1062
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-ui-bg-field", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
1063
|
+
/* @__PURE__ */ jsx(
|
|
1064
|
+
"th",
|
|
1065
|
+
{
|
|
1066
|
+
scope: "col",
|
|
1067
|
+
className: "px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1068
|
+
children: "#"
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
/* @__PURE__ */ jsx(
|
|
1072
|
+
"th",
|
|
1073
|
+
{
|
|
1074
|
+
scope: "col",
|
|
1075
|
+
className: "px-4 py-2 text-left text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1076
|
+
children: "Product ID"
|
|
1077
|
+
}
|
|
1078
|
+
),
|
|
1079
|
+
/* @__PURE__ */ jsx(
|
|
1080
|
+
"th",
|
|
1081
|
+
{
|
|
1082
|
+
scope: "col",
|
|
1083
|
+
className: "px-4 py-2 text-right text-[11px] font-semibold uppercase tracking-wide text-ui-fg-muted",
|
|
1084
|
+
children: "Wishlists"
|
|
1085
|
+
}
|
|
1086
|
+
)
|
|
1087
|
+
] }) }),
|
|
1088
|
+
/* @__PURE__ */ jsx("tbody", { className: "divide-y divide-ui-border-subtle", children: topFive.map((stat, index) => /* @__PURE__ */ jsxs(
|
|
1089
|
+
"tr",
|
|
1090
|
+
{
|
|
1091
|
+
className: stat.product_id === productId ? "bg-ui-bg-subtle" : "bg-ui-bg-base",
|
|
1092
|
+
children: [
|
|
1093
|
+
/* @__PURE__ */ jsxs("td", { className: "px-4 py-3 text-sm font-medium text-ui-fg-subtle", children: [
|
|
1094
|
+
"#",
|
|
1095
|
+
index + 1
|
|
1096
|
+
] }),
|
|
1097
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1098
|
+
/* @__PURE__ */ jsx(Text, { className: "font-mono text-sm", children: stat.product_id }),
|
|
1099
|
+
stat.product_id === productId && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "green", children: "Current" })
|
|
1100
|
+
] }) }),
|
|
1101
|
+
/* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-right text-sm font-semibold", children: stat.wishlist_count.toLocaleString() })
|
|
1102
|
+
]
|
|
1103
|
+
},
|
|
1104
|
+
stat.product_id
|
|
1105
|
+
)) })
|
|
1106
|
+
] }) })
|
|
1107
|
+
] })
|
|
1108
|
+
] });
|
|
1109
|
+
};
|
|
1110
|
+
defineWidgetConfig({
|
|
1111
|
+
zone: "product.details.side.after"
|
|
1112
|
+
});
|
|
990
1113
|
const i18nTranslations0 = {};
|
|
991
1114
|
const widgetModule = { widgets: [
|
|
992
1115
|
{
|
|
@@ -1004,6 +1127,10 @@ const widgetModule = { widgets: [
|
|
|
1004
1127
|
{
|
|
1005
1128
|
Component: ProductMetadataTableWidget,
|
|
1006
1129
|
zone: ["product.details.after"]
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
Component: ProductWishlistStatsWidget,
|
|
1133
|
+
zone: ["product.details.side.after"]
|
|
1007
1134
|
}
|
|
1008
1135
|
] };
|
|
1009
1136
|
const routeModule = {
|
package/README.md
CHANGED
|
@@ -194,6 +194,18 @@ inputs. No extra configuration is required beyond defining descriptors under
|
|
|
194
194
|
`metadata.products.descriptors` or `metadata.categories.descriptors` in
|
|
195
195
|
`medusa-config.ts`.
|
|
196
196
|
|
|
197
|
+
#### Wishlist Insights Widget
|
|
198
|
+
|
|
199
|
+
The admin extension also injects a **Wishlist performance** card into the product
|
|
200
|
+
details sidebar (`product.details.side.after`). The widget:
|
|
201
|
+
|
|
202
|
+
- Shows how many unique customers saved the current product.
|
|
203
|
+
- Highlights whether the product ranks within the most wishlisted items.
|
|
204
|
+
- Displays a live leaderboard (top 5) powered by `/admin/wishlist/stats`.
|
|
205
|
+
|
|
206
|
+
No additional configuration is required—install the plugin, run the wishlist
|
|
207
|
+
migration, and open any product inside Medusa Admin to see the UI.
|
|
208
|
+
|
|
197
209
|
#### Promotion Window
|
|
198
210
|
|
|
199
211
|
Configure how promotion dates are tracked using product metadata:
|