medusa-plugin-printify 0.1.0

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.
Files changed (39) hide show
  1. package/.medusa/server/jest.config.js +20 -0
  2. package/.medusa/server/jest.setup.js +4 -0
  3. package/.medusa/server/src/admin/index.js +472 -0
  4. package/.medusa/server/src/admin/index.mjs +473 -0
  5. package/.medusa/server/src/api/admin/plugin/route.js +7 -0
  6. package/.medusa/server/src/api/admin/printify/orders/[id]/submit/route.js +19 -0
  7. package/.medusa/server/src/api/admin/printify/orders/route.js +18 -0
  8. package/.medusa/server/src/api/admin/printify/products/[id]/route.js +113 -0
  9. package/.medusa/server/src/api/admin/printify/products/route.js +35 -0
  10. package/.medusa/server/src/api/admin/printify/shops/route.js +18 -0
  11. package/.medusa/server/src/api/middlewares.js +12 -0
  12. package/.medusa/server/src/api/store/plugin/route.js +7 -0
  13. package/.medusa/server/src/api/webhooks/printify/route.js +102 -0
  14. package/.medusa/server/src/jobs/auto-submit-orders-job.js +31 -0
  15. package/.medusa/server/src/jobs/sync-products-job.js +23 -0
  16. package/.medusa/server/src/lib/webhook-utils.js +13 -0
  17. package/.medusa/server/src/links/printify-order-medusa-order.js +10 -0
  18. package/.medusa/server/src/links/printify-product-medusa-product.js +10 -0
  19. package/.medusa/server/src/modules/printify/api-client.js +67 -0
  20. package/.medusa/server/src/modules/printify/index.js +13 -0
  21. package/.medusa/server/src/modules/printify/migrations/Migration20260305115907.js +21 -0
  22. package/.medusa/server/src/modules/printify/migrations/Migration20260313185144.js +14 -0
  23. package/.medusa/server/src/modules/printify/models/order.js +18 -0
  24. package/.medusa/server/src/modules/printify/models/product.js +22 -0
  25. package/.medusa/server/src/modules/printify/models/shop.js +11 -0
  26. package/.medusa/server/src/modules/printify/service.js +38 -0
  27. package/.medusa/server/src/modules/printify/types.js +3 -0
  28. package/.medusa/server/src/subscribers/order-placed.js +55 -0
  29. package/.medusa/server/src/subscribers/plugin-registered.js +37 -0
  30. package/.medusa/server/src/utils/build-variant-prices.js +17 -0
  31. package/.medusa/server/src/utils/currency-converter.js +46 -0
  32. package/.medusa/server/src/workflows/create-printify-order.js +86 -0
  33. package/.medusa/server/src/workflows/index.js +12 -0
  34. package/.medusa/server/src/workflows/option-mapper.js +64 -0
  35. package/.medusa/server/src/workflows/submit-printify-order.js +29 -0
  36. package/.medusa/server/src/workflows/sync-products.js +313 -0
  37. package/.medusa/server/src/workflows/sync-shops.js +30 -0
  38. package/README.md +253 -0
  39. package/package.json +78 -0
@@ -0,0 +1,473 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
+ import { Container, Heading, Text, Badge, Button, Table, Input, Switch, Label } from "@medusajs/ui";
4
+ import { useState, useEffect } from "react";
5
+ import { BuildingStorefront, ArrowRight, ArrowLeft, ArrowUpRightOnBox } from "@medusajs/icons";
6
+ import { Link, useSearchParams, useParams } from "react-router-dom";
7
+ const PrintifyOrderWidget = ({ data }) => {
8
+ const [printifyOrders, setPrintifyOrders] = useState([]);
9
+ const [submitting, setSubmitting] = useState(false);
10
+ const loadOrders = async () => {
11
+ const res = await fetch(
12
+ `/admin/printify/orders?medusa_order_id=${data.id}`,
13
+ { credentials: "include" }
14
+ );
15
+ const json = await res.json();
16
+ setPrintifyOrders(json.orders ?? []);
17
+ };
18
+ const submitOrder = async (orderId) => {
19
+ setSubmitting(true);
20
+ try {
21
+ await fetch(`/admin/printify/orders/${orderId}/submit`, {
22
+ method: "POST",
23
+ credentials: "include"
24
+ });
25
+ await loadOrders();
26
+ } finally {
27
+ setSubmitting(false);
28
+ }
29
+ };
30
+ useEffect(() => {
31
+ loadOrders();
32
+ }, [data.id]);
33
+ if (printifyOrders.length === 0) return null;
34
+ return /* @__PURE__ */ jsxs(Container, { className: "p-4", children: [
35
+ /* @__PURE__ */ jsx(Heading, { className: "mb-3", level: "h2", children: "Printify Fulfillment" }),
36
+ printifyOrders.map((order) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2 border-b last:border-0", children: [
37
+ /* @__PURE__ */ jsxs("div", { children: [
38
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "font-medium", children: order.printify_id ?? "Not yet submitted" }),
39
+ /* @__PURE__ */ jsx(
40
+ Badge,
41
+ {
42
+ color: order.status === "shipped" ? "green" : order.status === "in-production" ? "blue" : order.status === "pending" ? "orange" : "grey",
43
+ size: "2xsmall",
44
+ className: "mt-1",
45
+ children: order.status
46
+ }
47
+ )
48
+ ] }),
49
+ order.status === "pending" && /* @__PURE__ */ jsx(
50
+ Button,
51
+ {
52
+ size: "small",
53
+ variant: "secondary",
54
+ onClick: () => submitOrder(order.id),
55
+ isLoading: submitting,
56
+ children: "Submit to Printify"
57
+ }
58
+ ),
59
+ order.total_cost && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-muted", children: [
60
+ "Cost: $",
61
+ (order.total_cost / 100).toFixed(2)
62
+ ] })
63
+ ] }, order.id))
64
+ ] });
65
+ };
66
+ defineWidgetConfig({
67
+ zone: "order.details.after"
68
+ });
69
+ const PrintifyPage = () => {
70
+ const [shops, setShops] = useState([]);
71
+ const [syncing, setSyncing] = useState(false);
72
+ const loadShops = async () => {
73
+ const res = await fetch("/admin/printify/shops", { credentials: "include" });
74
+ const json = await res.json();
75
+ setShops(json.shops ?? []);
76
+ };
77
+ const syncShops = async () => {
78
+ setSyncing(true);
79
+ try {
80
+ await fetch("/admin/printify/shops", { method: "POST", credentials: "include" });
81
+ await loadShops();
82
+ } finally {
83
+ setSyncing(false);
84
+ }
85
+ };
86
+ useEffect(() => {
87
+ loadShops();
88
+ }, []);
89
+ return /* @__PURE__ */ jsxs(Container, { className: "p-8", children: [
90
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-6", children: [
91
+ /* @__PURE__ */ jsx(Heading, { children: "Printify Integration" }),
92
+ /* @__PURE__ */ jsx(Button, { onClick: syncShops, isLoading: syncing, size: "small", children: "Sync Shops" })
93
+ ] }),
94
+ /* @__PURE__ */ jsxs(Table, { children: [
95
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
96
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Shop Name" }),
97
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Printify ID" }),
98
+ /* @__PURE__ */ jsx(Table.HeaderCell, {})
99
+ ] }) }),
100
+ /* @__PURE__ */ jsx(Table.Body, { children: shops.map((shop) => /* @__PURE__ */ jsxs(Table.Row, { children: [
101
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
102
+ Link,
103
+ {
104
+ to: `/printify/products?shop_id=${shop.printify_id}&shop_name=${encodeURIComponent(shop.title)}`,
105
+ className: "block w-full text-inherit no-underline",
106
+ children: shop.title
107
+ }
108
+ ) }),
109
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
110
+ Link,
111
+ {
112
+ to: `/printify/products?shop_id=${shop.printify_id}&shop_name=${encodeURIComponent(shop.title)}`,
113
+ className: "block w-full no-underline",
114
+ children: /* @__PURE__ */ jsx(Badge, { color: "grey", size: "2xsmall", children: shop.printify_id })
115
+ }
116
+ ) }),
117
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
118
+ Link,
119
+ {
120
+ to: `/printify/products?shop_id=${shop.printify_id}&shop_name=${encodeURIComponent(shop.title)}`,
121
+ className: "block w-full no-underline text-ui-fg-muted",
122
+ children: /* @__PURE__ */ jsx(ArrowRight, {})
123
+ }
124
+ ) })
125
+ ] }, shop.id)) })
126
+ ] })
127
+ ] });
128
+ };
129
+ const config = defineRouteConfig({
130
+ label: "Printify",
131
+ icon: BuildingStorefront
132
+ });
133
+ const PrintifyProductsPage = () => {
134
+ const [products, setProducts] = useState([]);
135
+ const [syncing, setSyncing] = useState(false);
136
+ const [search, setSearch] = useState("");
137
+ const [searchParams] = useSearchParams();
138
+ const shopId = searchParams.get("shop_id");
139
+ const shopName = searchParams.get("shop_name") ?? shopId ?? "All Shops";
140
+ const loadProducts = async () => {
141
+ const params = new URLSearchParams();
142
+ if (shopId) params.set("shop_id", shopId);
143
+ const res = await fetch(`/admin/printify/products?${params}`, { credentials: "include" });
144
+ const json = await res.json();
145
+ setProducts(json.products ?? []);
146
+ };
147
+ const syncProducts = async () => {
148
+ setSyncing(true);
149
+ try {
150
+ await fetch("/admin/printify/products", {
151
+ method: "POST",
152
+ credentials: "include"
153
+ });
154
+ await loadProducts();
155
+ } finally {
156
+ setSyncing(false);
157
+ }
158
+ };
159
+ const toggleVisibility = async (product) => {
160
+ const newValue = !product.is_published;
161
+ setProducts(
162
+ (prev) => prev.map((p) => p.id === product.id ? { ...p, is_published: newValue } : p)
163
+ );
164
+ try {
165
+ const res = await fetch(`/admin/printify/products/${product.id}`, {
166
+ method: "PATCH",
167
+ credentials: "include",
168
+ headers: { "Content-Type": "application/json" },
169
+ body: JSON.stringify({ is_published: newValue })
170
+ });
171
+ if (!res.ok) throw new Error("Request failed");
172
+ } catch {
173
+ setProducts(
174
+ (prev) => prev.map((p) => p.id === product.id ? { ...p, is_published: product.is_published } : p)
175
+ );
176
+ }
177
+ };
178
+ useEffect(() => {
179
+ loadProducts();
180
+ }, [shopId]);
181
+ const filtered = products.filter(
182
+ (p) => p.title.toLowerCase().includes(search.toLowerCase())
183
+ );
184
+ const getThumb = (images) => {
185
+ const img = (images == null ? void 0 : images.find((i) => i.is_default)) ?? (images == null ? void 0 : images[0]);
186
+ return (img == null ? void 0 : img.src) ?? null;
187
+ };
188
+ return /* @__PURE__ */ jsxs(Container, { className: "p-8", children: [
189
+ /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxs(
190
+ Link,
191
+ {
192
+ to: "/printify",
193
+ className: "flex items-center gap-1 text-ui-fg-muted hover:text-ui-fg-base text-sm no-underline",
194
+ children: [
195
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "w-4 h-4" }),
196
+ "Back to Shops"
197
+ ]
198
+ }
199
+ ) }),
200
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-6", children: [
201
+ /* @__PURE__ */ jsxs("div", { children: [
202
+ /* @__PURE__ */ jsx(Heading, { children: shopName }),
203
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-muted", children: [
204
+ products.length,
205
+ " product",
206
+ products.length !== 1 ? "s" : ""
207
+ ] })
208
+ ] }),
209
+ /* @__PURE__ */ jsx(Button, { onClick: syncProducts, isLoading: syncing, size: "small", children: "Sync Products" })
210
+ ] }),
211
+ /* @__PURE__ */ jsx(
212
+ Input,
213
+ {
214
+ placeholder: "Search products...",
215
+ value: search,
216
+ onChange: (e) => setSearch(e.target.value),
217
+ className: "mb-4 max-w-xs"
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsxs(Table, { children: [
221
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
222
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "w-12" }),
223
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Title" }),
224
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Variants" }),
225
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
226
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Visible on Store" })
227
+ ] }) }),
228
+ /* @__PURE__ */ jsx(Table.Body, { children: filtered.map((product) => {
229
+ var _a;
230
+ return /* @__PURE__ */ jsxs(Table.Row, { children: [
231
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Link, { to: `/printify/products/${product.id}`, className: "block", children: getThumb(product.images) ? /* @__PURE__ */ jsx(
232
+ "img",
233
+ {
234
+ src: getThumb(product.images),
235
+ alt: "",
236
+ className: "w-10 h-10 rounded object-cover"
237
+ }
238
+ ) : /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded bg-ui-bg-subtle" }) }) }),
239
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Link, { to: `/printify/products/${product.id}`, className: "block text-inherit no-underline", children: product.title }) }),
240
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs(Link, { to: `/printify/products/${product.id}`, className: "block text-inherit no-underline", children: [
241
+ ((_a = product.variants) == null ? void 0 : _a.length) ?? 0,
242
+ " variants"
243
+ ] }) }),
244
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Link, { to: `/printify/products/${product.id}`, className: "block no-underline", children: /* @__PURE__ */ jsx(Badge, { color: product.is_published ? "green" : "grey", size: "2xsmall", children: product.is_published ? "Published" : "Draft" }) }) }),
245
+ /* @__PURE__ */ jsx(Table.Cell, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
246
+ Switch,
247
+ {
248
+ checked: product.is_published,
249
+ onCheckedChange: () => toggleVisibility(product)
250
+ }
251
+ ) })
252
+ ] }, product.id);
253
+ }) })
254
+ ] })
255
+ ] });
256
+ };
257
+ const formatCents = (cents) => `$${(cents / 100).toFixed(2)}`;
258
+ const PrintifyProductDetailPage = () => {
259
+ var _a, _b;
260
+ const { id } = useParams();
261
+ const [product, setProduct] = useState(null);
262
+ const [medusaProductId, setMedusaProductId] = useState(null);
263
+ const [loading, setLoading] = useState(true);
264
+ const [rawOpen, setRawOpen] = useState(false);
265
+ const [isPublished, setIsPublished] = useState(false);
266
+ const [toggling, setToggling] = useState(false);
267
+ useEffect(() => {
268
+ const load = async () => {
269
+ try {
270
+ const res = await fetch(`/admin/printify/products/${id}`, { credentials: "include" });
271
+ const json = await res.json();
272
+ setProduct(json.product);
273
+ setIsPublished(json.product.is_published);
274
+ setMedusaProductId(json.medusa_product_id ?? null);
275
+ } finally {
276
+ setLoading(false);
277
+ }
278
+ };
279
+ load();
280
+ }, [id]);
281
+ const toggleVisibility = async () => {
282
+ setToggling(true);
283
+ const newValue = !isPublished;
284
+ try {
285
+ const res = await fetch(`/admin/printify/products/${id}`, {
286
+ method: "PATCH",
287
+ credentials: "include",
288
+ headers: { "Content-Type": "application/json" },
289
+ body: JSON.stringify({ is_published: newValue })
290
+ });
291
+ if (res.ok) {
292
+ setIsPublished(newValue);
293
+ }
294
+ } finally {
295
+ setToggling(false);
296
+ }
297
+ };
298
+ if (loading) {
299
+ return /* @__PURE__ */ jsx(Container, { className: "p-8", children: /* @__PURE__ */ jsx(Text, { children: "Loading..." }) });
300
+ }
301
+ if (!product) {
302
+ return /* @__PURE__ */ jsx(Container, { className: "p-8", children: /* @__PURE__ */ jsx(Text, { children: "Product not found." }) });
303
+ }
304
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-8", children: [
305
+ /* @__PURE__ */ jsxs(
306
+ Link,
307
+ {
308
+ to: "/printify/products",
309
+ className: "flex items-center gap-1 text-ui-fg-muted hover:text-ui-fg-base text-sm w-fit no-underline",
310
+ children: [
311
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "w-4 h-4" }),
312
+ "Back to Products"
313
+ ]
314
+ }
315
+ ),
316
+ /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
317
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
318
+ /* @__PURE__ */ jsxs("div", { children: [
319
+ /* @__PURE__ */ jsx(Heading, { children: product.title }),
320
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-muted mt-1", children: [
321
+ "Printify ID: ",
322
+ product.printify_id
323
+ ] })
324
+ ] }),
325
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
326
+ /* @__PURE__ */ jsx(Badge, { color: isPublished ? "green" : "grey", size: "small", children: isPublished ? "Published" : "Draft" }),
327
+ /* @__PURE__ */ jsx(
328
+ Switch,
329
+ {
330
+ id: "visibility-toggle",
331
+ checked: isPublished,
332
+ onCheckedChange: toggleVisibility,
333
+ disabled: toggling
334
+ }
335
+ ),
336
+ /* @__PURE__ */ jsx(Label, { htmlFor: "visibility-toggle", className: "text-ui-fg-muted text-sm cursor-pointer", children: isPublished ? "Visible on storefront" : "Hidden from storefront" })
337
+ ] })
338
+ ] }),
339
+ product.description && /* @__PURE__ */ jsx(Text, { className: "mt-3", children: product.description })
340
+ ] }),
341
+ ((_a = product.images) == null ? void 0 : _a.length) > 0 && /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
342
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4", children: "Images" }),
343
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: product.images.map((img, i) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
344
+ /* @__PURE__ */ jsx(
345
+ "img",
346
+ {
347
+ src: img.src,
348
+ alt: `${product.title} - ${img.position}`,
349
+ className: "w-full aspect-square rounded-lg object-cover"
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsx(
353
+ Badge,
354
+ {
355
+ color: "grey",
356
+ size: "2xsmall",
357
+ className: "absolute bottom-2 left-2",
358
+ children: img.position
359
+ }
360
+ )
361
+ ] }, i)) })
362
+ ] }),
363
+ /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
364
+ /* @__PURE__ */ jsxs(Heading, { level: "h2", className: "mb-4", children: [
365
+ "Variants (",
366
+ ((_b = product.variants) == null ? void 0 : _b.length) ?? 0,
367
+ ")"
368
+ ] }),
369
+ /* @__PURE__ */ jsxs(Table, { children: [
370
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
371
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Title" }),
372
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "SKU" }),
373
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Cost" }),
374
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Price" }),
375
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" })
376
+ ] }) }),
377
+ /* @__PURE__ */ jsx(Table.Body, { children: (product.variants ?? []).map((v) => /* @__PURE__ */ jsxs(Table.Row, { children: [
378
+ /* @__PURE__ */ jsx(Table.Cell, { children: v.title }),
379
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("code", { className: "text-xs", children: v.sku }) }),
380
+ /* @__PURE__ */ jsx(Table.Cell, { children: formatCents(v.cost) }),
381
+ /* @__PURE__ */ jsx(Table.Cell, { children: formatCents(v.price) }),
382
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
383
+ Badge,
384
+ {
385
+ color: v.is_enabled ? "green" : "grey",
386
+ size: "2xsmall",
387
+ children: v.is_enabled ? "Enabled" : "Disabled"
388
+ }
389
+ ) })
390
+ ] }, v.id)) })
391
+ ] })
392
+ ] }),
393
+ /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
394
+ /* @__PURE__ */ jsx(Heading, { level: "h2", className: "mb-4", children: "Medusa Product" }),
395
+ medusaProductId ? /* @__PURE__ */ jsxs(
396
+ Link,
397
+ {
398
+ to: `/products/${medusaProductId}`,
399
+ className: "flex items-center gap-2 text-ui-fg-interactive hover:text-ui-fg-interactive-hover text-sm no-underline",
400
+ children: [
401
+ "View linked product",
402
+ /* @__PURE__ */ jsx(ArrowUpRightOnBox, { className: "w-4 h-4" })
403
+ ]
404
+ }
405
+ ) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted", children: "No linked Medusa product" })
406
+ ] }),
407
+ product.printify_data && /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
408
+ /* @__PURE__ */ jsxs(
409
+ "button",
410
+ {
411
+ onClick: () => setRawOpen(!rawOpen),
412
+ className: "flex items-center gap-2 w-full",
413
+ children: [
414
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Raw Printify Data" }),
415
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted", children: rawOpen ? "Hide" : "Show" })
416
+ ]
417
+ }
418
+ ),
419
+ rawOpen && /* @__PURE__ */ jsx("pre", { className: "mt-4 p-4 bg-ui-bg-subtle rounded-lg text-xs overflow-auto max-h-96", children: JSON.stringify(product.printify_data, null, 2) })
420
+ ] })
421
+ ] });
422
+ };
423
+ const i18nTranslations0 = {};
424
+ const widgetModule = { widgets: [
425
+ {
426
+ Component: PrintifyOrderWidget,
427
+ zone: ["order.details.after"]
428
+ }
429
+ ] };
430
+ const routeModule = {
431
+ routes: [
432
+ {
433
+ Component: PrintifyPage,
434
+ path: "/printify"
435
+ },
436
+ {
437
+ Component: PrintifyProductsPage,
438
+ path: "/printify/products"
439
+ },
440
+ {
441
+ Component: PrintifyProductDetailPage,
442
+ path: "/printify/products/:id"
443
+ }
444
+ ]
445
+ };
446
+ const menuItemModule = {
447
+ menuItems: [
448
+ {
449
+ label: config.label,
450
+ icon: config.icon,
451
+ path: "/printify",
452
+ nested: void 0,
453
+ rank: void 0,
454
+ translationNs: void 0
455
+ }
456
+ ]
457
+ };
458
+ const formModule = { customFields: {} };
459
+ const displayModule = {
460
+ displays: {}
461
+ };
462
+ const i18nModule = { resources: i18nTranslations0 };
463
+ const plugin = {
464
+ widgetModule,
465
+ routeModule,
466
+ menuItemModule,
467
+ formModule,
468
+ displayModule,
469
+ i18nModule
470
+ };
471
+ export {
472
+ plugin as default
473
+ };
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ async function GET(req, res) {
5
+ res.sendStatus(200);
6
+ }
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3BsdWdpbi9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUVBLGtCQUtDO0FBTE0sS0FBSyxVQUFVLEdBQUcsQ0FDdkIsR0FBa0IsRUFDbEIsR0FBbUI7SUFFbkIsR0FBRyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUN0QixDQUFDIn0=
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const printify_1 = require("../../../../../../modules/printify");
5
+ const submit_printify_order_1 = require("../../../../../../workflows/submit-printify-order");
6
+ // POST /admin/printify/orders/:id/submit — manually submit a pending order
7
+ async function POST(req, res) {
8
+ const { id } = req.params;
9
+ const service = req.scope.resolve(printify_1.PRINTIFY_MODULE);
10
+ const { shopId } = service.getOptions();
11
+ if (!shopId) {
12
+ return res.status(400).json({ error: "No shopId configured" });
13
+ }
14
+ const { result } = await (0, submit_printify_order_1.submitPrintifyOrderWorkflow)(req.scope).run({
15
+ input: { localOrderId: id, shopId },
16
+ });
17
+ res.json(result);
18
+ }
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50aWZ5L29yZGVycy9baWRdL3N1Ym1pdC9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQU1BLG9CQWNDO0FBbkJELGlFQUFvRTtBQUVwRSw2RkFBK0Y7QUFFL0YsMkVBQTJFO0FBQ3BFLEtBQUssVUFBVSxJQUFJLENBQUMsR0FBa0IsRUFBRSxHQUFtQjtJQUNoRSxNQUFNLEVBQUUsRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQTtJQUN6QixNQUFNLE9BQU8sR0FBMEIsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBQ3pFLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxPQUFPLENBQUMsVUFBVSxFQUFFLENBQUE7SUFFdkMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ1osT0FBTyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxzQkFBc0IsRUFBRSxDQUFDLENBQUE7SUFDaEUsQ0FBQztJQUVELE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsbURBQTJCLEVBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQztRQUNsRSxLQUFLLEVBQUUsRUFBRSxZQUFZLEVBQUUsRUFBRSxFQUFFLE1BQU0sRUFBRTtLQUNwQyxDQUFDLENBQUE7SUFFRixHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO0FBQ2xCLENBQUMifQ==
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const printify_1 = require("../../../../modules/printify");
5
+ // GET /admin/printify/orders
6
+ async function GET(req, res) {
7
+ const service = req.scope.resolve(printify_1.PRINTIFY_MODULE);
8
+ const { status, medusa_order_id, limit = "50", offset = "0" } = req.query;
9
+ const filters = {};
10
+ if (status)
11
+ filters.status = status;
12
+ if (medusa_order_id)
13
+ filters.medusa_order_id = medusa_order_id;
14
+ const orders = await service.listPrintifyOrders(filters);
15
+ const paginated = orders.slice(Number(offset), Number(offset) + Number(limit));
16
+ res.json({ orders: paginated, count: orders.length });
17
+ }
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50aWZ5L29yZGVycy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUtBLGtCQWVDO0FBbkJELDJEQUE4RDtBQUc5RCw2QkFBNkI7QUFDdEIsS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sT0FBTyxHQUEwQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDekUsTUFBTSxFQUFFLE1BQU0sRUFBRSxlQUFlLEVBQUUsS0FBSyxHQUFHLElBQUksRUFBRSxNQUFNLEdBQUcsR0FBRyxFQUFFLEdBQUcsR0FBRyxDQUFDLEtBR25FLENBQUE7SUFFRCxNQUFNLE9BQU8sR0FBNEIsRUFBRSxDQUFBO0lBQzNDLElBQUksTUFBTTtRQUFFLE9BQU8sQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFBO0lBQ25DLElBQUksZUFBZTtRQUFFLE9BQU8sQ0FBQyxlQUFlLEdBQUcsZUFBZSxDQUFBO0lBRTlELE1BQU0sTUFBTSxHQUFHLE1BQU0sT0FBTyxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ3hELE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtJQUU5RSxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUE7QUFDdkQsQ0FBQyJ9
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ exports.PATCH = PATCH;
5
+ const utils_1 = require("@medusajs/framework/utils");
6
+ const printify_1 = require("../../../../../modules/printify");
7
+ // GET /admin/printify/products/:id — get a single product with Medusa link
8
+ async function GET(req, res) {
9
+ const service = req.scope.resolve(printify_1.PRINTIFY_MODULE);
10
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
11
+ const product = await service.retrievePrintifyProduct(req.params.id);
12
+ let medusa_product_id = null;
13
+ try {
14
+ const { data } = await query.graph({
15
+ entity: "printify_product",
16
+ fields: ["product.id"],
17
+ filters: { id: product.id },
18
+ });
19
+ if (data.length > 0 && data[0].product?.id) {
20
+ medusa_product_id = data[0].product.id;
21
+ }
22
+ }
23
+ catch {
24
+ // Link module not configured or query failed — return null
25
+ }
26
+ res.json({ product, medusa_product_id });
27
+ }
28
+ /**
29
+ * PATCH /admin/printify/products/:id
30
+ *
31
+ * Toggles storefront visibility for a Printify product. Setting `is_published`
32
+ * also sets `visibility_override` to a non-null value, which prevents the sync
33
+ * job from overwriting this value in the future.
34
+ *
35
+ * NOTE: Once visibility is manually toggled, there is intentionally no UI path
36
+ * to reset back to "follow Printify" (visibility_override = null). To restore
37
+ * automatic sync behaviour, update `visibility_override` to null directly in
38
+ * the database or via a future admin action.
39
+ */
40
+ async function PATCH(req, res) {
41
+ const { is_published } = req.body;
42
+ if (typeof is_published !== "boolean") {
43
+ return res.status(400).json({ message: "is_published must be a boolean" });
44
+ }
45
+ const service = req.scope.resolve(printify_1.PRINTIFY_MODULE);
46
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
47
+ // Verify the product exists first
48
+ let existingProduct;
49
+ try {
50
+ existingProduct = await service.retrievePrintifyProduct(req.params.id);
51
+ }
52
+ catch {
53
+ return res.status(404).json({ error: "Product not found" });
54
+ }
55
+ // Now update (product is known to exist)
56
+ let product;
57
+ try {
58
+ ;
59
+ [product] = await service.updatePrintifyProducts({
60
+ selector: { id: req.params.id },
61
+ data: {
62
+ is_published,
63
+ // Set visibility_override to a boolean to mark this as an admin-set value (non-null = do not sync-overwrite)
64
+ visibility_override: is_published,
65
+ },
66
+ });
67
+ }
68
+ catch (err) {
69
+ return res.status(500).json({ error: "Failed to update product" });
70
+ }
71
+ try {
72
+ const { data } = await query.graph({
73
+ entity: "printify_product",
74
+ fields: ["product.id"],
75
+ filters: { id: product.id },
76
+ });
77
+ if (data.length > 0 && data[0].product?.id) {
78
+ const medusaProductId = data[0].product.id;
79
+ const productModuleService = req.scope.resolve("product");
80
+ await productModuleService.updateProducts(medusaProductId, { status: is_published ? "published" : "draft" });
81
+ // When publishing, ensure the Medusa product is assigned to a sales channel
82
+ if (is_published) {
83
+ let salesChannelId = null;
84
+ // Try shop-level sales channel first
85
+ const shops = await service.listPrintifyShops({ printify_id: product.shop_id });
86
+ salesChannelId = shops[0]?.sales_channel_id ?? null;
87
+ // Fall back to store default
88
+ if (!salesChannelId) {
89
+ const storeService = req.scope.resolve(utils_1.Modules.STORE);
90
+ const stores = await storeService.listStores({});
91
+ salesChannelId = stores[0]?.default_sales_channel_id ?? null;
92
+ }
93
+ if (salesChannelId) {
94
+ const link = req.scope.resolve(utils_1.ContainerRegistrationKeys.LINK);
95
+ try {
96
+ await link.create({
97
+ [utils_1.Modules.PRODUCT]: { product_id: medusaProductId },
98
+ [utils_1.Modules.SALES_CHANNEL]: { sales_channel_id: salesChannelId },
99
+ });
100
+ }
101
+ catch {
102
+ // Link may already exist — that's fine
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ catch {
109
+ // Link module not configured or Medusa product update failed — continue
110
+ }
111
+ res.json({ product });
112
+ }
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50aWZ5L3Byb2R1Y3RzL1tpZF0vcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFPQSxrQkFxQkM7QUFjRCxzQkE4RUM7QUF2SEQscURBQThFO0FBRTlFLDhEQUFpRTtBQUdqRSwyRUFBMkU7QUFDcEUsS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sT0FBTyxHQUEwQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDekUsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsS0FBSyxDQUFDLENBQUE7SUFFaEUsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsdUJBQXVCLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQTtJQUVwRSxJQUFJLGlCQUFpQixHQUFrQixJQUFJLENBQUE7SUFDM0MsSUFBSSxDQUFDO1FBQ0gsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLE1BQU0sS0FBSyxDQUFDLEtBQUssQ0FBQztZQUNqQyxNQUFNLEVBQUUsa0JBQWtCO1lBQzFCLE1BQU0sRUFBRSxDQUFDLFlBQVksQ0FBQztZQUN0QixPQUFPLEVBQUUsRUFBRSxFQUFFLEVBQUUsT0FBTyxDQUFDLEVBQUUsRUFBRTtTQUM1QixDQUFDLENBQUE7UUFDRixJQUFJLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxFQUFFLENBQUM7WUFDM0MsaUJBQWlCLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUE7UUFDeEMsQ0FBQztJQUNILENBQUM7SUFBQyxNQUFN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