@legenki/print2medusa 0.8.2 → 0.9.3

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.
@@ -2,8 +2,48 @@
2
2
  const jsxRuntime = require("react/jsx-runtime");
3
3
  const adminSdk = require("@medusajs/admin-sdk");
4
4
  const ui = require("@medusajs/ui");
5
- const utils = require("@medusajs/framework/utils");
6
5
  const react = require("react");
6
+ const ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
7
+ "AFN",
8
+ "ALL",
9
+ "AMD",
10
+ "BIF",
11
+ "CLP",
12
+ "COP",
13
+ "CRC",
14
+ "DJF",
15
+ "GNF",
16
+ "HUF",
17
+ "IDR",
18
+ "IQD",
19
+ "IRR",
20
+ "IRT",
21
+ "ISK",
22
+ "JPY",
23
+ "KMF",
24
+ "KRW",
25
+ "LBP",
26
+ "MGA",
27
+ "MMK",
28
+ "MNT",
29
+ "MUR",
30
+ "PKR",
31
+ "PYG",
32
+ "RSD",
33
+ "RWF",
34
+ "SOS",
35
+ "SYP",
36
+ "TZS",
37
+ "UGX",
38
+ "UZS",
39
+ "VND",
40
+ "XAF",
41
+ "XOF",
42
+ "XPF",
43
+ "YER",
44
+ "ZMK",
45
+ "ZWL"
46
+ ]);
7
47
  function minorUnitFactor(currencyCode) {
8
48
  return isZeroDecimalCurrency(currencyCode) ? 1 : 100;
9
49
  }
@@ -11,8 +51,7 @@ function isZeroDecimalCurrency(currencyCode) {
11
51
  if (!currencyCode) {
12
52
  return false;
13
53
  }
14
- const entry = utils.defaultCurrencies[currencyCode.toUpperCase()];
15
- return (entry == null ? void 0 : entry.decimal_digits) === 0;
54
+ return ZERO_DECIMAL_CURRENCIES.has(currencyCode.toUpperCase());
16
55
  }
17
56
  const MONEY_SCALE_VERSION = 2;
18
57
  const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
@@ -307,6 +346,16 @@ const PrintfulSyncWidget = () => {
307
346
  adminSdk.defineWidgetConfig({
308
347
  zone: "product.list.before"
309
348
  });
349
+ const CLASS_LABEL = {
350
+ apparel: "Apparel",
351
+ embroidery: "Embroidered",
352
+ print_media: "Print media"
353
+ };
354
+ const CLASS_COLOUR = {
355
+ apparel: "blue",
356
+ embroidery: "orange",
357
+ print_media: "green"
358
+ };
310
359
  const STATUS_COLOUR = {
311
360
  success: "green",
312
361
  running: "orange",
@@ -334,14 +383,22 @@ const PrintfulPage = () => {
334
383
  const [syncs, setSyncs] = react.useState([]);
335
384
  const [health, setHealth] = react.useState(null);
336
385
  const [stats, setStats] = react.useState(null);
386
+ const [designs, setDesigns] = react.useState([]);
387
+ const [bundles, setBundles] = react.useState([]);
388
+ const [promptProducts, setPromptProducts] = react.useState([]);
389
+ const [styles, setStyles] = react.useState([]);
390
+ const [style, setStyle] = react.useState("editorial");
391
+ const [artwork, setArtwork] = react.useState("");
337
392
  const [loading, setLoading] = react.useState(true);
338
393
  const [clearing, setClearing] = react.useState(false);
339
394
  const load = react.useCallback(async () => {
340
395
  var _a2;
341
- const [h, w, s] = await Promise.allSettled([
396
+ const [h, w, s, d, b] = await Promise.allSettled([
342
397
  fetch("/admin/printful/history", { credentials: "include" }),
343
398
  fetch("/admin/printful/health", { credentials: "include" }),
344
- fetch("/admin/printful/statistics", { credentials: "include" })
399
+ fetch("/admin/printful/statistics", { credentials: "include" }),
400
+ fetch("/admin/printful/design", { credentials: "include" }),
401
+ fetch("/admin/printful/bundles", { credentials: "include" })
345
402
  ]);
346
403
  if (h.status === "fulfilled" && h.value.ok) {
347
404
  const body = await h.value.json();
@@ -354,11 +411,40 @@ const PrintfulPage = () => {
354
411
  const body = await s.value.json();
355
412
  setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
356
413
  }
414
+ if (d.status === "fulfilled" && d.value.ok) {
415
+ const body = await d.value.json();
416
+ setDesigns(body.designs ?? []);
417
+ }
418
+ if (b.status === "fulfilled" && b.value.ok) {
419
+ const body = await b.value.json();
420
+ setBundles(body.bundles ?? []);
421
+ }
357
422
  setLoading(false);
358
423
  }, []);
424
+ const loadPrompts = react.useCallback(async () => {
425
+ const params = new URLSearchParams({ style });
426
+ if (artwork.trim()) {
427
+ params.set("artwork", artwork.trim());
428
+ }
429
+ try {
430
+ const res = await fetch(`/admin/printful/prompts?${params}`, {
431
+ credentials: "include"
432
+ });
433
+ if (!res.ok) {
434
+ return;
435
+ }
436
+ const body = await res.json();
437
+ setPromptProducts(body.products ?? []);
438
+ setStyles(body.styles ?? []);
439
+ } catch {
440
+ }
441
+ }, [style, artwork]);
359
442
  react.useEffect(() => {
360
443
  void load();
361
444
  }, [load]);
445
+ react.useEffect(() => {
446
+ void loadPrompts();
447
+ }, [loadPrompts]);
362
448
  const latest = syncs[0];
363
449
  const running = (latest == null ? void 0 : latest.status) === "running";
364
450
  react.useEffect(() => {
@@ -472,6 +558,115 @@ const PrintfulPage = () => {
472
558
  ] })
473
559
  ] })
474
560
  ] }),
561
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
562
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Design parameters" }),
563
+ designs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No products carry design parameters yet. Run a sync." }) : designs.map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
564
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
565
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: d.title }),
566
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: CLASS_COLOUR[d.product_class] ?? "grey", children: CLASS_LABEL[d.product_class] ?? d.product_class })
567
+ ] }),
568
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
569
+ d.technique ?? "—",
570
+ d.brand ? ` · ${d.brand}${d.model ? ` ${d.model}` : ""}` : "",
571
+ d.material ? ` · ${d.material}` : ""
572
+ ] }),
573
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
574
+ "Design goes on: ",
575
+ d.core_placements.join(", ") || "—",
576
+ d.placements.length > d.core_placements.length ? ` (${d.placements.length - d.core_placements.length} more available)` : ""
577
+ ] }),
578
+ d.colors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Base colours:" }),
580
+ d.colors.slice(0, 12).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
581
+ "span",
582
+ {
583
+ title: `${c.name}${c.hex ? ` ${c.hex}` : ""}`,
584
+ className: "inline-block h-4 w-4 rounded border border-ui-border-base",
585
+ style: c.hex ? { backgroundColor: c.hex } : void 0
586
+ },
587
+ c.name
588
+ )),
589
+ d.colors.length > 12 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
590
+ "+",
591
+ d.colors.length - 12
592
+ ] })
593
+ ] }),
594
+ d.sizes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
595
+ d.product_class === "print_media" ? "Sizes" : "Sizes",
596
+ ":",
597
+ " ",
598
+ d.sizes.slice(0, 8).join(", "),
599
+ d.sizes.length > 8 ? ` +${d.sizes.length - 8}` : ""
600
+ ] })
601
+ ] }, d.product_id))
602
+ ] }),
603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
604
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Bundles" }),
605
+ bundles.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No bundles yet. Add printful_bundle_members to a variant's metadata to make it a bundle." }) : bundles.map((b) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
607
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: b.title }),
608
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: b.available ? "green" : "red", children: b.available ? "all members in stock" : "member unavailable" }),
609
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: b.status === "published" ? "green" : "grey", children: b.status }),
610
+ b.missing_count > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "orange", children: [
611
+ b.missing_count,
612
+ " member",
613
+ b.missing_count === 1 ? "" : "s",
614
+ " not found"
615
+ ] })
616
+ ] }),
617
+ b.members.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
618
+ ui.Text,
619
+ {
620
+ size: "small",
621
+ className: "text-ui-fg-subtle pl-3",
622
+ children: [
623
+ m.quantity,
624
+ "× ",
625
+ m.product_title ?? "—",
626
+ m.title ? ` · ${m.title}` : "",
627
+ m.status === "missing" ? " — variant not found" : m.status === "active" || m.status === "unknown" ? "" : ` — ${m.status.replace(/_/g, " ")}`
628
+ ]
629
+ },
630
+ m.variant_id
631
+ ))
632
+ ] }, b.variant_id))
633
+ ] }),
634
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
635
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Mockup prompts" }),
636
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Paste these into an image model. Printful’s own generator renders a product on a plain background — right for a catalogue thumbnail, not for an editorial mockup." }),
637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
638
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: style, onValueChange: setStyle, children: [
639
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { className: "w-40", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Style" }) }),
640
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: styles.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: s.id, children: s.label }, s.id)) })
641
+ ] }),
642
+ /* @__PURE__ */ jsxRuntime.jsx(
643
+ ui.Input,
644
+ {
645
+ className: "flex-1 min-w-64",
646
+ placeholder: "What is the artwork? (optional)",
647
+ value: artwork,
648
+ onChange: (e) => setArtwork(e.target.value)
649
+ }
650
+ )
651
+ ] }),
652
+ promptProducts.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No prompts yet. Run a sync so products carry design parameters." }) : promptProducts.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
653
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.title }),
654
+ p.prompts.map((prompt, i) => /* @__PURE__ */ jsxRuntime.jsxs(
655
+ "div",
656
+ {
657
+ className: "flex items-start gap-2 rounded-md border border-ui-border-base p-2",
658
+ children: [
659
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
660
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: prompt.label }),
661
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: prompt.text })
662
+ ] }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: prompt.text })
664
+ ]
665
+ },
666
+ `${p.product_id}-${i}`
667
+ ))
668
+ ] }, p.product_id))
669
+ ] }),
475
670
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
476
671
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Recent syncs" }),
477
672
  syncs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No syncs yet." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
@@ -1,8 +1,48 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Badge, Text, Button, toast, Prompt, Table } from "@medusajs/ui";
4
- import { defaultCurrencies } from "@medusajs/framework/utils";
3
+ import { Container, Heading, Badge, Text, Button, toast, Prompt, Select, Input, Copy, Table } from "@medusajs/ui";
5
4
  import { useState, useEffect, useCallback } from "react";
5
+ const ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
6
+ "AFN",
7
+ "ALL",
8
+ "AMD",
9
+ "BIF",
10
+ "CLP",
11
+ "COP",
12
+ "CRC",
13
+ "DJF",
14
+ "GNF",
15
+ "HUF",
16
+ "IDR",
17
+ "IQD",
18
+ "IRR",
19
+ "IRT",
20
+ "ISK",
21
+ "JPY",
22
+ "KMF",
23
+ "KRW",
24
+ "LBP",
25
+ "MGA",
26
+ "MMK",
27
+ "MNT",
28
+ "MUR",
29
+ "PKR",
30
+ "PYG",
31
+ "RSD",
32
+ "RWF",
33
+ "SOS",
34
+ "SYP",
35
+ "TZS",
36
+ "UGX",
37
+ "UZS",
38
+ "VND",
39
+ "XAF",
40
+ "XOF",
41
+ "XPF",
42
+ "YER",
43
+ "ZMK",
44
+ "ZWL"
45
+ ]);
6
46
  function minorUnitFactor(currencyCode) {
7
47
  return isZeroDecimalCurrency(currencyCode) ? 1 : 100;
8
48
  }
@@ -10,8 +50,7 @@ function isZeroDecimalCurrency(currencyCode) {
10
50
  if (!currencyCode) {
11
51
  return false;
12
52
  }
13
- const entry = defaultCurrencies[currencyCode.toUpperCase()];
14
- return (entry == null ? void 0 : entry.decimal_digits) === 0;
53
+ return ZERO_DECIMAL_CURRENCIES.has(currencyCode.toUpperCase());
15
54
  }
16
55
  const MONEY_SCALE_VERSION = 2;
17
56
  const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
@@ -306,6 +345,16 @@ const PrintfulSyncWidget = () => {
306
345
  defineWidgetConfig({
307
346
  zone: "product.list.before"
308
347
  });
348
+ const CLASS_LABEL = {
349
+ apparel: "Apparel",
350
+ embroidery: "Embroidered",
351
+ print_media: "Print media"
352
+ };
353
+ const CLASS_COLOUR = {
354
+ apparel: "blue",
355
+ embroidery: "orange",
356
+ print_media: "green"
357
+ };
309
358
  const STATUS_COLOUR = {
310
359
  success: "green",
311
360
  running: "orange",
@@ -333,14 +382,22 @@ const PrintfulPage = () => {
333
382
  const [syncs, setSyncs] = useState([]);
334
383
  const [health, setHealth] = useState(null);
335
384
  const [stats, setStats] = useState(null);
385
+ const [designs, setDesigns] = useState([]);
386
+ const [bundles, setBundles] = useState([]);
387
+ const [promptProducts, setPromptProducts] = useState([]);
388
+ const [styles, setStyles] = useState([]);
389
+ const [style, setStyle] = useState("editorial");
390
+ const [artwork, setArtwork] = useState("");
336
391
  const [loading, setLoading] = useState(true);
337
392
  const [clearing, setClearing] = useState(false);
338
393
  const load = useCallback(async () => {
339
394
  var _a2;
340
- const [h, w, s] = await Promise.allSettled([
395
+ const [h, w, s, d, b] = await Promise.allSettled([
341
396
  fetch("/admin/printful/history", { credentials: "include" }),
342
397
  fetch("/admin/printful/health", { credentials: "include" }),
343
- fetch("/admin/printful/statistics", { credentials: "include" })
398
+ fetch("/admin/printful/statistics", { credentials: "include" }),
399
+ fetch("/admin/printful/design", { credentials: "include" }),
400
+ fetch("/admin/printful/bundles", { credentials: "include" })
344
401
  ]);
345
402
  if (h.status === "fulfilled" && h.value.ok) {
346
403
  const body = await h.value.json();
@@ -353,11 +410,40 @@ const PrintfulPage = () => {
353
410
  const body = await s.value.json();
354
411
  setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
355
412
  }
413
+ if (d.status === "fulfilled" && d.value.ok) {
414
+ const body = await d.value.json();
415
+ setDesigns(body.designs ?? []);
416
+ }
417
+ if (b.status === "fulfilled" && b.value.ok) {
418
+ const body = await b.value.json();
419
+ setBundles(body.bundles ?? []);
420
+ }
356
421
  setLoading(false);
357
422
  }, []);
423
+ const loadPrompts = useCallback(async () => {
424
+ const params = new URLSearchParams({ style });
425
+ if (artwork.trim()) {
426
+ params.set("artwork", artwork.trim());
427
+ }
428
+ try {
429
+ const res = await fetch(`/admin/printful/prompts?${params}`, {
430
+ credentials: "include"
431
+ });
432
+ if (!res.ok) {
433
+ return;
434
+ }
435
+ const body = await res.json();
436
+ setPromptProducts(body.products ?? []);
437
+ setStyles(body.styles ?? []);
438
+ } catch {
439
+ }
440
+ }, [style, artwork]);
358
441
  useEffect(() => {
359
442
  void load();
360
443
  }, [load]);
444
+ useEffect(() => {
445
+ void loadPrompts();
446
+ }, [loadPrompts]);
361
447
  const latest = syncs[0];
362
448
  const running = (latest == null ? void 0 : latest.status) === "running";
363
449
  useEffect(() => {
@@ -471,6 +557,115 @@ const PrintfulPage = () => {
471
557
  ] })
472
558
  ] })
473
559
  ] }),
560
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
561
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Design parameters" }),
562
+ designs.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No products carry design parameters yet. Run a sync." }) : designs.map((d) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
563
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
564
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: d.title }),
565
+ /* @__PURE__ */ jsx(Badge, { color: CLASS_COLOUR[d.product_class] ?? "grey", children: CLASS_LABEL[d.product_class] ?? d.product_class })
566
+ ] }),
567
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
568
+ d.technique ?? "—",
569
+ d.brand ? ` · ${d.brand}${d.model ? ` ${d.model}` : ""}` : "",
570
+ d.material ? ` · ${d.material}` : ""
571
+ ] }),
572
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
573
+ "Design goes on: ",
574
+ d.core_placements.join(", ") || "—",
575
+ d.placements.length > d.core_placements.length ? ` (${d.placements.length - d.core_placements.length} more available)` : ""
576
+ ] }),
577
+ d.colors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
578
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Base colours:" }),
579
+ d.colors.slice(0, 12).map((c) => /* @__PURE__ */ jsx(
580
+ "span",
581
+ {
582
+ title: `${c.name}${c.hex ? ` ${c.hex}` : ""}`,
583
+ className: "inline-block h-4 w-4 rounded border border-ui-border-base",
584
+ style: c.hex ? { backgroundColor: c.hex } : void 0
585
+ },
586
+ c.name
587
+ )),
588
+ d.colors.length > 12 && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
589
+ "+",
590
+ d.colors.length - 12
591
+ ] })
592
+ ] }),
593
+ d.sizes.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
594
+ d.product_class === "print_media" ? "Sizes" : "Sizes",
595
+ ":",
596
+ " ",
597
+ d.sizes.slice(0, 8).join(", "),
598
+ d.sizes.length > 8 ? ` +${d.sizes.length - 8}` : ""
599
+ ] })
600
+ ] }, d.product_id))
601
+ ] }),
602
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
603
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Bundles" }),
604
+ bundles.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No bundles yet. Add printful_bundle_members to a variant's metadata to make it a bundle." }) : bundles.map((b) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
605
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
606
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: b.title }),
607
+ /* @__PURE__ */ jsx(Badge, { color: b.available ? "green" : "red", children: b.available ? "all members in stock" : "member unavailable" }),
608
+ /* @__PURE__ */ jsx(Badge, { color: b.status === "published" ? "green" : "grey", children: b.status }),
609
+ b.missing_count > 0 && /* @__PURE__ */ jsxs(Badge, { color: "orange", children: [
610
+ b.missing_count,
611
+ " member",
612
+ b.missing_count === 1 ? "" : "s",
613
+ " not found"
614
+ ] })
615
+ ] }),
616
+ b.members.map((m) => /* @__PURE__ */ jsxs(
617
+ Text,
618
+ {
619
+ size: "small",
620
+ className: "text-ui-fg-subtle pl-3",
621
+ children: [
622
+ m.quantity,
623
+ "× ",
624
+ m.product_title ?? "—",
625
+ m.title ? ` · ${m.title}` : "",
626
+ m.status === "missing" ? " — variant not found" : m.status === "active" || m.status === "unknown" ? "" : ` — ${m.status.replace(/_/g, " ")}`
627
+ ]
628
+ },
629
+ m.variant_id
630
+ ))
631
+ ] }, b.variant_id))
632
+ ] }),
633
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
634
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Mockup prompts" }),
635
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Paste these into an image model. Printful’s own generator renders a product on a plain background — right for a catalogue thumbnail, not for an editorial mockup." }),
636
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
637
+ /* @__PURE__ */ jsxs(Select, { value: style, onValueChange: setStyle, children: [
638
+ /* @__PURE__ */ jsx(Select.Trigger, { className: "w-40", children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Style" }) }),
639
+ /* @__PURE__ */ jsx(Select.Content, { children: styles.map((s) => /* @__PURE__ */ jsx(Select.Item, { value: s.id, children: s.label }, s.id)) })
640
+ ] }),
641
+ /* @__PURE__ */ jsx(
642
+ Input,
643
+ {
644
+ className: "flex-1 min-w-64",
645
+ placeholder: "What is the artwork? (optional)",
646
+ value: artwork,
647
+ onChange: (e) => setArtwork(e.target.value)
648
+ }
649
+ )
650
+ ] }),
651
+ promptProducts.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No prompts yet. Run a sync so products carry design parameters." }) : promptProducts.map((p) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
652
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.title }),
653
+ p.prompts.map((prompt, i) => /* @__PURE__ */ jsxs(
654
+ "div",
655
+ {
656
+ className: "flex items-start gap-2 rounded-md border border-ui-border-base p-2",
657
+ children: [
658
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
659
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-muted", children: prompt.label }),
660
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: prompt.text })
661
+ ] }),
662
+ /* @__PURE__ */ jsx(Copy, { content: prompt.text })
663
+ ]
664
+ },
665
+ `${p.product_id}-${i}`
666
+ ))
667
+ ] }, p.product_id))
668
+ ] }),
474
669
  /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
475
670
  /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Recent syncs" }),
476
671
  syncs.length === 0 ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: loading ? "Loading…" : "No syncs yet." }) : /* @__PURE__ */ jsxs(Table, { children: [
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const bundle_1 = require("../../../../utils/bundle");
6
+ /**
7
+ * Bundles and whether each can currently be sold.
8
+ *
9
+ * A pure read of the database — no Printful call. Availability comes from the
10
+ * `printful_availability_status` the sync already stamped on every member
11
+ * variant, which is the same source the sync's own bundle pass decides on. A
12
+ * live lookup here could disagree with the status the catalogue was last
13
+ * published against, and show the owner a bundle as sellable that the next
14
+ * sync is about to draft.
15
+ *
16
+ * Every member is listed, available or not, because the question the owner has
17
+ * when a bundle goes off sale is *which* member ran out.
18
+ */
19
+ async function GET(req, res) {
20
+ const productModule = req.scope.resolve(utils_1.Modules.PRODUCT);
21
+ const products = await productModule.listProducts({}, { relations: ["variants"], take: 1000 });
22
+ // Members are variant ids on other products, so the whole catalogue has to
23
+ // be indexed before any bundle can be resolved.
24
+ const variantIndex = new Map();
25
+ for (const product of products) {
26
+ for (const variant of product.variants ?? []) {
27
+ const metadata = (variant.metadata ?? {});
28
+ variantIndex.set(variant.id, {
29
+ title: variant.title ?? variant.id,
30
+ product_title: product.title,
31
+ status: typeof metadata.printful_availability_status === "string"
32
+ ? metadata.printful_availability_status
33
+ : "unknown",
34
+ });
35
+ }
36
+ }
37
+ const bundles = [];
38
+ for (const product of products) {
39
+ for (const variant of product.variants ?? []) {
40
+ const members = (0, bundle_1.bundleMembersOf)({
41
+ id: variant.id,
42
+ quantity: 1,
43
+ metadata: (variant.metadata ?? {}),
44
+ });
45
+ if (!members.length) {
46
+ continue;
47
+ }
48
+ const resolved = members.map((member) => {
49
+ const found = variantIndex.get(member.variant_id);
50
+ return {
51
+ variant_id: member.variant_id,
52
+ quantity: member.quantity ?? 1,
53
+ // A member whose variant no longer exists is reported as such rather
54
+ // than hidden: it is the likeliest reason a bundle fails at Printful,
55
+ // and it is invisible everywhere else in the admin.
56
+ title: found?.title ?? null,
57
+ product_title: found?.product_title ?? null,
58
+ status: found?.status ?? "missing",
59
+ };
60
+ });
61
+ const availability = (0, bundle_1.planBundleAvailability)(resolved
62
+ .filter((m) => m.status !== "missing")
63
+ .map((m) => ({ printful_availability_status: m.status })));
64
+ bundles.push({
65
+ product_id: product.id,
66
+ variant_id: variant.id,
67
+ title: product.title,
68
+ variant_title: variant.title,
69
+ status: product.status,
70
+ available: availability.available,
71
+ member_count: resolved.length,
72
+ missing_count: resolved.filter((m) => m.status === "missing").length,
73
+ members: resolved,
74
+ });
75
+ }
76
+ }
77
+ res.status(200).json({ bundles });
78
+ }
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL2J1bmRsZXMvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFvQkEsa0JBMkVDO0FBOUZELHFEQUFtRDtBQUNuRCxxREFHaUM7QUFFakM7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0ksS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sYUFBYSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUV4RCxNQUFNLFFBQVEsR0FBRyxNQUFNLGFBQWEsQ0FBQyxZQUFZLENBQy9DLEVBQUUsRUFDRixFQUFFLFNBQVMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FDeEMsQ0FBQTtJQUVELDJFQUEyRTtJQUMzRSxnREFBZ0Q7SUFDaEQsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLEVBR3pCLENBQUE7SUFDSCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQy9CLEtBQUssTUFBTSxPQUFPLElBQUksT0FBTyxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUM3QyxNQUFNLFFBQVEsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLElBQUksRUFBRSxDQUE0QixDQUFBO1lBQ3BFLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsRUFBRTtnQkFDM0IsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLElBQUksT0FBTyxDQUFDLEVBQUU7Z0JBQ2xDLGFBQWEsRUFBRSxPQUFPLENBQUMsS0FBSztnQkFDNUIsTUFBTSxFQUNKLE9BQU8sUUFBUSxDQUFDLDRCQUE0QixLQUFLLFFBQVE7b0JBQ3ZELENBQUMsQ0FBQyxRQUFRLENBQUMsNEJBQTRCO29CQUN2QyxD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
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const design_1 = require("../../../../utils/design");
6
+ /**
7
+ * Design parameters per product, read from the variant metadata the sync
8
+ * stamped.
9
+ *
10
+ * A pure read of the database — no Printful call. The sync already paid for
11
+ * the catalog lookups, and this page is opened far more often than the
12
+ * catalogue changes.
13
+ *
14
+ * Products whose variants carry no design metadata are omitted rather than
15
+ * listed empty: one was never enriched, the other has nothing to show, and
16
+ * rendering them the same way tells the owner something false.
17
+ */
18
+ async function GET(req, res) {
19
+ const productModule = req.scope.resolve(utils_1.Modules.PRODUCT);
20
+ const products = await productModule.listProducts({}, { relations: ["variants"], take: 100 });
21
+ const designs = products
22
+ .map((product) => {
23
+ const summary = (0, design_1.summarizeProductDesign)(product.variants ?? []);
24
+ if (!summary) {
25
+ return null;
26
+ }
27
+ return {
28
+ product_id: product.id,
29
+ title: product.title,
30
+ status: product.status,
31
+ variant_count: (product.variants ?? []).length,
32
+ ...summary,
33
+ };
34
+ })
35
+ .filter((d) => d !== null);
36
+ res.status(200).json({ designs });
37
+ }
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL2Rlc2lnbi9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQWdCQSxrQkF5QkM7QUF4Q0QscURBQW1EO0FBQ25ELHFEQUFpRTtBQUVqRTs7Ozs7Ozs7Ozs7R0FXRztBQUNJLEtBQUssVUFBVSxHQUFHLENBQUMsR0FBa0IsRUFBRSxHQUFtQjtJQUMvRCxNQUFNLGFBQWEsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsT0FBTyxDQUFDLENBQUE7SUFFeEQsTUFBTSxRQUFRLEdBQUcsTUFBTSxhQUFhLENBQUMsWUFBWSxDQUMvQyxFQUFFLEVBQ0YsRUFBRSxTQUFTLEVBQUUsQ0FBQyxVQUFVLENBQUMsRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLENBQ3ZDLENBQUE7SUFFRCxNQUFNLE9BQU8sR0FBRyxRQUFRO1NBQ3JCLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQ2YsTUFBTSxPQUFPLEdBQUcsSUFBQSwrQkFBc0IsRUFBQyxPQUFPLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFBO1FBQzlELElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE9BQU8sSUFBSSxDQUFBO1FBQ2IsQ0FBQztRQUNELE9BQU87WUFDTCxVQUFVLEVBQUUsT0FBTyxDQUFDLEVBQUU7WUFDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO1lBQ3BCLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTTtZQUN0QixhQUFhLEVBQUUsQ0FBQyxPQUFPLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU07WUFDOUMsR0FBRyxPQUFPO1NBQ1gsQ0FBQTtJQUNILENBQUMsQ0FBQztTQUNELE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBOEIsRUFBRSxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQTtJQUV4RCxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUE7QUFDbkMsQ0FBQyJ9