@legenki/print2medusa 0.8.2 → 0.9.2

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.
@@ -307,6 +307,16 @@ const PrintfulSyncWidget = () => {
307
307
  adminSdk.defineWidgetConfig({
308
308
  zone: "product.list.before"
309
309
  });
310
+ const CLASS_LABEL = {
311
+ apparel: "Apparel",
312
+ embroidery: "Embroidered",
313
+ print_media: "Print media"
314
+ };
315
+ const CLASS_COLOUR = {
316
+ apparel: "blue",
317
+ embroidery: "orange",
318
+ print_media: "green"
319
+ };
310
320
  const STATUS_COLOUR = {
311
321
  success: "green",
312
322
  running: "orange",
@@ -334,14 +344,22 @@ const PrintfulPage = () => {
334
344
  const [syncs, setSyncs] = react.useState([]);
335
345
  const [health, setHealth] = react.useState(null);
336
346
  const [stats, setStats] = react.useState(null);
347
+ const [designs, setDesigns] = react.useState([]);
348
+ const [bundles, setBundles] = react.useState([]);
349
+ const [promptProducts, setPromptProducts] = react.useState([]);
350
+ const [styles, setStyles] = react.useState([]);
351
+ const [style, setStyle] = react.useState("editorial");
352
+ const [artwork, setArtwork] = react.useState("");
337
353
  const [loading, setLoading] = react.useState(true);
338
354
  const [clearing, setClearing] = react.useState(false);
339
355
  const load = react.useCallback(async () => {
340
356
  var _a2;
341
- const [h, w, s] = await Promise.allSettled([
357
+ const [h, w, s, d, b] = await Promise.allSettled([
342
358
  fetch("/admin/printful/history", { credentials: "include" }),
343
359
  fetch("/admin/printful/health", { credentials: "include" }),
344
- fetch("/admin/printful/statistics", { credentials: "include" })
360
+ fetch("/admin/printful/statistics", { credentials: "include" }),
361
+ fetch("/admin/printful/design", { credentials: "include" }),
362
+ fetch("/admin/printful/bundles", { credentials: "include" })
345
363
  ]);
346
364
  if (h.status === "fulfilled" && h.value.ok) {
347
365
  const body = await h.value.json();
@@ -354,11 +372,40 @@ const PrintfulPage = () => {
354
372
  const body = await s.value.json();
355
373
  setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
356
374
  }
375
+ if (d.status === "fulfilled" && d.value.ok) {
376
+ const body = await d.value.json();
377
+ setDesigns(body.designs ?? []);
378
+ }
379
+ if (b.status === "fulfilled" && b.value.ok) {
380
+ const body = await b.value.json();
381
+ setBundles(body.bundles ?? []);
382
+ }
357
383
  setLoading(false);
358
384
  }, []);
385
+ const loadPrompts = react.useCallback(async () => {
386
+ const params = new URLSearchParams({ style });
387
+ if (artwork.trim()) {
388
+ params.set("artwork", artwork.trim());
389
+ }
390
+ try {
391
+ const res = await fetch(`/admin/printful/prompts?${params}`, {
392
+ credentials: "include"
393
+ });
394
+ if (!res.ok) {
395
+ return;
396
+ }
397
+ const body = await res.json();
398
+ setPromptProducts(body.products ?? []);
399
+ setStyles(body.styles ?? []);
400
+ } catch {
401
+ }
402
+ }, [style, artwork]);
359
403
  react.useEffect(() => {
360
404
  void load();
361
405
  }, [load]);
406
+ react.useEffect(() => {
407
+ void loadPrompts();
408
+ }, [loadPrompts]);
362
409
  const latest = syncs[0];
363
410
  const running = (latest == null ? void 0 : latest.status) === "running";
364
411
  react.useEffect(() => {
@@ -472,6 +519,115 @@ const PrintfulPage = () => {
472
519
  ] })
473
520
  ] })
474
521
  ] }),
522
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
523
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Design parameters" }),
524
+ 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: [
525
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
526
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: d.title }),
527
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: CLASS_COLOUR[d.product_class] ?? "grey", children: CLASS_LABEL[d.product_class] ?? d.product_class })
528
+ ] }),
529
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
530
+ d.technique ?? "—",
531
+ d.brand ? ` · ${d.brand}${d.model ? ` ${d.model}` : ""}` : "",
532
+ d.material ? ` · ${d.material}` : ""
533
+ ] }),
534
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
535
+ "Design goes on: ",
536
+ d.core_placements.join(", ") || "—",
537
+ d.placements.length > d.core_placements.length ? ` (${d.placements.length - d.core_placements.length} more available)` : ""
538
+ ] }),
539
+ d.colors.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
540
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Base colours:" }),
541
+ d.colors.slice(0, 12).map((c) => /* @__PURE__ */ jsxRuntime.jsx(
542
+ "span",
543
+ {
544
+ title: `${c.name}${c.hex ? ` ${c.hex}` : ""}`,
545
+ className: "inline-block h-4 w-4 rounded border border-ui-border-base",
546
+ style: c.hex ? { backgroundColor: c.hex } : void 0
547
+ },
548
+ c.name
549
+ )),
550
+ d.colors.length > 12 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
551
+ "+",
552
+ d.colors.length - 12
553
+ ] })
554
+ ] }),
555
+ d.sizes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
556
+ d.product_class === "print_media" ? "Sizes" : "Sizes",
557
+ ":",
558
+ " ",
559
+ d.sizes.slice(0, 8).join(", "),
560
+ d.sizes.length > 8 ? ` +${d.sizes.length - 8}` : ""
561
+ ] })
562
+ ] }, d.product_id))
563
+ ] }),
564
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
565
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Bundles" }),
566
+ 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: [
567
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
568
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: b.title }),
569
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: b.available ? "green" : "red", children: b.available ? "all members in stock" : "member unavailable" }),
570
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: b.status === "published" ? "green" : "grey", children: b.status }),
571
+ b.missing_count > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "orange", children: [
572
+ b.missing_count,
573
+ " member",
574
+ b.missing_count === 1 ? "" : "s",
575
+ " not found"
576
+ ] })
577
+ ] }),
578
+ b.members.map((m) => /* @__PURE__ */ jsxRuntime.jsxs(
579
+ ui.Text,
580
+ {
581
+ size: "small",
582
+ className: "text-ui-fg-subtle pl-3",
583
+ children: [
584
+ m.quantity,
585
+ "× ",
586
+ m.product_title ?? "—",
587
+ m.title ? ` · ${m.title}` : "",
588
+ m.status === "missing" ? " — variant not found" : m.status === "active" || m.status === "unknown" ? "" : ` — ${m.status.replace(/_/g, " ")}`
589
+ ]
590
+ },
591
+ m.variant_id
592
+ ))
593
+ ] }, b.variant_id))
594
+ ] }),
595
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
596
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Mockup prompts" }),
597
+ /* @__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." }),
598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
599
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: style, onValueChange: setStyle, children: [
600
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { className: "w-40", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Style" }) }),
601
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: styles.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: s.id, children: s.label }, s.id)) })
602
+ ] }),
603
+ /* @__PURE__ */ jsxRuntime.jsx(
604
+ ui.Input,
605
+ {
606
+ className: "flex-1 min-w-64",
607
+ placeholder: "What is the artwork? (optional)",
608
+ value: artwork,
609
+ onChange: (e) => setArtwork(e.target.value)
610
+ }
611
+ )
612
+ ] }),
613
+ 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: [
614
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.title }),
615
+ p.prompts.map((prompt, i) => /* @__PURE__ */ jsxRuntime.jsxs(
616
+ "div",
617
+ {
618
+ className: "flex items-start gap-2 rounded-md border border-ui-border-base p-2",
619
+ children: [
620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
621
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: prompt.label }),
622
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: prompt.text })
623
+ ] }),
624
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: prompt.text })
625
+ ]
626
+ },
627
+ `${p.product_id}-${i}`
628
+ ))
629
+ ] }, p.product_id))
630
+ ] }),
475
631
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
476
632
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Recent syncs" }),
477
633
  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,6 +1,6 @@
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";
3
+ import { Container, Heading, Badge, Text, Button, toast, Prompt, Select, Input, Copy, Table } from "@medusajs/ui";
4
4
  import { defaultCurrencies } from "@medusajs/framework/utils";
5
5
  import { useState, useEffect, useCallback } from "react";
6
6
  function minorUnitFactor(currencyCode) {
@@ -306,6 +306,16 @@ const PrintfulSyncWidget = () => {
306
306
  defineWidgetConfig({
307
307
  zone: "product.list.before"
308
308
  });
309
+ const CLASS_LABEL = {
310
+ apparel: "Apparel",
311
+ embroidery: "Embroidered",
312
+ print_media: "Print media"
313
+ };
314
+ const CLASS_COLOUR = {
315
+ apparel: "blue",
316
+ embroidery: "orange",
317
+ print_media: "green"
318
+ };
309
319
  const STATUS_COLOUR = {
310
320
  success: "green",
311
321
  running: "orange",
@@ -333,14 +343,22 @@ const PrintfulPage = () => {
333
343
  const [syncs, setSyncs] = useState([]);
334
344
  const [health, setHealth] = useState(null);
335
345
  const [stats, setStats] = useState(null);
346
+ const [designs, setDesigns] = useState([]);
347
+ const [bundles, setBundles] = useState([]);
348
+ const [promptProducts, setPromptProducts] = useState([]);
349
+ const [styles, setStyles] = useState([]);
350
+ const [style, setStyle] = useState("editorial");
351
+ const [artwork, setArtwork] = useState("");
336
352
  const [loading, setLoading] = useState(true);
337
353
  const [clearing, setClearing] = useState(false);
338
354
  const load = useCallback(async () => {
339
355
  var _a2;
340
- const [h, w, s] = await Promise.allSettled([
356
+ const [h, w, s, d, b] = await Promise.allSettled([
341
357
  fetch("/admin/printful/history", { credentials: "include" }),
342
358
  fetch("/admin/printful/health", { credentials: "include" }),
343
- fetch("/admin/printful/statistics", { credentials: "include" })
359
+ fetch("/admin/printful/statistics", { credentials: "include" }),
360
+ fetch("/admin/printful/design", { credentials: "include" }),
361
+ fetch("/admin/printful/bundles", { credentials: "include" })
344
362
  ]);
345
363
  if (h.status === "fulfilled" && h.value.ok) {
346
364
  const body = await h.value.json();
@@ -353,11 +371,40 @@ const PrintfulPage = () => {
353
371
  const body = await s.value.json();
354
372
  setStats(((_a2 = body.statistics) == null ? void 0 : _a2[0]) ?? null);
355
373
  }
374
+ if (d.status === "fulfilled" && d.value.ok) {
375
+ const body = await d.value.json();
376
+ setDesigns(body.designs ?? []);
377
+ }
378
+ if (b.status === "fulfilled" && b.value.ok) {
379
+ const body = await b.value.json();
380
+ setBundles(body.bundles ?? []);
381
+ }
356
382
  setLoading(false);
357
383
  }, []);
384
+ const loadPrompts = useCallback(async () => {
385
+ const params = new URLSearchParams({ style });
386
+ if (artwork.trim()) {
387
+ params.set("artwork", artwork.trim());
388
+ }
389
+ try {
390
+ const res = await fetch(`/admin/printful/prompts?${params}`, {
391
+ credentials: "include"
392
+ });
393
+ if (!res.ok) {
394
+ return;
395
+ }
396
+ const body = await res.json();
397
+ setPromptProducts(body.products ?? []);
398
+ setStyles(body.styles ?? []);
399
+ } catch {
400
+ }
401
+ }, [style, artwork]);
358
402
  useEffect(() => {
359
403
  void load();
360
404
  }, [load]);
405
+ useEffect(() => {
406
+ void loadPrompts();
407
+ }, [loadPrompts]);
361
408
  const latest = syncs[0];
362
409
  const running = (latest == null ? void 0 : latest.status) === "running";
363
410
  useEffect(() => {
@@ -471,6 +518,115 @@ const PrintfulPage = () => {
471
518
  ] })
472
519
  ] })
473
520
  ] }),
521
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
522
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Design parameters" }),
523
+ 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: [
524
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
525
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: d.title }),
526
+ /* @__PURE__ */ jsx(Badge, { color: CLASS_COLOUR[d.product_class] ?? "grey", children: CLASS_LABEL[d.product_class] ?? d.product_class })
527
+ ] }),
528
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
529
+ d.technique ?? "—",
530
+ d.brand ? ` · ${d.brand}${d.model ? ` ${d.model}` : ""}` : "",
531
+ d.material ? ` · ${d.material}` : ""
532
+ ] }),
533
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
534
+ "Design goes on: ",
535
+ d.core_placements.join(", ") || "—",
536
+ d.placements.length > d.core_placements.length ? ` (${d.placements.length - d.core_placements.length} more available)` : ""
537
+ ] }),
538
+ d.colors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
539
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Base colours:" }),
540
+ d.colors.slice(0, 12).map((c) => /* @__PURE__ */ jsx(
541
+ "span",
542
+ {
543
+ title: `${c.name}${c.hex ? ` ${c.hex}` : ""}`,
544
+ className: "inline-block h-4 w-4 rounded border border-ui-border-base",
545
+ style: c.hex ? { backgroundColor: c.hex } : void 0
546
+ },
547
+ c.name
548
+ )),
549
+ d.colors.length > 12 && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
550
+ "+",
551
+ d.colors.length - 12
552
+ ] })
553
+ ] }),
554
+ d.sizes.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
555
+ d.product_class === "print_media" ? "Sizes" : "Sizes",
556
+ ":",
557
+ " ",
558
+ d.sizes.slice(0, 8).join(", "),
559
+ d.sizes.length > 8 ? ` +${d.sizes.length - 8}` : ""
560
+ ] })
561
+ ] }, d.product_id))
562
+ ] }),
563
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
564
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Bundles" }),
565
+ 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: [
566
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
567
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: b.title }),
568
+ /* @__PURE__ */ jsx(Badge, { color: b.available ? "green" : "red", children: b.available ? "all members in stock" : "member unavailable" }),
569
+ /* @__PURE__ */ jsx(Badge, { color: b.status === "published" ? "green" : "grey", children: b.status }),
570
+ b.missing_count > 0 && /* @__PURE__ */ jsxs(Badge, { color: "orange", children: [
571
+ b.missing_count,
572
+ " member",
573
+ b.missing_count === 1 ? "" : "s",
574
+ " not found"
575
+ ] })
576
+ ] }),
577
+ b.members.map((m) => /* @__PURE__ */ jsxs(
578
+ Text,
579
+ {
580
+ size: "small",
581
+ className: "text-ui-fg-subtle pl-3",
582
+ children: [
583
+ m.quantity,
584
+ "× ",
585
+ m.product_title ?? "—",
586
+ m.title ? ` · ${m.title}` : "",
587
+ m.status === "missing" ? " — variant not found" : m.status === "active" || m.status === "unknown" ? "" : ` — ${m.status.replace(/_/g, " ")}`
588
+ ]
589
+ },
590
+ m.variant_id
591
+ ))
592
+ ] }, b.variant_id))
593
+ ] }),
594
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-3", children: [
595
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Mockup prompts" }),
596
+ /* @__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." }),
597
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
598
+ /* @__PURE__ */ jsxs(Select, { value: style, onValueChange: setStyle, children: [
599
+ /* @__PURE__ */ jsx(Select.Trigger, { className: "w-40", children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Style" }) }),
600
+ /* @__PURE__ */ jsx(Select.Content, { children: styles.map((s) => /* @__PURE__ */ jsx(Select.Item, { value: s.id, children: s.label }, s.id)) })
601
+ ] }),
602
+ /* @__PURE__ */ jsx(
603
+ Input,
604
+ {
605
+ className: "flex-1 min-w-64",
606
+ placeholder: "What is the artwork? (optional)",
607
+ value: artwork,
608
+ onChange: (e) => setArtwork(e.target.value)
609
+ }
610
+ )
611
+ ] }),
612
+ 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: [
613
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.title }),
614
+ p.prompts.map((prompt, i) => /* @__PURE__ */ jsxs(
615
+ "div",
616
+ {
617
+ className: "flex items-start gap-2 rounded-md border border-ui-border-base p-2",
618
+ children: [
619
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 flex-1", children: [
620
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-muted", children: prompt.label }),
621
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: prompt.text })
622
+ ] }),
623
+ /* @__PURE__ */ jsx(Copy, { content: prompt.text })
624
+ ]
625
+ },
626
+ `${p.product_id}-${i}`
627
+ ))
628
+ ] }, p.product_id))
629
+ ] }),
474
630
  /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
475
631
  /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Recent syncs" }),
476
632
  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
@@ -0,0 +1,55 @@
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
+ const prompt_1 = require("../../../../utils/prompt");
7
+ /**
8
+ * Mockup prompts per product.
9
+ *
10
+ * A pure read of the database plus string assembly — no Printful call, and no
11
+ * image generation. The plugin writes prompts; generation happens wherever you
12
+ * paste them, which is what keeps API keys, rate-limit queues and file storage
13
+ * out of this repo.
14
+ */
15
+ async function GET(req, res) {
16
+ const productModule = req.scope.resolve(utils_1.Modules.PRODUCT);
17
+ const query = req.query;
18
+ // An unrecognized style falls back to the first preset rather than
19
+ // erroring: a mistyped query parameter should still produce prompts.
20
+ const requested = typeof query.style === "string" ? query.style : undefined;
21
+ const style = prompt_1.PROMPT_STYLES.some((s) => s.id === requested)
22
+ ? requested
23
+ : undefined;
24
+ const artwork = typeof query.artwork === "string" && query.artwork.trim()
25
+ ? query.artwork.trim()
26
+ : undefined;
27
+ const products = await productModule.listProducts({}, { relations: ["variants"], take: 100 });
28
+ const items = products
29
+ .map((product) => {
30
+ const summary = (0, design_1.summarizeProductDesign)(product.variants ?? []);
31
+ if (!summary) {
32
+ // Never enriched by a sync. Omitted rather than listed with no
33
+ // prompts, which would read as "this product cannot be prompted for".
34
+ return null;
35
+ }
36
+ const prompts = (0, prompt_1.buildProductPrompts)({ design: summary, style, artwork });
37
+ if (prompts.length === 0) {
38
+ return null;
39
+ }
40
+ return {
41
+ product_id: product.id,
42
+ title: product.title,
43
+ product_class: summary.product_class,
44
+ prompts,
45
+ };
46
+ })
47
+ .filter((p) => p !== null);
48
+ res.status(200).json({
49
+ products: items,
50
+ styles: prompt_1.PROMPT_STYLES.map((s) => ({ id: s.id, label: s.label })),
51
+ style: style ?? prompt_1.PROMPT_STYLES[0].id,
52
+ artwork,
53
+ });
54
+ }
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL3Byb21wdHMvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFpQkEsa0JBa0RDO0FBbEVELHFEQUFtRDtBQUNuRCxxREFBaUU7QUFDakUscURBSWlDO0FBRWpDOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsR0FBRyxDQUFDLEdBQWtCLEVBQUUsR0FBbUI7SUFDL0QsTUFBTSxhQUFhLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsZUFBTyxDQUFDLE9BQU8sQ0FBQyxDQUFBO0lBQ3hELE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFnQyxDQUFBO0lBRWxELG1FQUFtRTtJQUNuRSxxRUFBcUU7SUFDckUsTUFBTSxTQUFTLEdBQUcsT0FBTyxLQUFLLENBQUMsS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFBO0lBQzNFLE1BQU0sS0FBSyxHQUFHLHNCQUFhLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLFNBQVMsQ0FBQztRQUN6RCxDQUFDLENBQUUsU0FBeUI7UUFDNUIsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtJQUViLE1BQU0sT0FBTyxHQUNYLE9BQU8sS0FBSyxDQUFDLE9BQU8sS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUU7UUFDdkQsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFO1FBQ3RCLENBQUMsQ0FBQyxTQUFTLENBQUE7SUFFZixNQUFNLFFBQVEsR0FBRyxNQUFNLGFBQWEsQ0FBQyxZQUFZLENBQy9DLEVBQUUsRUFDRixFQUFFLFNBQVMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FDdkMsQ0FBQTtJQUVELE1BQU0sS0FBSyxHQUFHLFFBQVE7U0FDbkIsR0FBRyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUU7UUFDZixNQUFNLE9BQU8sR0FBRyxJQUFBLCtCQUFzQixFQUFDLE9BQU8sQ0FBQyxRQUFRLElBQUksRUFBRSxDQUFDLENBQUE7UUFDOUQsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ2IsK0RBQStEO1lBQy9ELHNFQUFzRTtZQUN0RSxPQUFPLElBQUksQ0FBQTtRQUNiLENBQUM7UUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFBLDRCQUFtQixFQUFDLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQTtRQUN4RSxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDekIsT0FBTyxJQUFJLENBQUE7UUFDYixDQUFDO1FBRUQsT0FBTztZQUNMLFVBQVUsRUFBRSxPQUFPLENBQUMsRUFBRTtZQUN0QixLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7WUFDcEIsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhO1lBQ3BDLE9BQU87U0FDUixDQUFBO0lBQ0gsQ0FBQyxDQUFDO1NBQ0QsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUE4QixFQUFFLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFBO0lBRXhELEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ25CLFFBQVEsRUFBRSxLQUFLO1FBQ2YsTUFBTSxFQUFFLHNCQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQ2hFLEtBQUssRUFBRSxLQUFLLElBQUksc0JBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFO1FBQ25DLE9BQU87S0FDUixDQUFDLENBQUE7QUFDSixDQUFDIn0=