@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.
- package/.medusa/server/src/admin/index.js +200 -5
- package/.medusa/server/src/admin/index.mjs +201 -6
- package/.medusa/server/src/api/admin/printful/bundles/route.js +79 -0
- package/.medusa/server/src/api/admin/printful/design/route.js +38 -0
- package/.medusa/server/src/api/admin/printful/prompts/route.js +55 -0
- package/.medusa/server/src/utils/bundle.js +243 -0
- package/.medusa/server/src/utils/currency.js +68 -13
- package/.medusa/server/src/utils/design.js +438 -0
- package/.medusa/server/src/utils/printful-client.js +23 -1
- package/.medusa/server/src/utils/prompt.js +423 -0
- package/.medusa/server/src/workflows/apply-order-status.js +16 -5
- package/.medusa/server/src/workflows/create-printful-order.js +20 -3
- package/.medusa/server/src/workflows/sync-products.js +68 -1
- package/README.md +91 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|