@asteby/storefront-react 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/dist/chunk-2WZIWIBW.js +75 -0
- package/dist/chunk-2WZIWIBW.js.map +1 -0
- package/dist/chunk-672OTUM2.js +60 -0
- package/dist/chunk-672OTUM2.js.map +1 -0
- package/dist/{chunk-C6QZNKRE.js → chunk-6CVKW7F3.js} +6 -37
- package/dist/chunk-6CVKW7F3.js.map +1 -0
- package/dist/chunk-DQBL2DFJ.js +40 -0
- package/dist/chunk-DQBL2DFJ.js.map +1 -0
- package/dist/index.cjs +431 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -6
- package/dist/index.d.ts +227 -6
- package/dist/index.js +424 -69
- package/dist/index.js.map +1 -1
- package/dist/mockups.js +2 -1
- package/dist/mockups.js.map +1 -1
- package/dist/payments.cjs +766 -0
- package/dist/payments.cjs.map +1 -0
- package/dist/payments.d.cts +364 -0
- package/dist/payments.d.ts +364 -0
- package/dist/payments.js +680 -0
- package/dist/payments.js.map +1 -0
- package/dist/promotions.cjs +573 -0
- package/dist/promotions.cjs.map +1 -0
- package/dist/promotions.d.cts +243 -0
- package/dist/promotions.d.ts +243 -0
- package/dist/promotions.js +480 -0
- package/dist/promotions.js.map +1 -0
- package/package.json +23 -2
- package/dist/chunk-C6QZNKRE.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -502,6 +502,7 @@ function useProductOptions(product, config = {}) {
|
|
|
502
502
|
exists: exact ? true : loose.length > 0 && Object.keys(selectedOptions).length < product.options.length,
|
|
503
503
|
available: exact ? exact.available_for_sale : Object.keys(selectedOptions).length < product.options.length && loose.some((v) => v.available_for_sale),
|
|
504
504
|
variant: exact,
|
|
505
|
+
availability: exact ? storefront.resolveAvailability(exact) : loose.length ? storefront.productAvailability({ variants: loose, available_for_sale: loose.some((v) => v.available_for_sale) }) : null,
|
|
505
506
|
search: `?${search.toString()}`
|
|
506
507
|
};
|
|
507
508
|
})
|
|
@@ -520,21 +521,32 @@ function toMerchandise(product, variant) {
|
|
|
520
521
|
function AddToCartButton({
|
|
521
522
|
variantId,
|
|
522
523
|
quantity = 1,
|
|
524
|
+
availability,
|
|
525
|
+
availabilitySource,
|
|
526
|
+
lowStockThreshold,
|
|
527
|
+
locale,
|
|
523
528
|
merchandise,
|
|
524
529
|
onAdded,
|
|
525
530
|
onClick,
|
|
526
531
|
disabled,
|
|
527
|
-
children
|
|
532
|
+
children,
|
|
528
533
|
type = "button",
|
|
529
534
|
...rest
|
|
530
535
|
}) {
|
|
531
536
|
const cart = useCart();
|
|
537
|
+
const sf = useOptionalStorefront();
|
|
538
|
+
const resolved = availability ?? (availabilitySource ? storefront.resolveAvailability(availabilitySource, { lowStockThreshold: lowStockThreshold ?? null }) : null);
|
|
539
|
+
const view = resolved ? storefront.availabilityView(resolved, locale ?? sf?.locale ?? "es-MX") : null;
|
|
540
|
+
const blocked = view ? !view.ctaEnabled : false;
|
|
541
|
+
const qty = view?.maxPerOrder != null ? Math.min(quantity, view.maxPerOrder) : quantity;
|
|
542
|
+
const label = children ?? view?.ctaLabel ?? "Agregar al carrito";
|
|
532
543
|
const [adding, setAdding] = react.useState(false);
|
|
533
544
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
534
545
|
"button",
|
|
535
546
|
{
|
|
536
547
|
type,
|
|
537
|
-
disabled: disabled || !variantId || adding,
|
|
548
|
+
disabled: disabled || !variantId || adding || blocked,
|
|
549
|
+
"data-availability": view?.status,
|
|
538
550
|
"aria-busy": adding || void 0,
|
|
539
551
|
"data-adding": adding || void 0,
|
|
540
552
|
onClick: async (event) => {
|
|
@@ -542,17 +554,247 @@ function AddToCartButton({
|
|
|
542
554
|
if (event.defaultPrevented || !variantId) return;
|
|
543
555
|
setAdding(true);
|
|
544
556
|
try {
|
|
545
|
-
const result = await cart.addLines([{ variant_id: variantId, quantity, ...merchandise ? { merchandise } : {} }]);
|
|
557
|
+
const result = await cart.addLines([{ variant_id: variantId, quantity: qty, ...merchandise ? { merchandise } : {} }]);
|
|
546
558
|
if (result) onAdded?.();
|
|
547
559
|
} finally {
|
|
548
560
|
setAdding(false);
|
|
549
561
|
}
|
|
550
562
|
},
|
|
551
563
|
...rest,
|
|
552
|
-
children
|
|
564
|
+
children: label
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
var SR_ONLY = {
|
|
569
|
+
position: "absolute",
|
|
570
|
+
width: 1,
|
|
571
|
+
height: 1,
|
|
572
|
+
margin: -1,
|
|
573
|
+
padding: 0,
|
|
574
|
+
overflow: "hidden",
|
|
575
|
+
clip: "rect(0 0 0 0)",
|
|
576
|
+
whiteSpace: "nowrap",
|
|
577
|
+
border: 0
|
|
578
|
+
};
|
|
579
|
+
function SrOnly({ children }) {
|
|
580
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: SR_ONLY, children });
|
|
581
|
+
}
|
|
582
|
+
function useLocale(locale) {
|
|
583
|
+
const sf = useOptionalStorefront();
|
|
584
|
+
return locale ?? sf?.locale ?? "es-MX";
|
|
585
|
+
}
|
|
586
|
+
function pickAvailability({ availability, source, lowStockThreshold }) {
|
|
587
|
+
const opts = { lowStockThreshold: lowStockThreshold ?? null };
|
|
588
|
+
if (availability) return availability;
|
|
589
|
+
return source ? storefront.resolveAvailability(source, opts) : null;
|
|
590
|
+
}
|
|
591
|
+
function useAvailability(input) {
|
|
592
|
+
const locale = useLocale(input.locale);
|
|
593
|
+
const a = pickAvailability(input);
|
|
594
|
+
return react.useMemo(
|
|
595
|
+
() => a ? storefront.availabilityView(a, locale, { messages: input.messages ?? null }) : null,
|
|
596
|
+
// `a` is rebuilt from props each render; key by its content
|
|
597
|
+
[JSON.stringify(a), locale, input.messages]
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
function AvailabilityBadge({
|
|
601
|
+
availability,
|
|
602
|
+
source,
|
|
603
|
+
lowStockThreshold,
|
|
604
|
+
locale,
|
|
605
|
+
messages,
|
|
606
|
+
placement = "detail",
|
|
607
|
+
live,
|
|
608
|
+
...rest
|
|
609
|
+
}) {
|
|
610
|
+
const view = useAvailability({ availability, source, lowStockThreshold, locale, messages });
|
|
611
|
+
const loc = useLocale(locale);
|
|
612
|
+
if (!view) return null;
|
|
613
|
+
if (placement === "card" && !view.showOnCard) return null;
|
|
614
|
+
const m = storefront.availabilityMessages(loc, messages);
|
|
615
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
616
|
+
"span",
|
|
617
|
+
{
|
|
618
|
+
"data-availability": view.status,
|
|
619
|
+
"data-tone": view.tone,
|
|
620
|
+
"data-placement": placement,
|
|
621
|
+
...live ? { role: "status" } : {},
|
|
622
|
+
...rest,
|
|
623
|
+
children: [
|
|
624
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": view.srText !== view.label ? true : void 0, children: view.label }),
|
|
625
|
+
view.srText !== view.label && /* @__PURE__ */ jsxRuntime.jsx(SrOnly, { children: view.srText }),
|
|
626
|
+
placement === "detail" && view.sublabel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
627
|
+
" ",
|
|
628
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-availability-note": "eta", children: view.sublabel })
|
|
629
|
+
] }),
|
|
630
|
+
placement === "detail" && view.maxPerOrder !== null && view.status !== "out_of_stock" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
631
|
+
" ",
|
|
632
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "data-availability-note": "max", children: storefront.formatAvailabilityMessage(m.max_per_order, { n: view.maxPerOrder }) })
|
|
633
|
+
] })
|
|
634
|
+
]
|
|
553
635
|
}
|
|
554
636
|
);
|
|
555
637
|
}
|
|
638
|
+
function VariantOptionState({ children, availability, source, lowStockThreshold, locale, messages, ...rest }) {
|
|
639
|
+
const a = pickAvailability({ availability, source, lowStockThreshold });
|
|
640
|
+
const loc = useLocale(locale);
|
|
641
|
+
if (!a) return /* @__PURE__ */ jsxRuntime.jsx("span", { ...rest, children });
|
|
642
|
+
const m = storefront.availabilityMessages(loc, messages);
|
|
643
|
+
const status = storefront.availabilityView(a, loc, { messages: messages ?? null }).status;
|
|
644
|
+
if (status === "out_of_stock") {
|
|
645
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-availability": status, ...rest, children: [
|
|
646
|
+
/* @__PURE__ */ jsxRuntime.jsx("s", { children }),
|
|
647
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SrOnly, { children: [
|
|
648
|
+
" (",
|
|
649
|
+
m["option.sold_out"],
|
|
650
|
+
")"
|
|
651
|
+
] })
|
|
652
|
+
] });
|
|
653
|
+
}
|
|
654
|
+
if (status === "backorder" || status === "preorder") {
|
|
655
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-availability": status, ...rest, children: [
|
|
656
|
+
children,
|
|
657
|
+
" ",
|
|
658
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { "data-availability-tag": true, children: [
|
|
659
|
+
"(",
|
|
660
|
+
m[status === "backorder" ? "option.backorder" : "option.preorder"],
|
|
661
|
+
")"
|
|
662
|
+
] })
|
|
663
|
+
] });
|
|
664
|
+
}
|
|
665
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { "data-availability": status, ...rest, children });
|
|
666
|
+
}
|
|
667
|
+
function RestockNotifyForm({
|
|
668
|
+
sku,
|
|
669
|
+
market,
|
|
670
|
+
allowPhone = false,
|
|
671
|
+
action,
|
|
672
|
+
client: clientProp,
|
|
673
|
+
enhance = true,
|
|
674
|
+
onSubscribed,
|
|
675
|
+
onError,
|
|
676
|
+
availability,
|
|
677
|
+
source,
|
|
678
|
+
lowStockThreshold,
|
|
679
|
+
locale,
|
|
680
|
+
messages,
|
|
681
|
+
...rest
|
|
682
|
+
}) {
|
|
683
|
+
const sf = useOptionalStorefront();
|
|
684
|
+
const view = useAvailability({ availability, source, lowStockThreshold, locale, messages });
|
|
685
|
+
const loc = useLocale(locale);
|
|
686
|
+
const id = react.useId();
|
|
687
|
+
const [state, setState] = react.useState("idle");
|
|
688
|
+
if (!view?.showNotify) return null;
|
|
689
|
+
const m = storefront.availabilityMessages(loc, messages);
|
|
690
|
+
const client = clientProp ?? sf?.client ?? null;
|
|
691
|
+
if (state === "done") {
|
|
692
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "data-restock": "done", children: m["notify.done"] });
|
|
693
|
+
}
|
|
694
|
+
const submit = enhance && client ? (e) => {
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
const form = new FormData(e.currentTarget);
|
|
697
|
+
const email = String(form.get("email") ?? "").trim();
|
|
698
|
+
const phone = String(form.get("phone") ?? "").trim();
|
|
699
|
+
if (!email && !phone) {
|
|
700
|
+
setState("error");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
setState("sending");
|
|
704
|
+
client.restock.subscribe({ sku, locale: loc, consent: true, ...email ? { email } : {}, ...phone ? { phone } : {}, ...market ? { market } : {} }).then(() => {
|
|
705
|
+
setState("done");
|
|
706
|
+
onSubscribed?.();
|
|
707
|
+
}).catch((err) => {
|
|
708
|
+
setState("error");
|
|
709
|
+
onError?.(err);
|
|
710
|
+
});
|
|
711
|
+
} : void 0;
|
|
712
|
+
return /* @__PURE__ */ jsxRuntime.jsx("form", { method: "post", ...action !== void 0 ? { action } : {}, ...rest, onSubmit: submit, "data-restock": state, children: /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: m["notify.title"] }),
|
|
714
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "sku", value: sku }),
|
|
715
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "locale", value: loc }),
|
|
716
|
+
market && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name: "market", value: market }),
|
|
717
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
718
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `${id}-email`, children: m["notify.email"] }),
|
|
719
|
+
" ",
|
|
720
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { id: `${id}-email`, name: "email", type: "email", autoComplete: "email", required: !allowPhone })
|
|
721
|
+
] }),
|
|
722
|
+
allowPhone && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
723
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `${id}-phone`, children: m["notify.phone"] }),
|
|
724
|
+
" ",
|
|
725
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { id: `${id}-phone`, name: "phone", type: "tel", autoComplete: "tel" })
|
|
726
|
+
] }),
|
|
727
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
728
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { id: `${id}-consent`, name: "consent", type: "checkbox", value: "true", required: true }),
|
|
729
|
+
" ",
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: `${id}-consent`, children: m["notify.consent"] })
|
|
731
|
+
] }),
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: state === "sending", "aria-busy": state === "sending" || void 0, children: m["notify.submit"] }),
|
|
733
|
+
state === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: m["notify.error"] })
|
|
734
|
+
] }) });
|
|
735
|
+
}
|
|
736
|
+
function CartLineProblem({ line, locale, messages, onAdjust, onRemove, ...rest }) {
|
|
737
|
+
const cart = useOptionalCart();
|
|
738
|
+
const loc = useLocale(locale);
|
|
739
|
+
if (!line.problem) return null;
|
|
740
|
+
const view = storefront.cartLineProblemView(line.problem, loc, { messages: messages ?? null, lineQuantity: line.quantity });
|
|
741
|
+
const adjust = onAdjust ?? (cart ? (id, q) => cart.updateLines([{ id, quantity: q }]) : void 0);
|
|
742
|
+
const remove = onRemove ?? (cart ? (id) => cart.removeLines([id]) : void 0);
|
|
743
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-cart-line-problem": view.code, ...rest, children: [
|
|
744
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: view.message }),
|
|
745
|
+
view.adjustTo !== null && adjust && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void adjust(line.id, view.adjustTo), children: view.adjustLabel }),
|
|
746
|
+
remove && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void remove(line.id), children: view.removeLabel })
|
|
747
|
+
] });
|
|
748
|
+
}
|
|
749
|
+
function StockConflictAlert({
|
|
750
|
+
error: errorProp,
|
|
751
|
+
cart: cartProp,
|
|
752
|
+
locale,
|
|
753
|
+
messages,
|
|
754
|
+
onAdjust,
|
|
755
|
+
onRemove,
|
|
756
|
+
onDismiss,
|
|
757
|
+
autoFocus = true,
|
|
758
|
+
...rest
|
|
759
|
+
}) {
|
|
760
|
+
const cartCtx = useOptionalCart();
|
|
761
|
+
const loc = useLocale(locale);
|
|
762
|
+
const ref = react.useRef(null);
|
|
763
|
+
const error = errorProp !== void 0 ? errorProp : cartCtx?.error ?? null;
|
|
764
|
+
const cart = cartProp !== void 0 ? cartProp : cartCtx?.cart ?? null;
|
|
765
|
+
const active = storefront.isStorefrontError(error) && error.code === "insufficient_stock";
|
|
766
|
+
react.useEffect(() => {
|
|
767
|
+
if (active && autoFocus) ref.current?.focus({ preventScroll: true });
|
|
768
|
+
}, [active, error, autoFocus]);
|
|
769
|
+
if (!active) return null;
|
|
770
|
+
const m = storefront.availabilityMessages(loc, messages);
|
|
771
|
+
const lines = error.stockLines;
|
|
772
|
+
const adjust = onAdjust ?? (cartCtx ? (id, q) => cartCtx.updateLines([{ id, quantity: q }]) : void 0);
|
|
773
|
+
const remove = onRemove ?? (cartCtx ? (id) => cartCtx.removeLines([id]) : void 0);
|
|
774
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, role: "alert", tabIndex: -1, "data-stock-conflict": true, ...rest, children: [
|
|
775
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: m["conflict.title"] }) }),
|
|
776
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: m["conflict.body"] }),
|
|
777
|
+
lines.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { children: lines.map((l) => {
|
|
778
|
+
const cartLine = cart?.lines.find((x) => l.line_id ? x.id === l.line_id : x.merchandise.sku === l.sku);
|
|
779
|
+
const lineId = l.line_id ?? cartLine?.id ?? null;
|
|
780
|
+
const view = storefront.cartLineProblemView(l, loc, { messages: messages ?? null, ...cartLine ? { lineQuantity: cartLine.quantity } : {} });
|
|
781
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { "data-cart-line-problem": view.code, children: [
|
|
782
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: cartLine ? `${cartLine.merchandise.product.title}${cartLine.merchandise.title ? ` (${cartLine.merchandise.title})` : ""}` : l.sku }),
|
|
783
|
+
": ",
|
|
784
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: view.message }),
|
|
785
|
+
lineId && view.adjustTo !== null && adjust && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
786
|
+
" ",
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void adjust(lineId, view.adjustTo), children: view.adjustLabel })
|
|
788
|
+
] }),
|
|
789
|
+
lineId && remove && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
790
|
+
" ",
|
|
791
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void remove(lineId), children: view.removeLabel })
|
|
792
|
+
] })
|
|
793
|
+
] }, `${l.sku}:${l.line_id ?? ""}`);
|
|
794
|
+
}) }),
|
|
795
|
+
onDismiss && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onDismiss, children: m["conflict.dismiss"] })
|
|
796
|
+
] });
|
|
797
|
+
}
|
|
556
798
|
var clean = (s, max) => {
|
|
557
799
|
if (!s) return void 0;
|
|
558
800
|
const text = s.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -586,7 +828,8 @@ function seoMeta(input) {
|
|
|
586
828
|
function seoOf(seo, fallbackTitle, fallbackDescription) {
|
|
587
829
|
return { title: seo.title ?? fallbackTitle, description: seo.description ?? fallbackDescription ?? null };
|
|
588
830
|
}
|
|
589
|
-
|
|
831
|
+
var OG_AVAILABILITY = { in_stock: "in stock", low_stock: "in stock", out_of_stock: "out of stock", backorder: "backorder", preorder: "preorder" };
|
|
832
|
+
function productMeta(product, shop, url, options = {}) {
|
|
590
833
|
const { title, description } = seoOf(product.seo, product.title, product.description);
|
|
591
834
|
const meta = seoMeta({
|
|
592
835
|
title,
|
|
@@ -601,7 +844,7 @@ function productMeta(product, shop, url) {
|
|
|
601
844
|
meta.push(
|
|
602
845
|
{ property: "product:price:amount", content: product.price_range.min.amount },
|
|
603
846
|
{ property: "product:price:currency", content: product.price_range.min.currency_code },
|
|
604
|
-
{ property: "product:availability", content: product.
|
|
847
|
+
{ property: "product:availability", content: OG_AVAILABILITY[storefront.productAvailability(product, options).status] ?? "out of stock" }
|
|
605
848
|
);
|
|
606
849
|
if (product.vendor) meta.push({ property: "product:brand", content: product.vendor });
|
|
607
850
|
return meta;
|
|
@@ -638,22 +881,24 @@ function withVariant(url, variantId) {
|
|
|
638
881
|
return `${url}${url.includes("?") ? "&" : "?"}variant=${encodeURIComponent(variantId)}`;
|
|
639
882
|
}
|
|
640
883
|
}
|
|
641
|
-
function productJsonLd(product, shop, url) {
|
|
884
|
+
function productJsonLd(product, shop, url, options = {}) {
|
|
642
885
|
const images = product.images.length ? product.images.map((i) => i.url) : product.featured_image ? [product.featured_image.url] : [];
|
|
643
|
-
const offers = product.variants.map(
|
|
644
|
-
(v)
|
|
886
|
+
const offers = product.variants.map((v) => {
|
|
887
|
+
const availability = storefront.resolveAvailability(v, options);
|
|
888
|
+
return compact({
|
|
645
889
|
"@type": "Offer",
|
|
646
890
|
sku: v.sku,
|
|
647
891
|
gtin: v.barcode ?? void 0,
|
|
648
892
|
name: product.variants.length > 1 ? `${product.title} - ${v.title}` : void 0,
|
|
649
893
|
price: v.price.amount,
|
|
650
894
|
priceCurrency: v.price.currency_code,
|
|
651
|
-
availability:
|
|
895
|
+
availability: storefront.schemaAvailability(availability.status),
|
|
896
|
+
availabilityStarts: availability.status === "preorder" ? availability.preorder_release_at ?? void 0 : void 0,
|
|
652
897
|
itemCondition: "https://schema.org/NewCondition",
|
|
653
898
|
url: product.variants.length > 1 ? withVariant(url, v.id) : url,
|
|
654
899
|
seller: shop ? { "@type": "Organization", name: shop.name } : void 0
|
|
655
|
-
})
|
|
656
|
-
);
|
|
900
|
+
});
|
|
901
|
+
});
|
|
657
902
|
const additionalProperty = Object.entries(product.attributes).map(([name, value]) => ({ "@type": "PropertyValue", name, value }));
|
|
658
903
|
return compact({
|
|
659
904
|
"@context": CONTEXT,
|
|
@@ -765,6 +1010,156 @@ function JsonLd({ data, nonce }) {
|
|
|
765
1010
|
if (!data || Array.isArray(data) && data.length === 0) return null;
|
|
766
1011
|
return /* @__PURE__ */ jsxRuntime.jsx("script", { type: "application/ld+json", nonce, dangerouslySetInnerHTML: { __html: serializeJsonLd(data) } });
|
|
767
1012
|
}
|
|
1013
|
+
var OPS_BUILTIN_SECTION_TYPES = ["hero", "product_grid", "collection_list", "rich_text", "faq", "image_with_text"];
|
|
1014
|
+
var OPS_PROP_KINDS = ["text", "textarea", "richtext", "image", "link", "number", "select", "boolean", "collection", "collection_list", "product", "product_list", "list"];
|
|
1015
|
+
var SectionTypesValidationError = class extends Error {
|
|
1016
|
+
issues;
|
|
1017
|
+
constructor(issues) {
|
|
1018
|
+
super(`Invalid section types for OPS:
|
|
1019
|
+
${issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`);
|
|
1020
|
+
this.name = "SectionTypesValidationError";
|
|
1021
|
+
this.issues = issues;
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
var SNAKE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|
|
1025
|
+
var KIND_MAP = {
|
|
1026
|
+
text: "text",
|
|
1027
|
+
textarea: "textarea",
|
|
1028
|
+
richtext: "richtext",
|
|
1029
|
+
url: "text",
|
|
1030
|
+
color: "text",
|
|
1031
|
+
image: "image",
|
|
1032
|
+
number: "number",
|
|
1033
|
+
boolean: "boolean",
|
|
1034
|
+
select: "select",
|
|
1035
|
+
collection: "collection",
|
|
1036
|
+
product: "product",
|
|
1037
|
+
products: "product_list",
|
|
1038
|
+
list: "list"
|
|
1039
|
+
};
|
|
1040
|
+
var LIST_KINDS = /* @__PURE__ */ new Set(["list"]);
|
|
1041
|
+
function convertProps(schema, path, issues) {
|
|
1042
|
+
const out = [];
|
|
1043
|
+
for (const [key, spec] of Object.entries(schema ?? {})) {
|
|
1044
|
+
const p = `${path}.${key}`;
|
|
1045
|
+
const kind = KIND_MAP[spec.kind];
|
|
1046
|
+
if (!kind) {
|
|
1047
|
+
issues.push({ path: `${p}.kind`, message: `unknown kind "${String(spec.kind)}"` });
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const prop = { key, kind, label: spec.label };
|
|
1051
|
+
if (spec.required) prop.required = true;
|
|
1052
|
+
if ("default" in spec && spec.default !== void 0) prop.default = spec.default;
|
|
1053
|
+
if (spec.options) prop.options = spec.options.map((o) => ({ value: o.value, label: o.label }));
|
|
1054
|
+
if (spec.min !== void 0) prop.min = spec.min;
|
|
1055
|
+
if (spec.max !== void 0) prop.max = spec.max;
|
|
1056
|
+
if (LIST_KINDS.has(kind)) prop.fields = convertProps(spec.items, `${p}.fields`, issues);
|
|
1057
|
+
out.push(prop);
|
|
1058
|
+
}
|
|
1059
|
+
return out;
|
|
1060
|
+
}
|
|
1061
|
+
function validateOpsSectionTypes(types) {
|
|
1062
|
+
const issues = [];
|
|
1063
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1064
|
+
const walk = (props, path) => {
|
|
1065
|
+
if (!Array.isArray(props)) {
|
|
1066
|
+
issues.push({ path, message: "must be a list" });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1070
|
+
props.forEach((prop, i) => {
|
|
1071
|
+
const p = `${path}[${i}]`;
|
|
1072
|
+
if (typeof prop.key !== "string" || !SNAKE.test(prop.key)) issues.push({ path: `${p}.key`, message: `"${String(prop.key)}" must be snake_case (a-z, 0-9, _)` });
|
|
1073
|
+
else if (keys.has(prop.key)) issues.push({ path: `${p}.key`, message: `duplicate key "${prop.key}"` });
|
|
1074
|
+
else keys.add(prop.key);
|
|
1075
|
+
if (typeof prop.label !== "string" || !prop.label.trim()) issues.push({ path: `${p}.label`, message: "label is required" });
|
|
1076
|
+
if (!OPS_PROP_KINDS.includes(prop.kind)) {
|
|
1077
|
+
issues.push({ path: `${p}.kind`, message: `unknown kind "${String(prop.kind)}"` });
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (prop.kind === "list") {
|
|
1081
|
+
if (!prop.fields || prop.fields.length === 0) issues.push({ path: `${p}.fields`, message: 'list requires a non-empty "fields"' });
|
|
1082
|
+
else walk(prop.fields, `${p}.fields`);
|
|
1083
|
+
}
|
|
1084
|
+
if (prop.kind === "select" && (!prop.options || prop.options.length === 0)) issues.push({ path: `${p}.options`, message: 'select requires non-empty "options"' });
|
|
1085
|
+
if (prop.min !== void 0 && prop.max !== void 0 && prop.min > prop.max) issues.push({ path: `${p}.max`, message: "max must be >= min" });
|
|
1086
|
+
});
|
|
1087
|
+
};
|
|
1088
|
+
types.forEach((t, i) => {
|
|
1089
|
+
const p = t.type ? t.type : `[${i}]`;
|
|
1090
|
+
if (!t.type || !t.type.trim()) issues.push({ path: `${p}.type`, message: "type is required" });
|
|
1091
|
+
else if (seen.has(t.type)) issues.push({ path: `${p}.type`, message: `duplicate type "${t.type}"` });
|
|
1092
|
+
else seen.add(t.type);
|
|
1093
|
+
if (!t.label || !t.label.trim()) issues.push({ path: `${p}.label`, message: "label is required" });
|
|
1094
|
+
if (t.max_per_page !== void 0 && (!Number.isInteger(t.max_per_page) || t.max_per_page < 1)) issues.push({ path: `${p}.max_per_page`, message: "must be a positive integer" });
|
|
1095
|
+
walk(t.schema?.props, `${p}.schema.props`);
|
|
1096
|
+
});
|
|
1097
|
+
return issues;
|
|
1098
|
+
}
|
|
1099
|
+
function sectionTypesToOps(source, options = {}) {
|
|
1100
|
+
const excludeBuiltin = options.excludeBuiltin ?? true;
|
|
1101
|
+
const defs = Array.isArray(source) ? source : source.definitions();
|
|
1102
|
+
const builtin = new Set(OPS_BUILTIN_SECTION_TYPES);
|
|
1103
|
+
const issues = [];
|
|
1104
|
+
const warnings = [];
|
|
1105
|
+
const types = [];
|
|
1106
|
+
for (const def of defs) {
|
|
1107
|
+
if (excludeBuiltin && builtin.has(def.type)) {
|
|
1108
|
+
const known = options.builtinProps?.[def.type];
|
|
1109
|
+
if (known) {
|
|
1110
|
+
for (const name of Object.keys(def.schema ?? {})) {
|
|
1111
|
+
if (!known.includes(name)) warnings.push({ type: def.type, prop: name, message: `prop "${name}" of built-in type "${def.type}" does not exist in the OPS schema; built-in types cannot be redefined, so it will not be editable in OPS` });
|
|
1112
|
+
}
|
|
1113
|
+
} else {
|
|
1114
|
+
warnings.push({ type: def.type, message: `"${def.type}" is a built-in OPS type: not synced, and its local props cannot be verified against the OPS schema` });
|
|
1115
|
+
}
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
const entryIssues = [];
|
|
1119
|
+
const props = convertProps(def.schema, `${def.type}.schema.props`, entryIssues);
|
|
1120
|
+
issues.push(...entryIssues);
|
|
1121
|
+
const out = { type: def.type, label: def.label ?? def.type, schema: { props } };
|
|
1122
|
+
if (def.description) out.description = def.description;
|
|
1123
|
+
if (def.category) out.category = def.category;
|
|
1124
|
+
if (def.maxPerPage !== void 0) out.max_per_page = def.maxPerPage;
|
|
1125
|
+
types.push(out);
|
|
1126
|
+
}
|
|
1127
|
+
issues.push(...validateOpsSectionTypes(types));
|
|
1128
|
+
if (issues.length) throw new SectionTypesValidationError(issues);
|
|
1129
|
+
return { types, warnings };
|
|
1130
|
+
}
|
|
1131
|
+
var SYNC_PATH = "/storefront/content/section-types";
|
|
1132
|
+
var count = (v) => Array.isArray(v) ? v.length : typeof v === "number" ? v : 0;
|
|
1133
|
+
async function syncSectionTypes(target, source, options = {}) {
|
|
1134
|
+
const { types, warnings } = sectionTypesToOps(source, options);
|
|
1135
|
+
let raw;
|
|
1136
|
+
if ("request" in target && typeof target.request === "function") {
|
|
1137
|
+
raw = await target.request({ method: "PUT", path: SYNC_PATH, body: { section_types: types } });
|
|
1138
|
+
} else {
|
|
1139
|
+
const t = target;
|
|
1140
|
+
if (!t.token.startsWith("sk_")) throw new TypeError("syncSectionTypes: an sk_ token with content:write is required");
|
|
1141
|
+
const f = t.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1142
|
+
const url = t.baseUrl.replace(/\/+$/, "") + SYNC_PATH;
|
|
1143
|
+
const res = await f(url, {
|
|
1144
|
+
method: "PUT",
|
|
1145
|
+
headers: { Authorization: `Bearer ${t.token}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
1146
|
+
body: JSON.stringify({ section_types: types })
|
|
1147
|
+
});
|
|
1148
|
+
const text = await res.text();
|
|
1149
|
+
let body;
|
|
1150
|
+
try {
|
|
1151
|
+
body = text ? JSON.parse(text) : void 0;
|
|
1152
|
+
} catch {
|
|
1153
|
+
body = void 0;
|
|
1154
|
+
}
|
|
1155
|
+
if (!res.ok) {
|
|
1156
|
+
throw new storefront.StorefrontError({ status: res.status, code: body?.code ?? body?.error?.code ?? "unknown", message: body?.message ?? body?.error?.message ?? `Section types sync failed (${res.status})`, url });
|
|
1157
|
+
}
|
|
1158
|
+
raw = body;
|
|
1159
|
+
}
|
|
1160
|
+
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
1161
|
+
return { created: count(data?.created), updated: count(data?.updated), warnings };
|
|
1162
|
+
}
|
|
768
1163
|
function defineSection(definition) {
|
|
769
1164
|
return definition;
|
|
770
1165
|
}
|
|
@@ -788,6 +1183,9 @@ function resolveSectionProps(schema, raw, path = "") {
|
|
|
788
1183
|
else if (spec.required) errors.push({ prop: key, message: "is required" });
|
|
789
1184
|
continue;
|
|
790
1185
|
}
|
|
1186
|
+
if (spec.kind === "image" && typeof value === "object" && value !== null && typeof value.url === "string") {
|
|
1187
|
+
value = value.url;
|
|
1188
|
+
}
|
|
791
1189
|
if (STRING_KINDS.has(spec.kind)) {
|
|
792
1190
|
if (typeof value !== "string") {
|
|
793
1191
|
fail2("must be a string");
|
|
@@ -911,6 +1309,14 @@ var SectionRegistry = class {
|
|
|
911
1309
|
toJSONSchema() {
|
|
912
1310
|
return Object.fromEntries(this.definitions().map((d) => [d.type, sectionJsonSchema(d)]));
|
|
913
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Section types in the OPS `storefront_content` format, ready for
|
|
1314
|
+
* `PUT /storefront/content/section-types`. Throws `SectionTypesValidationError`
|
|
1315
|
+
* on anything OPS would reject; `warnings` flags built-in types.
|
|
1316
|
+
*/
|
|
1317
|
+
toOpsSectionTypes(options) {
|
|
1318
|
+
return sectionTypesToOps(this, options);
|
|
1319
|
+
}
|
|
914
1320
|
};
|
|
915
1321
|
function createSectionRegistry(definitions = []) {
|
|
916
1322
|
return new SectionRegistry(definitions);
|
|
@@ -1339,7 +1745,9 @@ function NearestLocationForm({
|
|
|
1339
1745
|
}
|
|
1340
1746
|
|
|
1341
1747
|
exports.AddToCartButton = AddToCartButton;
|
|
1748
|
+
exports.AvailabilityBadge = AvailabilityBadge;
|
|
1342
1749
|
exports.CART_COOKIE_NAME = CART_COOKIE_NAME;
|
|
1750
|
+
exports.CartLineProblem = CartLineProblem;
|
|
1343
1751
|
exports.CartProvider = CartProvider;
|
|
1344
1752
|
exports.DEFAULT_IMAGE_WIDTHS = DEFAULT_IMAGE_WIDTHS;
|
|
1345
1753
|
exports.DirectionsLink = DirectionsLink;
|
|
@@ -1349,15 +1757,22 @@ exports.LocationList = LocationList;
|
|
|
1349
1757
|
exports.LocationPicker = LocationPicker;
|
|
1350
1758
|
exports.Money = Money;
|
|
1351
1759
|
exports.NearestLocationForm = NearestLocationForm;
|
|
1760
|
+
exports.OPS_BUILTIN_SECTION_TYPES = OPS_BUILTIN_SECTION_TYPES;
|
|
1761
|
+
exports.OPS_PROP_KINDS = OPS_PROP_KINDS;
|
|
1352
1762
|
exports.OpenStatus = OpenStatus;
|
|
1353
1763
|
exports.PickupOption = PickupOption;
|
|
1354
1764
|
exports.ProductPrice = ProductPrice;
|
|
1765
|
+
exports.RestockNotifyForm = RestockNotifyForm;
|
|
1355
1766
|
exports.SELECTED_LOCATION_PARAM = SELECTED_LOCATION_PARAM;
|
|
1356
1767
|
exports.SELECTED_LOCATION_STORAGE_KEY = SELECTED_LOCATION_STORAGE_KEY;
|
|
1357
1768
|
exports.SectionRegistry = SectionRegistry;
|
|
1769
|
+
exports.SectionTypesValidationError = SectionTypesValidationError;
|
|
1358
1770
|
exports.Sections = Sections;
|
|
1771
|
+
exports.SrOnly = SrOnly;
|
|
1772
|
+
exports.StockConflictAlert = StockConflictAlert;
|
|
1359
1773
|
exports.StorefrontProvider = StorefrontProvider;
|
|
1360
1774
|
exports.ThemeStyle = ThemeStyle;
|
|
1775
|
+
exports.VariantOptionState = VariantOptionState;
|
|
1361
1776
|
exports.VariantSelector = VariantSelector;
|
|
1362
1777
|
exports.breadcrumbJsonLd = breadcrumbJsonLd;
|
|
1363
1778
|
exports.collectionJsonLd = collectionJsonLd;
|
|
@@ -1378,14 +1793,17 @@ exports.readCartCookie = readCartCookie;
|
|
|
1378
1793
|
exports.readSelectedLocationId = readSelectedLocationId;
|
|
1379
1794
|
exports.resolveSectionProps = resolveSectionProps;
|
|
1380
1795
|
exports.sectionJsonSchema = sectionJsonSchema;
|
|
1796
|
+
exports.sectionTypesToOps = sectionTypesToOps;
|
|
1381
1797
|
exports.seoMeta = seoMeta;
|
|
1382
1798
|
exports.serializeCartCookie = serializeCartCookie;
|
|
1383
1799
|
exports.serializeJsonLd = serializeJsonLd;
|
|
1800
|
+
exports.syncSectionTypes = syncSectionTypes;
|
|
1384
1801
|
exports.themeFontsHref = themeFontsHref;
|
|
1385
1802
|
exports.themeToCss = themeToCss;
|
|
1386
1803
|
exports.themeToCssVariables = themeToCssVariables;
|
|
1387
1804
|
exports.toMerchandise = toMerchandise;
|
|
1388
1805
|
exports.tokenName = tokenName;
|
|
1806
|
+
exports.useAvailability = useAvailability;
|
|
1389
1807
|
exports.useCart = useCart;
|
|
1390
1808
|
exports.useGeolocation = useGeolocation;
|
|
1391
1809
|
exports.useNearestLocations = useNearestLocations;
|
|
@@ -1395,6 +1813,7 @@ exports.useProductOptions = useProductOptions;
|
|
|
1395
1813
|
exports.useSelectedLocation = useSelectedLocation;
|
|
1396
1814
|
exports.useStorefront = useStorefront;
|
|
1397
1815
|
exports.useStorefrontClient = useStorefrontClient;
|
|
1816
|
+
exports.validateOpsSectionTypes = validateOpsSectionTypes;
|
|
1398
1817
|
exports.writeCartCookie = writeCartCookie;
|
|
1399
1818
|
//# sourceMappingURL=index.cjs.map
|
|
1400
1819
|
//# sourceMappingURL=index.cjs.map
|