@lodashventure/medusa-products-bought-together 0.1.19 → 0.1.23
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 +255 -1
- package/.medusa/server/src/admin/index.mjs +255 -1
- package/.medusa/server/src/api/admin/products-bought-together/[product_id]/route.js +66 -0
- package/.medusa/server/src/modules/productsBoughtTogether/migrations/Migration20250315132351.js +1 -1
- package/.medusa/server/src/subscribers/pbt-order-handler.js +3 -5
- package/package.json +1 -1
|
@@ -1,6 +1,260 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
6
|
+
const react = require("react");
|
|
7
|
+
const adminSdk = require("@medusajs/admin-sdk");
|
|
8
|
+
const ui = require("@medusajs/ui");
|
|
9
|
+
const icons = require("@medusajs/icons");
|
|
2
10
|
require("@medusajs/admin-shared");
|
|
3
|
-
const
|
|
11
|
+
const DEFAULT_BASE_URL = typeof window !== "undefined" ? window.location.origin : "/";
|
|
12
|
+
const resolveUrl = (input) => {
|
|
13
|
+
if (input instanceof URL) {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return new URL(input);
|
|
18
|
+
} catch {
|
|
19
|
+
const sanitizedPath = input.replace(/^[\/]+/, "");
|
|
20
|
+
const base = DEFAULT_BASE_URL.replace(/\/$/, "");
|
|
21
|
+
return new URL(`${base}/${sanitizedPath}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
class FetchError extends Error {
|
|
25
|
+
constructor(message, statusText, status) {
|
|
26
|
+
super(message);
|
|
27
|
+
__publicField(this, "statusText");
|
|
28
|
+
__publicField(this, "status");
|
|
29
|
+
this.statusText = statusText;
|
|
30
|
+
this.status = status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const buildHeaders = (headers) => {
|
|
34
|
+
const resolved = new Headers();
|
|
35
|
+
if (!headers) {
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
if (headers instanceof Headers) {
|
|
39
|
+
headers.forEach((value, key) => {
|
|
40
|
+
if (value != null) {
|
|
41
|
+
resolved.append(key, value);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return resolved;
|
|
45
|
+
}
|
|
46
|
+
const appendEntry = (key, value) => {
|
|
47
|
+
if (value === void 0 || value === null) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolved.append(key, String(value));
|
|
51
|
+
};
|
|
52
|
+
if (Array.isArray(headers)) {
|
|
53
|
+
headers.forEach(([key, value]) => appendEntry(key, value));
|
|
54
|
+
return resolved;
|
|
55
|
+
}
|
|
56
|
+
Object.entries(headers).forEach(
|
|
57
|
+
([key, value]) => appendEntry(key, value)
|
|
58
|
+
);
|
|
59
|
+
return resolved;
|
|
60
|
+
};
|
|
61
|
+
const normalizeRequestInit = (init, resolvedHeaders) => {
|
|
62
|
+
var _a;
|
|
63
|
+
const requestInit = {
|
|
64
|
+
...init,
|
|
65
|
+
headers: resolvedHeaders,
|
|
66
|
+
credentials: "include"
|
|
67
|
+
};
|
|
68
|
+
const body = init == null ? void 0 : init.body;
|
|
69
|
+
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
70
|
+
if (!resolvedHeaders.has("accept")) {
|
|
71
|
+
resolvedHeaders.set("accept", "application/json");
|
|
72
|
+
}
|
|
73
|
+
if (!isFormData && body && typeof body === "object") {
|
|
74
|
+
if (!resolvedHeaders.has("content-type")) {
|
|
75
|
+
resolvedHeaders.set("content-type", "application/json");
|
|
76
|
+
}
|
|
77
|
+
if (((_a = resolvedHeaders.get("content-type")) == null ? void 0 : _a.includes("application/json")) && !(body instanceof ArrayBuffer) && !(body instanceof Blob)) {
|
|
78
|
+
requestInit.body = JSON.stringify(body);
|
|
79
|
+
return requestInit;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
requestInit.body = body;
|
|
83
|
+
return requestInit;
|
|
84
|
+
};
|
|
85
|
+
const parseResponse = async (response, headers) => {
|
|
86
|
+
var _a;
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
let message = response.statusText || "Request failed";
|
|
89
|
+
try {
|
|
90
|
+
const errorBody = await response.clone().json();
|
|
91
|
+
if (errorBody == null ? void 0 : errorBody.message) {
|
|
92
|
+
message = errorBody.message;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
throw new FetchError(message, response.statusText, response.status);
|
|
97
|
+
}
|
|
98
|
+
const expectsJson = ((_a = headers.get("accept")) == null ? void 0 : _a.includes("application/json")) ?? false;
|
|
99
|
+
if (!expectsJson) {
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
if (response.status === 204) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
return await response.json();
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const clientFetch = async (input, init) => {
|
|
112
|
+
const url = resolveUrl(input);
|
|
113
|
+
const headers = buildHeaders(init == null ? void 0 : init.headers);
|
|
114
|
+
const requestInit = normalizeRequestInit(init, headers);
|
|
115
|
+
const response = await fetch(url, requestInit);
|
|
116
|
+
return await parseResponse(response, headers);
|
|
117
|
+
};
|
|
118
|
+
const sdk = {
|
|
119
|
+
client: {
|
|
120
|
+
fetch: clientFetch
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const loadingPlaceholders = Array.from({ length: 3 });
|
|
124
|
+
const statusColor = (status) => {
|
|
125
|
+
switch ((status || "").toLowerCase()) {
|
|
126
|
+
case "published":
|
|
127
|
+
return "green";
|
|
128
|
+
case "proposed":
|
|
129
|
+
return "orange";
|
|
130
|
+
case "rejected":
|
|
131
|
+
return "red";
|
|
132
|
+
case "draft":
|
|
133
|
+
return "grey";
|
|
134
|
+
default:
|
|
135
|
+
return "grey";
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const ProductBoughtTogetherWidget = ({ data }) => {
|
|
139
|
+
const [entries, setEntries] = react.useState([]);
|
|
140
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
141
|
+
const [error, setError] = react.useState(null);
|
|
142
|
+
const fetchProducts = async () => {
|
|
143
|
+
setIsLoading(true);
|
|
144
|
+
setError(null);
|
|
145
|
+
try {
|
|
146
|
+
const response = await sdk.client.fetch(
|
|
147
|
+
`/admin/products-bought-together/${data.id}`
|
|
148
|
+
);
|
|
149
|
+
setEntries((response == null ? void 0 : response.products) ?? []);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
const message = err instanceof FetchError ? err.message : err instanceof Error ? err.message : "Failed to load products bought together";
|
|
152
|
+
setError(message);
|
|
153
|
+
setEntries([]);
|
|
154
|
+
} finally {
|
|
155
|
+
setIsLoading(false);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
react.useEffect(() => {
|
|
159
|
+
fetchProducts();
|
|
160
|
+
}, [data.id]);
|
|
161
|
+
const totalFrequency = react.useMemo(() => {
|
|
162
|
+
return entries.reduce((sum, entry) => sum + entry.frequency, 0);
|
|
163
|
+
}, [entries]);
|
|
164
|
+
const summaryText = react.useMemo(() => {
|
|
165
|
+
var _a;
|
|
166
|
+
if (!entries.length) {
|
|
167
|
+
return "We need more order data to show co-purchase insights.";
|
|
168
|
+
}
|
|
169
|
+
const topEntry = entries[0];
|
|
170
|
+
const productName = ((_a = topEntry.product) == null ? void 0 : _a.title) ?? "another product";
|
|
171
|
+
if (entries.length === 1) {
|
|
172
|
+
return `Customers bought this item with ${productName} across ${topEntry.frequency} ${topEntry.frequency === 1 ? "order" : "orders"}.`;
|
|
173
|
+
}
|
|
174
|
+
return `Tracking ${entries.length} related products across ${totalFrequency} ${totalFrequency === 1 ? "order" : "orders"}.`;
|
|
175
|
+
}, [entries, totalFrequency]);
|
|
176
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y px-0 pb-0", children: [
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-6", children: [
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md bg-ui-bg-component p-2", children: /* @__PURE__ */ jsxRuntime.jsx(icons.ChartBar, { className: "h-5 w-5 text-ui-fg-subtle" }) }),
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-1", children: [
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Bought Together Insights" }),
|
|
181
|
+
!isLoading && !error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: summaryText }),
|
|
182
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-ui-fg-muted", children: [
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowPathMini, { className: "h-3 w-3 animate-spin" }),
|
|
184
|
+
"Loading co-purchase data…"
|
|
185
|
+
] })
|
|
186
|
+
] })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5", children: [
|
|
189
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: error }),
|
|
190
|
+
!error && isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: loadingPlaceholders.map((_, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
191
|
+
"div",
|
|
192
|
+
{
|
|
193
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-ui-border-subtle px-4 py-3",
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-10 w-10 animate-pulse rounded-md bg-ui-bg-subtle" }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-32 animate-pulse rounded bg-ui-bg-subtle" }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-ui-bg-subtle" })
|
|
200
|
+
] })
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-6 w-16 animate-pulse rounded bg-ui-bg-subtle" })
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
`placeholder-${index}`
|
|
206
|
+
)) }),
|
|
207
|
+
!error && !isLoading && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-ui-border-base bg-ui-bg-component px-4 py-5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: "Once customers purchase this item with others, you will see the most common pairings here." }) }),
|
|
208
|
+
!error && !isLoading && entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: entries.map((entry) => {
|
|
209
|
+
const product = entry.product;
|
|
210
|
+
const title = (product == null ? void 0 : product.title) ?? "Product unavailable";
|
|
211
|
+
const handle = product == null ? void 0 : product.handle;
|
|
212
|
+
const status = product == null ? void 0 : product.status;
|
|
213
|
+
const thumbnail = product == null ? void 0 : product.thumbnail;
|
|
214
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
215
|
+
"div",
|
|
216
|
+
{
|
|
217
|
+
className: "flex items-center justify-between gap-4 rounded-lg border border-ui-border-base px-4 py-3",
|
|
218
|
+
children: [
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
|
|
220
|
+
thumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
221
|
+
"img",
|
|
222
|
+
{
|
|
223
|
+
src: thumbnail,
|
|
224
|
+
alt: title,
|
|
225
|
+
className: "h-10 w-10 rounded-md border border-ui-border-base object-cover"
|
|
226
|
+
}
|
|
227
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-md border border-dashed border-ui-border-base bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(icons.CubeSolid, { className: "h-4 w-4 text-ui-fg-muted" }) }),
|
|
228
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
229
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "truncate font-medium text-ui-fg-base", children: title }),
|
|
230
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "truncate text-xs text-ui-fg-subtle", children: handle ? `/${handle}` : `ID: ${entry.product_id}` })
|
|
231
|
+
] })
|
|
232
|
+
] }),
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "small", color: "blue", children: [
|
|
235
|
+
entry.frequency,
|
|
236
|
+
" ",
|
|
237
|
+
entry.frequency === 1 ? "order" : "orders"
|
|
238
|
+
] }),
|
|
239
|
+
status && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: statusColor(status), children: status })
|
|
240
|
+
] })
|
|
241
|
+
]
|
|
242
|
+
},
|
|
243
|
+
entry.product_id
|
|
244
|
+
);
|
|
245
|
+
}) })
|
|
246
|
+
] })
|
|
247
|
+
] });
|
|
248
|
+
};
|
|
249
|
+
adminSdk.defineWidgetConfig({
|
|
250
|
+
zone: "product.details.after"
|
|
251
|
+
});
|
|
252
|
+
const widgetModule = { widgets: [
|
|
253
|
+
{
|
|
254
|
+
Component: ProductBoughtTogetherWidget,
|
|
255
|
+
zone: ["product.details.after"]
|
|
256
|
+
}
|
|
257
|
+
] };
|
|
4
258
|
const routeModule = {
|
|
5
259
|
routes: []
|
|
6
260
|
};
|
|
@@ -1,5 +1,259 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
5
|
+
import { useState, useEffect, useMemo } from "react";
|
|
6
|
+
import { defineWidgetConfig } from "@medusajs/admin-sdk";
|
|
7
|
+
import { Container, Heading, Text, Alert, Badge } from "@medusajs/ui";
|
|
8
|
+
import { ChartBar, ArrowPathMini, CubeSolid } from "@medusajs/icons";
|
|
1
9
|
import "@medusajs/admin-shared";
|
|
2
|
-
const
|
|
10
|
+
const DEFAULT_BASE_URL = typeof window !== "undefined" ? window.location.origin : "/";
|
|
11
|
+
const resolveUrl = (input) => {
|
|
12
|
+
if (input instanceof URL) {
|
|
13
|
+
return input;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return new URL(input);
|
|
17
|
+
} catch {
|
|
18
|
+
const sanitizedPath = input.replace(/^[\/]+/, "");
|
|
19
|
+
const base = DEFAULT_BASE_URL.replace(/\/$/, "");
|
|
20
|
+
return new URL(`${base}/${sanitizedPath}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
class FetchError extends Error {
|
|
24
|
+
constructor(message, statusText, status) {
|
|
25
|
+
super(message);
|
|
26
|
+
__publicField(this, "statusText");
|
|
27
|
+
__publicField(this, "status");
|
|
28
|
+
this.statusText = statusText;
|
|
29
|
+
this.status = status;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const buildHeaders = (headers) => {
|
|
33
|
+
const resolved = new Headers();
|
|
34
|
+
if (!headers) {
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
if (headers instanceof Headers) {
|
|
38
|
+
headers.forEach((value, key) => {
|
|
39
|
+
if (value != null) {
|
|
40
|
+
resolved.append(key, value);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
const appendEntry = (key, value) => {
|
|
46
|
+
if (value === void 0 || value === null) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
resolved.append(key, String(value));
|
|
50
|
+
};
|
|
51
|
+
if (Array.isArray(headers)) {
|
|
52
|
+
headers.forEach(([key, value]) => appendEntry(key, value));
|
|
53
|
+
return resolved;
|
|
54
|
+
}
|
|
55
|
+
Object.entries(headers).forEach(
|
|
56
|
+
([key, value]) => appendEntry(key, value)
|
|
57
|
+
);
|
|
58
|
+
return resolved;
|
|
59
|
+
};
|
|
60
|
+
const normalizeRequestInit = (init, resolvedHeaders) => {
|
|
61
|
+
var _a;
|
|
62
|
+
const requestInit = {
|
|
63
|
+
...init,
|
|
64
|
+
headers: resolvedHeaders,
|
|
65
|
+
credentials: "include"
|
|
66
|
+
};
|
|
67
|
+
const body = init == null ? void 0 : init.body;
|
|
68
|
+
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
69
|
+
if (!resolvedHeaders.has("accept")) {
|
|
70
|
+
resolvedHeaders.set("accept", "application/json");
|
|
71
|
+
}
|
|
72
|
+
if (!isFormData && body && typeof body === "object") {
|
|
73
|
+
if (!resolvedHeaders.has("content-type")) {
|
|
74
|
+
resolvedHeaders.set("content-type", "application/json");
|
|
75
|
+
}
|
|
76
|
+
if (((_a = resolvedHeaders.get("content-type")) == null ? void 0 : _a.includes("application/json")) && !(body instanceof ArrayBuffer) && !(body instanceof Blob)) {
|
|
77
|
+
requestInit.body = JSON.stringify(body);
|
|
78
|
+
return requestInit;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
requestInit.body = body;
|
|
82
|
+
return requestInit;
|
|
83
|
+
};
|
|
84
|
+
const parseResponse = async (response, headers) => {
|
|
85
|
+
var _a;
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
let message = response.statusText || "Request failed";
|
|
88
|
+
try {
|
|
89
|
+
const errorBody = await response.clone().json();
|
|
90
|
+
if (errorBody == null ? void 0 : errorBody.message) {
|
|
91
|
+
message = errorBody.message;
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
throw new FetchError(message, response.statusText, response.status);
|
|
96
|
+
}
|
|
97
|
+
const expectsJson = ((_a = headers.get("accept")) == null ? void 0 : _a.includes("application/json")) ?? false;
|
|
98
|
+
if (!expectsJson) {
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 204) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return await response.json();
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const clientFetch = async (input, init) => {
|
|
111
|
+
const url = resolveUrl(input);
|
|
112
|
+
const headers = buildHeaders(init == null ? void 0 : init.headers);
|
|
113
|
+
const requestInit = normalizeRequestInit(init, headers);
|
|
114
|
+
const response = await fetch(url, requestInit);
|
|
115
|
+
return await parseResponse(response, headers);
|
|
116
|
+
};
|
|
117
|
+
const sdk = {
|
|
118
|
+
client: {
|
|
119
|
+
fetch: clientFetch
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const loadingPlaceholders = Array.from({ length: 3 });
|
|
123
|
+
const statusColor = (status) => {
|
|
124
|
+
switch ((status || "").toLowerCase()) {
|
|
125
|
+
case "published":
|
|
126
|
+
return "green";
|
|
127
|
+
case "proposed":
|
|
128
|
+
return "orange";
|
|
129
|
+
case "rejected":
|
|
130
|
+
return "red";
|
|
131
|
+
case "draft":
|
|
132
|
+
return "grey";
|
|
133
|
+
default:
|
|
134
|
+
return "grey";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const ProductBoughtTogetherWidget = ({ data }) => {
|
|
138
|
+
const [entries, setEntries] = useState([]);
|
|
139
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
140
|
+
const [error, setError] = useState(null);
|
|
141
|
+
const fetchProducts = async () => {
|
|
142
|
+
setIsLoading(true);
|
|
143
|
+
setError(null);
|
|
144
|
+
try {
|
|
145
|
+
const response = await sdk.client.fetch(
|
|
146
|
+
`/admin/products-bought-together/${data.id}`
|
|
147
|
+
);
|
|
148
|
+
setEntries((response == null ? void 0 : response.products) ?? []);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const message = err instanceof FetchError ? err.message : err instanceof Error ? err.message : "Failed to load products bought together";
|
|
151
|
+
setError(message);
|
|
152
|
+
setEntries([]);
|
|
153
|
+
} finally {
|
|
154
|
+
setIsLoading(false);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
fetchProducts();
|
|
159
|
+
}, [data.id]);
|
|
160
|
+
const totalFrequency = useMemo(() => {
|
|
161
|
+
return entries.reduce((sum, entry) => sum + entry.frequency, 0);
|
|
162
|
+
}, [entries]);
|
|
163
|
+
const summaryText = useMemo(() => {
|
|
164
|
+
var _a;
|
|
165
|
+
if (!entries.length) {
|
|
166
|
+
return "We need more order data to show co-purchase insights.";
|
|
167
|
+
}
|
|
168
|
+
const topEntry = entries[0];
|
|
169
|
+
const productName = ((_a = topEntry.product) == null ? void 0 : _a.title) ?? "another product";
|
|
170
|
+
if (entries.length === 1) {
|
|
171
|
+
return `Customers bought this item with ${productName} across ${topEntry.frequency} ${topEntry.frequency === 1 ? "order" : "orders"}.`;
|
|
172
|
+
}
|
|
173
|
+
return `Tracking ${entries.length} related products across ${totalFrequency} ${totalFrequency === 1 ? "order" : "orders"}.`;
|
|
174
|
+
}, [entries, totalFrequency]);
|
|
175
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y px-0 pb-0", children: [
|
|
176
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-6", children: [
|
|
177
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md bg-ui-bg-component p-2", children: /* @__PURE__ */ jsx(ChartBar, { className: "h-5 w-5 text-ui-fg-subtle" }) }),
|
|
178
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-1", children: [
|
|
179
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Bought Together Insights" }),
|
|
180
|
+
!isLoading && !error && /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: summaryText }),
|
|
181
|
+
isLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-ui-fg-muted", children: [
|
|
182
|
+
/* @__PURE__ */ jsx(ArrowPathMini, { className: "h-3 w-3 animate-spin" }),
|
|
183
|
+
"Loading co-purchase data…"
|
|
184
|
+
] })
|
|
185
|
+
] })
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-5", children: [
|
|
188
|
+
error && /* @__PURE__ */ jsx(Alert, { variant: "error", children: error }),
|
|
189
|
+
!error && isLoading && /* @__PURE__ */ jsx("div", { className: "space-y-3", children: loadingPlaceholders.map((_, index) => /* @__PURE__ */ jsxs(
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
className: "flex items-center justify-between gap-3 rounded-lg border border-ui-border-subtle px-4 py-3",
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
195
|
+
/* @__PURE__ */ jsx("div", { className: "h-10 w-10 animate-pulse rounded-md bg-ui-bg-subtle" }),
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
197
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-32 animate-pulse rounded bg-ui-bg-subtle" }),
|
|
198
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-ui-bg-subtle" })
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 w-16 animate-pulse rounded bg-ui-bg-subtle" })
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
`placeholder-${index}`
|
|
205
|
+
)) }),
|
|
206
|
+
!error && !isLoading && entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-ui-border-base bg-ui-bg-component px-4 py-5", children: /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Once customers purchase this item with others, you will see the most common pairings here." }) }),
|
|
207
|
+
!error && !isLoading && entries.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-3", children: entries.map((entry) => {
|
|
208
|
+
const product = entry.product;
|
|
209
|
+
const title = (product == null ? void 0 : product.title) ?? "Product unavailable";
|
|
210
|
+
const handle = product == null ? void 0 : product.handle;
|
|
211
|
+
const status = product == null ? void 0 : product.status;
|
|
212
|
+
const thumbnail = product == null ? void 0 : product.thumbnail;
|
|
213
|
+
return /* @__PURE__ */ jsxs(
|
|
214
|
+
"div",
|
|
215
|
+
{
|
|
216
|
+
className: "flex items-center justify-between gap-4 rounded-lg border border-ui-border-base px-4 py-3",
|
|
217
|
+
children: [
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
|
|
219
|
+
thumbnail ? /* @__PURE__ */ jsx(
|
|
220
|
+
"img",
|
|
221
|
+
{
|
|
222
|
+
src: thumbnail,
|
|
223
|
+
alt: title,
|
|
224
|
+
className: "h-10 w-10 rounded-md border border-ui-border-base object-cover"
|
|
225
|
+
}
|
|
226
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-md border border-dashed border-ui-border-base bg-ui-bg-subtle", children: /* @__PURE__ */ jsx(CubeSolid, { className: "h-4 w-4 text-ui-fg-muted" }) }),
|
|
227
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
228
|
+
/* @__PURE__ */ jsx(Text, { className: "truncate font-medium text-ui-fg-base", children: title }),
|
|
229
|
+
/* @__PURE__ */ jsx(Text, { className: "truncate text-xs text-ui-fg-subtle", children: handle ? `/${handle}` : `ID: ${entry.product_id}` })
|
|
230
|
+
] })
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-end gap-1", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs(Badge, { size: "small", color: "blue", children: [
|
|
234
|
+
entry.frequency,
|
|
235
|
+
" ",
|
|
236
|
+
entry.frequency === 1 ? "order" : "orders"
|
|
237
|
+
] }),
|
|
238
|
+
status && /* @__PURE__ */ jsx(Badge, { size: "xsmall", color: statusColor(status), children: status })
|
|
239
|
+
] })
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
entry.product_id
|
|
243
|
+
);
|
|
244
|
+
}) })
|
|
245
|
+
] })
|
|
246
|
+
] });
|
|
247
|
+
};
|
|
248
|
+
defineWidgetConfig({
|
|
249
|
+
zone: "product.details.after"
|
|
250
|
+
});
|
|
251
|
+
const widgetModule = { widgets: [
|
|
252
|
+
{
|
|
253
|
+
Component: ProductBoughtTogetherWidget,
|
|
254
|
+
zone: ["product.details.after"]
|
|
255
|
+
}
|
|
256
|
+
] };
|
|
3
257
|
const routeModule = {
|
|
4
258
|
routes: []
|
|
5
259
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const productsBoughtTogether_1 = require("../../../../modules/productsBoughtTogether");
|
|
6
|
+
const GET = async (req, res) => {
|
|
7
|
+
const { product_id: productId } = (req.params || {});
|
|
8
|
+
const productsBoughtTogetherService = req.scope.resolve(productsBoughtTogether_1.PRODUCTS_BOUGHT_TOGETHER_MODULE);
|
|
9
|
+
const productModuleService = req.scope.resolve(utils_1.Modules.PRODUCT);
|
|
10
|
+
try {
|
|
11
|
+
const relationships = await productsBoughtTogetherService.getByProductsId(productId);
|
|
12
|
+
if (!relationships.length) {
|
|
13
|
+
const payload = {
|
|
14
|
+
products: [],
|
|
15
|
+
count: 0,
|
|
16
|
+
};
|
|
17
|
+
return res.status(200).json(payload);
|
|
18
|
+
}
|
|
19
|
+
const relatedFrequency = new Map();
|
|
20
|
+
for (const relationship of relationships) {
|
|
21
|
+
const counterpart = relationship.productId1 === productId
|
|
22
|
+
? relationship.productId2
|
|
23
|
+
: relationship.productId1;
|
|
24
|
+
if (!counterpart) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const current = relatedFrequency.get(counterpart) ?? 0;
|
|
28
|
+
relatedFrequency.set(counterpart, Math.max(current, relationship.frequency));
|
|
29
|
+
}
|
|
30
|
+
if (!relatedFrequency.size) {
|
|
31
|
+
const payload = {
|
|
32
|
+
products: [],
|
|
33
|
+
count: 0,
|
|
34
|
+
};
|
|
35
|
+
return res.status(200).json(payload);
|
|
36
|
+
}
|
|
37
|
+
const relatedProductIds = Array.from(relatedFrequency.keys());
|
|
38
|
+
const relatedProducts = await productModuleService.listProducts({
|
|
39
|
+
id: relatedProductIds,
|
|
40
|
+
});
|
|
41
|
+
const productMap = new Map();
|
|
42
|
+
for (const product of relatedProducts) {
|
|
43
|
+
productMap.set(product.id, product);
|
|
44
|
+
}
|
|
45
|
+
const response = relatedProductIds
|
|
46
|
+
.map((id) => ({
|
|
47
|
+
product_id: id,
|
|
48
|
+
frequency: relatedFrequency.get(id) ?? 0,
|
|
49
|
+
product: productMap.get(id) ?? null,
|
|
50
|
+
}))
|
|
51
|
+
.sort((a, b) => b.frequency - a.frequency);
|
|
52
|
+
const payload = {
|
|
53
|
+
products: response,
|
|
54
|
+
count: response.length,
|
|
55
|
+
};
|
|
56
|
+
return res.status(200).json(payload);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const message = error instanceof Error
|
|
60
|
+
? error.message
|
|
61
|
+
: "Failed to load products bought together";
|
|
62
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, message);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.GET = GET;
|
|
66
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3Byb2R1Y3RzLWJvdWdodC10b2dldGhlci9bcHJvZHVjdF9pZF0vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQ0EscURBQWlFO0FBTWpFLHVGQUE2RjtBQWF0RixNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDbkUsTUFBTSxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsR0FBRyxDQUFDLEdBQUcsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUVsRCxDQUFDO0lBRUYsTUFBTSw2QkFBNkIsR0FDakMsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsd0RBQStCLENBQUMsQ0FBQztJQUVyRCxNQUFNLG9CQUFvQixHQUEwQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FDbkUsZUFBTyxDQUFDLE9BQU8sQ0FDaEIsQ0FBQztJQUVGLElBQUksQ0FBQztRQUNILE1BQU0sYUFBYSxHQUNqQixNQUFNLDZCQUE2QixDQUFDLGVBQWUsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUVqRSxJQUFJLENBQUMsYUFBYSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQzFCLE1BQU0sT0FBTyxHQUFtQztnQkFDOUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ1osS0FBSyxFQUFFLENBQUM7YUFDVCxDQUFDO1lBRUYsT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN2QyxDQUFDO1FBRUQsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztRQUVuRCxLQUFLLE1BQU0sWUFBWSxJQUFJLGFBQWEsRUFBRSxDQUFDO1lBQ3pDLE1BQU0sV0FBVyxHQUNmLFlBQVksQ0FBQyxVQUFVLEtBQUssU0FBUztnQkFDbkMsQ0FB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
|
package/.medusa/server/src/modules/productsBoughtTogether/migrations/Migration20250315132351.js
CHANGED
|
@@ -23,4 +23,4 @@ class Migration20250315132351 extends migrations_1.Migration {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
exports.Migration20250315132351 = Migration20250315132351;
|
|
26
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTAzMTUxMzIzNTEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9wcm9kdWN0c0JvdWdodFRvZ2V0aGVyL21pZ3JhdGlvbnMvTWlncmF0aW9uMjAyNTAzMTUxMzIzNTEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7O0dBVUc7OztBQUVILHlFQUFxRTtBQUVyRSxNQUFhLHVCQUF3QixTQUFRLHNCQUFTO0lBQzNDLEtBQUssQ0FBQyxFQUFFO1FBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FDVCxtV0FBbVcsQ0FDcFcsQ0FBQztRQUNGLElBQUksQ0FBQyxNQUFNLENBQ1QsMklBQTJJLENBQzVJLENBQUM7SUFDSixDQUFDO0lBRVEsS0FBSyxDQUFDLElBQUk7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQywwREFBMEQsQ0FBQyxDQUFDO0lBQzFFLENBQUM7Q0FDRjtBQWJELDBEQWFDIn0=
|
|
@@ -18,12 +18,10 @@ const productsBoughtTogether_1 = require("../modules/productsBoughtTogether");
|
|
|
18
18
|
async function pbtOrderHandler({ event: { data }, container, }) {
|
|
19
19
|
const orderService = container.resolve(utils_1.Modules.ORDER);
|
|
20
20
|
const order = await orderService.retrieveOrder(data.id, {
|
|
21
|
-
relations: [
|
|
22
|
-
"items"
|
|
23
|
-
]
|
|
21
|
+
relations: ["items"],
|
|
24
22
|
});
|
|
25
23
|
if (order.items) {
|
|
26
|
-
const uniqueProductIds = Array.from(new Set(order.items.map(item => item.product_id))).filter(entry => entry !== null && entry !== undefined);
|
|
24
|
+
const uniqueProductIds = Array.from(new Set(order.items.map((item) => item.product_id))).filter((entry) => entry !== null && entry !== undefined);
|
|
27
25
|
const productsBoughtTogetherService = container.resolve(productsBoughtTogether_1.PRODUCTS_BOUGHT_TOGETHER_MODULE);
|
|
28
26
|
productsBoughtTogetherService.update(uniqueProductIds);
|
|
29
27
|
}
|
|
@@ -31,4 +29,4 @@ async function pbtOrderHandler({ event: { data }, container, }) {
|
|
|
31
29
|
exports.config = {
|
|
32
30
|
event: `order.placed`,
|
|
33
31
|
};
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
32
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGJ0LW9yZGVyLWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvc3Vic2NyaWJlcnMvcGJ0LW9yZGVyLWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7O0dBVUc7OztBQVFILGtDQW9CQztBQXpCRCxxREFBb0Q7QUFFcEQsOEVBQW9GO0FBR3JFLEtBQUssVUFBVSxlQUFlLENBQUMsRUFDNUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLEVBQ2YsU0FBUyxHQUNzQjtJQUMvQixNQUFNLFlBQVksR0FBd0IsU0FBUyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7SUFFM0UsTUFBTSxLQUFLLEdBQWEsTUFBTSxZQUFZLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUU7UUFDaEUsU0FBUyxFQUFFLENBQUMsT0FBTyxDQUFDO0tBQ3JCLENBQUMsQ0FBQztJQUVILElBQUksS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2hCLE1BQU0sZ0JBQWdCLEdBQWEsS0FBSyxDQUFDLElBQUksQ0FDM0MsSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUNwRCxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsS0FBSyxLQUFLLElBQUksSUFBSSxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUM7UUFFM0QsTUFBTSw2QkFBNkIsR0FDakMsU0FBUyxDQUFDLE9BQU8sQ0FBQyx3REFBK0IsQ0FBQyxDQUFDO1FBRXJELDZCQUE2QixDQUFDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ3pELENBQUM7QUFDSCxDQUFDO0FBRVksUUFBQSxNQUFNLEdBQXFCO0lBQ3RDLEtBQUssRUFBRSxjQUFjO0NBQ3RCLENBQUMifQ==
|
package/package.json
CHANGED