@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.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
export { DEFAULT_IMAGE_WIDTHS, Image, Money, ProductPrice, defaultImageLoader } from './chunk-6CVKW7F3.js';
|
|
2
|
+
export { ThemeStyle, isSafeCssValue, themeFontsHref, themeToCss, themeToCssVariables, tokenName } from './chunk-672OTUM2.js';
|
|
3
|
+
import { useOptionalStorefront } from './chunk-DQBL2DFJ.js';
|
|
4
|
+
export { StorefrontProvider, useOptionalStorefront, useStorefront, useStorefrontClient } from './chunk-DQBL2DFJ.js';
|
|
3
5
|
import { createContext, useRef, useState, useCallback, useEffect, useMemo, useContext, Fragment as Fragment$1, useId } from 'react';
|
|
4
|
-
import {
|
|
6
|
+
import { resolveAvailability, productAvailability, availabilityView, availabilityMessages, formatAvailabilityMessage, cartLineProblemView, isStorefrontError, schemaAvailability, StorefrontError, formatMoney } from '@asteby/storefront';
|
|
5
7
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
6
8
|
import { toSchemaOpeningHours, isValidPoint, rankByDistance, openStatus, directionsUrl } from '@asteby/storefront/geo';
|
|
7
9
|
|
|
@@ -347,6 +349,7 @@ function useProductOptions(product, config = {}) {
|
|
|
347
349
|
exists: exact ? true : loose.length > 0 && Object.keys(selectedOptions).length < product.options.length,
|
|
348
350
|
available: exact ? exact.available_for_sale : Object.keys(selectedOptions).length < product.options.length && loose.some((v) => v.available_for_sale),
|
|
349
351
|
variant: exact,
|
|
352
|
+
availability: exact ? resolveAvailability(exact) : loose.length ? productAvailability({ variants: loose, available_for_sale: loose.some((v) => v.available_for_sale) }) : null,
|
|
350
353
|
search: `?${search.toString()}`
|
|
351
354
|
};
|
|
352
355
|
})
|
|
@@ -365,21 +368,32 @@ function toMerchandise(product, variant) {
|
|
|
365
368
|
function AddToCartButton({
|
|
366
369
|
variantId,
|
|
367
370
|
quantity = 1,
|
|
371
|
+
availability,
|
|
372
|
+
availabilitySource,
|
|
373
|
+
lowStockThreshold,
|
|
374
|
+
locale,
|
|
368
375
|
merchandise,
|
|
369
376
|
onAdded,
|
|
370
377
|
onClick,
|
|
371
378
|
disabled,
|
|
372
|
-
children
|
|
379
|
+
children,
|
|
373
380
|
type = "button",
|
|
374
381
|
...rest
|
|
375
382
|
}) {
|
|
376
383
|
const cart = useCart();
|
|
384
|
+
const sf = useOptionalStorefront();
|
|
385
|
+
const resolved = availability ?? (availabilitySource ? resolveAvailability(availabilitySource, { lowStockThreshold: lowStockThreshold ?? null }) : null);
|
|
386
|
+
const view = resolved ? availabilityView(resolved, locale ?? sf?.locale ?? "es-MX") : null;
|
|
387
|
+
const blocked = view ? !view.ctaEnabled : false;
|
|
388
|
+
const qty = view?.maxPerOrder != null ? Math.min(quantity, view.maxPerOrder) : quantity;
|
|
389
|
+
const label = children ?? view?.ctaLabel ?? "Agregar al carrito";
|
|
377
390
|
const [adding, setAdding] = useState(false);
|
|
378
391
|
return /* @__PURE__ */ jsx(
|
|
379
392
|
"button",
|
|
380
393
|
{
|
|
381
394
|
type,
|
|
382
|
-
disabled: disabled || !variantId || adding,
|
|
395
|
+
disabled: disabled || !variantId || adding || blocked,
|
|
396
|
+
"data-availability": view?.status,
|
|
383
397
|
"aria-busy": adding || void 0,
|
|
384
398
|
"data-adding": adding || void 0,
|
|
385
399
|
onClick: async (event) => {
|
|
@@ -387,17 +401,247 @@ function AddToCartButton({
|
|
|
387
401
|
if (event.defaultPrevented || !variantId) return;
|
|
388
402
|
setAdding(true);
|
|
389
403
|
try {
|
|
390
|
-
const result = await cart.addLines([{ variant_id: variantId, quantity, ...merchandise ? { merchandise } : {} }]);
|
|
404
|
+
const result = await cart.addLines([{ variant_id: variantId, quantity: qty, ...merchandise ? { merchandise } : {} }]);
|
|
391
405
|
if (result) onAdded?.();
|
|
392
406
|
} finally {
|
|
393
407
|
setAdding(false);
|
|
394
408
|
}
|
|
395
409
|
},
|
|
396
410
|
...rest,
|
|
397
|
-
children
|
|
411
|
+
children: label
|
|
398
412
|
}
|
|
399
413
|
);
|
|
400
414
|
}
|
|
415
|
+
var SR_ONLY = {
|
|
416
|
+
position: "absolute",
|
|
417
|
+
width: 1,
|
|
418
|
+
height: 1,
|
|
419
|
+
margin: -1,
|
|
420
|
+
padding: 0,
|
|
421
|
+
overflow: "hidden",
|
|
422
|
+
clip: "rect(0 0 0 0)",
|
|
423
|
+
whiteSpace: "nowrap",
|
|
424
|
+
border: 0
|
|
425
|
+
};
|
|
426
|
+
function SrOnly({ children }) {
|
|
427
|
+
return /* @__PURE__ */ jsx("span", { style: SR_ONLY, children });
|
|
428
|
+
}
|
|
429
|
+
function useLocale(locale) {
|
|
430
|
+
const sf = useOptionalStorefront();
|
|
431
|
+
return locale ?? sf?.locale ?? "es-MX";
|
|
432
|
+
}
|
|
433
|
+
function pickAvailability({ availability, source, lowStockThreshold }) {
|
|
434
|
+
const opts = { lowStockThreshold: lowStockThreshold ?? null };
|
|
435
|
+
if (availability) return availability;
|
|
436
|
+
return source ? resolveAvailability(source, opts) : null;
|
|
437
|
+
}
|
|
438
|
+
function useAvailability(input) {
|
|
439
|
+
const locale = useLocale(input.locale);
|
|
440
|
+
const a = pickAvailability(input);
|
|
441
|
+
return useMemo(
|
|
442
|
+
() => a ? availabilityView(a, locale, { messages: input.messages ?? null }) : null,
|
|
443
|
+
// `a` is rebuilt from props each render; key by its content
|
|
444
|
+
[JSON.stringify(a), locale, input.messages]
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
function AvailabilityBadge({
|
|
448
|
+
availability,
|
|
449
|
+
source,
|
|
450
|
+
lowStockThreshold,
|
|
451
|
+
locale,
|
|
452
|
+
messages,
|
|
453
|
+
placement = "detail",
|
|
454
|
+
live,
|
|
455
|
+
...rest
|
|
456
|
+
}) {
|
|
457
|
+
const view = useAvailability({ availability, source, lowStockThreshold, locale, messages });
|
|
458
|
+
const loc = useLocale(locale);
|
|
459
|
+
if (!view) return null;
|
|
460
|
+
if (placement === "card" && !view.showOnCard) return null;
|
|
461
|
+
const m = availabilityMessages(loc, messages);
|
|
462
|
+
return /* @__PURE__ */ jsxs(
|
|
463
|
+
"span",
|
|
464
|
+
{
|
|
465
|
+
"data-availability": view.status,
|
|
466
|
+
"data-tone": view.tone,
|
|
467
|
+
"data-placement": placement,
|
|
468
|
+
...live ? { role: "status" } : {},
|
|
469
|
+
...rest,
|
|
470
|
+
children: [
|
|
471
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": view.srText !== view.label ? true : void 0, children: view.label }),
|
|
472
|
+
view.srText !== view.label && /* @__PURE__ */ jsx(SrOnly, { children: view.srText }),
|
|
473
|
+
placement === "detail" && view.sublabel && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
474
|
+
" ",
|
|
475
|
+
/* @__PURE__ */ jsx("span", { "data-availability-note": "eta", children: view.sublabel })
|
|
476
|
+
] }),
|
|
477
|
+
placement === "detail" && view.maxPerOrder !== null && view.status !== "out_of_stock" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
478
|
+
" ",
|
|
479
|
+
/* @__PURE__ */ jsx("span", { "data-availability-note": "max", children: formatAvailabilityMessage(m.max_per_order, { n: view.maxPerOrder }) })
|
|
480
|
+
] })
|
|
481
|
+
]
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
function VariantOptionState({ children, availability, source, lowStockThreshold, locale, messages, ...rest }) {
|
|
486
|
+
const a = pickAvailability({ availability, source, lowStockThreshold });
|
|
487
|
+
const loc = useLocale(locale);
|
|
488
|
+
if (!a) return /* @__PURE__ */ jsx("span", { ...rest, children });
|
|
489
|
+
const m = availabilityMessages(loc, messages);
|
|
490
|
+
const status = availabilityView(a, loc, { messages: messages ?? null }).status;
|
|
491
|
+
if (status === "out_of_stock") {
|
|
492
|
+
return /* @__PURE__ */ jsxs("span", { "data-availability": status, ...rest, children: [
|
|
493
|
+
/* @__PURE__ */ jsx("s", { children }),
|
|
494
|
+
/* @__PURE__ */ jsxs(SrOnly, { children: [
|
|
495
|
+
" (",
|
|
496
|
+
m["option.sold_out"],
|
|
497
|
+
")"
|
|
498
|
+
] })
|
|
499
|
+
] });
|
|
500
|
+
}
|
|
501
|
+
if (status === "backorder" || status === "preorder") {
|
|
502
|
+
return /* @__PURE__ */ jsxs("span", { "data-availability": status, ...rest, children: [
|
|
503
|
+
children,
|
|
504
|
+
" ",
|
|
505
|
+
/* @__PURE__ */ jsxs("span", { "data-availability-tag": true, children: [
|
|
506
|
+
"(",
|
|
507
|
+
m[status === "backorder" ? "option.backorder" : "option.preorder"],
|
|
508
|
+
")"
|
|
509
|
+
] })
|
|
510
|
+
] });
|
|
511
|
+
}
|
|
512
|
+
return /* @__PURE__ */ jsx("span", { "data-availability": status, ...rest, children });
|
|
513
|
+
}
|
|
514
|
+
function RestockNotifyForm({
|
|
515
|
+
sku,
|
|
516
|
+
market,
|
|
517
|
+
allowPhone = false,
|
|
518
|
+
action,
|
|
519
|
+
client: clientProp,
|
|
520
|
+
enhance = true,
|
|
521
|
+
onSubscribed,
|
|
522
|
+
onError,
|
|
523
|
+
availability,
|
|
524
|
+
source,
|
|
525
|
+
lowStockThreshold,
|
|
526
|
+
locale,
|
|
527
|
+
messages,
|
|
528
|
+
...rest
|
|
529
|
+
}) {
|
|
530
|
+
const sf = useOptionalStorefront();
|
|
531
|
+
const view = useAvailability({ availability, source, lowStockThreshold, locale, messages });
|
|
532
|
+
const loc = useLocale(locale);
|
|
533
|
+
const id = useId();
|
|
534
|
+
const [state, setState] = useState("idle");
|
|
535
|
+
if (!view?.showNotify) return null;
|
|
536
|
+
const m = availabilityMessages(loc, messages);
|
|
537
|
+
const client = clientProp ?? sf?.client ?? null;
|
|
538
|
+
if (state === "done") {
|
|
539
|
+
return /* @__PURE__ */ jsx("p", { role: "status", "data-restock": "done", children: m["notify.done"] });
|
|
540
|
+
}
|
|
541
|
+
const submit = enhance && client ? (e) => {
|
|
542
|
+
e.preventDefault();
|
|
543
|
+
const form = new FormData(e.currentTarget);
|
|
544
|
+
const email = String(form.get("email") ?? "").trim();
|
|
545
|
+
const phone = String(form.get("phone") ?? "").trim();
|
|
546
|
+
if (!email && !phone) {
|
|
547
|
+
setState("error");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
setState("sending");
|
|
551
|
+
client.restock.subscribe({ sku, locale: loc, consent: true, ...email ? { email } : {}, ...phone ? { phone } : {}, ...market ? { market } : {} }).then(() => {
|
|
552
|
+
setState("done");
|
|
553
|
+
onSubscribed?.();
|
|
554
|
+
}).catch((err) => {
|
|
555
|
+
setState("error");
|
|
556
|
+
onError?.(err);
|
|
557
|
+
});
|
|
558
|
+
} : void 0;
|
|
559
|
+
return /* @__PURE__ */ jsx("form", { method: "post", ...action !== void 0 ? { action } : {}, ...rest, onSubmit: submit, "data-restock": state, children: /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
560
|
+
/* @__PURE__ */ jsx("legend", { children: m["notify.title"] }),
|
|
561
|
+
/* @__PURE__ */ jsx("input", { type: "hidden", name: "sku", value: sku }),
|
|
562
|
+
/* @__PURE__ */ jsx("input", { type: "hidden", name: "locale", value: loc }),
|
|
563
|
+
market && /* @__PURE__ */ jsx("input", { type: "hidden", name: "market", value: market }),
|
|
564
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
565
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${id}-email`, children: m["notify.email"] }),
|
|
566
|
+
" ",
|
|
567
|
+
/* @__PURE__ */ jsx("input", { id: `${id}-email`, name: "email", type: "email", autoComplete: "email", required: !allowPhone })
|
|
568
|
+
] }),
|
|
569
|
+
allowPhone && /* @__PURE__ */ jsxs("p", { children: [
|
|
570
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${id}-phone`, children: m["notify.phone"] }),
|
|
571
|
+
" ",
|
|
572
|
+
/* @__PURE__ */ jsx("input", { id: `${id}-phone`, name: "phone", type: "tel", autoComplete: "tel" })
|
|
573
|
+
] }),
|
|
574
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
575
|
+
/* @__PURE__ */ jsx("input", { id: `${id}-consent`, name: "consent", type: "checkbox", value: "true", required: true }),
|
|
576
|
+
" ",
|
|
577
|
+
/* @__PURE__ */ jsx("label", { htmlFor: `${id}-consent`, children: m["notify.consent"] })
|
|
578
|
+
] }),
|
|
579
|
+
/* @__PURE__ */ jsx("button", { type: "submit", disabled: state === "sending", "aria-busy": state === "sending" || void 0, children: m["notify.submit"] }),
|
|
580
|
+
state === "error" && /* @__PURE__ */ jsx("p", { role: "alert", children: m["notify.error"] })
|
|
581
|
+
] }) });
|
|
582
|
+
}
|
|
583
|
+
function CartLineProblem({ line, locale, messages, onAdjust, onRemove, ...rest }) {
|
|
584
|
+
const cart = useOptionalCart();
|
|
585
|
+
const loc = useLocale(locale);
|
|
586
|
+
if (!line.problem) return null;
|
|
587
|
+
const view = cartLineProblemView(line.problem, loc, { messages: messages ?? null, lineQuantity: line.quantity });
|
|
588
|
+
const adjust = onAdjust ?? (cart ? (id, q) => cart.updateLines([{ id, quantity: q }]) : void 0);
|
|
589
|
+
const remove = onRemove ?? (cart ? (id) => cart.removeLines([id]) : void 0);
|
|
590
|
+
return /* @__PURE__ */ jsxs("div", { "data-cart-line-problem": view.code, ...rest, children: [
|
|
591
|
+
/* @__PURE__ */ jsx("p", { children: view.message }),
|
|
592
|
+
view.adjustTo !== null && adjust && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void adjust(line.id, view.adjustTo), children: view.adjustLabel }),
|
|
593
|
+
remove && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void remove(line.id), children: view.removeLabel })
|
|
594
|
+
] });
|
|
595
|
+
}
|
|
596
|
+
function StockConflictAlert({
|
|
597
|
+
error: errorProp,
|
|
598
|
+
cart: cartProp,
|
|
599
|
+
locale,
|
|
600
|
+
messages,
|
|
601
|
+
onAdjust,
|
|
602
|
+
onRemove,
|
|
603
|
+
onDismiss,
|
|
604
|
+
autoFocus = true,
|
|
605
|
+
...rest
|
|
606
|
+
}) {
|
|
607
|
+
const cartCtx = useOptionalCart();
|
|
608
|
+
const loc = useLocale(locale);
|
|
609
|
+
const ref = useRef(null);
|
|
610
|
+
const error = errorProp !== void 0 ? errorProp : cartCtx?.error ?? null;
|
|
611
|
+
const cart = cartProp !== void 0 ? cartProp : cartCtx?.cart ?? null;
|
|
612
|
+
const active = isStorefrontError(error) && error.code === "insufficient_stock";
|
|
613
|
+
useEffect(() => {
|
|
614
|
+
if (active && autoFocus) ref.current?.focus({ preventScroll: true });
|
|
615
|
+
}, [active, error, autoFocus]);
|
|
616
|
+
if (!active) return null;
|
|
617
|
+
const m = availabilityMessages(loc, messages);
|
|
618
|
+
const lines = error.stockLines;
|
|
619
|
+
const adjust = onAdjust ?? (cartCtx ? (id, q) => cartCtx.updateLines([{ id, quantity: q }]) : void 0);
|
|
620
|
+
const remove = onRemove ?? (cartCtx ? (id) => cartCtx.removeLines([id]) : void 0);
|
|
621
|
+
return /* @__PURE__ */ jsxs("div", { ref, role: "alert", tabIndex: -1, "data-stock-conflict": true, ...rest, children: [
|
|
622
|
+
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: m["conflict.title"] }) }),
|
|
623
|
+
/* @__PURE__ */ jsx("p", { children: m["conflict.body"] }),
|
|
624
|
+
lines.length > 0 && /* @__PURE__ */ jsx("ul", { children: lines.map((l) => {
|
|
625
|
+
const cartLine = cart?.lines.find((x) => l.line_id ? x.id === l.line_id : x.merchandise.sku === l.sku);
|
|
626
|
+
const lineId = l.line_id ?? cartLine?.id ?? null;
|
|
627
|
+
const view = cartLineProblemView(l, loc, { messages: messages ?? null, ...cartLine ? { lineQuantity: cartLine.quantity } : {} });
|
|
628
|
+
return /* @__PURE__ */ jsxs("li", { "data-cart-line-problem": view.code, children: [
|
|
629
|
+
/* @__PURE__ */ jsx("span", { children: cartLine ? `${cartLine.merchandise.product.title}${cartLine.merchandise.title ? ` (${cartLine.merchandise.title})` : ""}` : l.sku }),
|
|
630
|
+
": ",
|
|
631
|
+
/* @__PURE__ */ jsx("span", { children: view.message }),
|
|
632
|
+
lineId && view.adjustTo !== null && adjust && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
633
|
+
" ",
|
|
634
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void adjust(lineId, view.adjustTo), children: view.adjustLabel })
|
|
635
|
+
] }),
|
|
636
|
+
lineId && remove && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
637
|
+
" ",
|
|
638
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void remove(lineId), children: view.removeLabel })
|
|
639
|
+
] })
|
|
640
|
+
] }, `${l.sku}:${l.line_id ?? ""}`);
|
|
641
|
+
}) }),
|
|
642
|
+
onDismiss && /* @__PURE__ */ jsx("button", { type: "button", onClick: onDismiss, children: m["conflict.dismiss"] })
|
|
643
|
+
] });
|
|
644
|
+
}
|
|
401
645
|
var clean = (s, max) => {
|
|
402
646
|
if (!s) return void 0;
|
|
403
647
|
const text = s.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -431,7 +675,8 @@ function seoMeta(input) {
|
|
|
431
675
|
function seoOf(seo, fallbackTitle, fallbackDescription) {
|
|
432
676
|
return { title: seo.title ?? fallbackTitle, description: seo.description ?? fallbackDescription ?? null };
|
|
433
677
|
}
|
|
434
|
-
|
|
678
|
+
var OG_AVAILABILITY = { in_stock: "in stock", low_stock: "in stock", out_of_stock: "out of stock", backorder: "backorder", preorder: "preorder" };
|
|
679
|
+
function productMeta(product, shop, url, options = {}) {
|
|
435
680
|
const { title, description } = seoOf(product.seo, product.title, product.description);
|
|
436
681
|
const meta = seoMeta({
|
|
437
682
|
title,
|
|
@@ -446,7 +691,7 @@ function productMeta(product, shop, url) {
|
|
|
446
691
|
meta.push(
|
|
447
692
|
{ property: "product:price:amount", content: product.price_range.min.amount },
|
|
448
693
|
{ property: "product:price:currency", content: product.price_range.min.currency_code },
|
|
449
|
-
{ property: "product:availability", content: product.
|
|
694
|
+
{ property: "product:availability", content: OG_AVAILABILITY[productAvailability(product, options).status] ?? "out of stock" }
|
|
450
695
|
);
|
|
451
696
|
if (product.vendor) meta.push({ property: "product:brand", content: product.vendor });
|
|
452
697
|
return meta;
|
|
@@ -483,22 +728,24 @@ function withVariant(url, variantId) {
|
|
|
483
728
|
return `${url}${url.includes("?") ? "&" : "?"}variant=${encodeURIComponent(variantId)}`;
|
|
484
729
|
}
|
|
485
730
|
}
|
|
486
|
-
function productJsonLd(product, shop, url) {
|
|
731
|
+
function productJsonLd(product, shop, url, options = {}) {
|
|
487
732
|
const images = product.images.length ? product.images.map((i) => i.url) : product.featured_image ? [product.featured_image.url] : [];
|
|
488
|
-
const offers = product.variants.map(
|
|
489
|
-
(v)
|
|
733
|
+
const offers = product.variants.map((v) => {
|
|
734
|
+
const availability = resolveAvailability(v, options);
|
|
735
|
+
return compact({
|
|
490
736
|
"@type": "Offer",
|
|
491
737
|
sku: v.sku,
|
|
492
738
|
gtin: v.barcode ?? void 0,
|
|
493
739
|
name: product.variants.length > 1 ? `${product.title} - ${v.title}` : void 0,
|
|
494
740
|
price: v.price.amount,
|
|
495
741
|
priceCurrency: v.price.currency_code,
|
|
496
|
-
availability:
|
|
742
|
+
availability: schemaAvailability(availability.status),
|
|
743
|
+
availabilityStarts: availability.status === "preorder" ? availability.preorder_release_at ?? void 0 : void 0,
|
|
497
744
|
itemCondition: "https://schema.org/NewCondition",
|
|
498
745
|
url: product.variants.length > 1 ? withVariant(url, v.id) : url,
|
|
499
746
|
seller: shop ? { "@type": "Organization", name: shop.name } : void 0
|
|
500
|
-
})
|
|
501
|
-
);
|
|
747
|
+
});
|
|
748
|
+
});
|
|
502
749
|
const additionalProperty = Object.entries(product.attributes).map(([name, value]) => ({ "@type": "PropertyValue", name, value }));
|
|
503
750
|
return compact({
|
|
504
751
|
"@context": CONTEXT,
|
|
@@ -610,6 +857,156 @@ function JsonLd({ data, nonce }) {
|
|
|
610
857
|
if (!data || Array.isArray(data) && data.length === 0) return null;
|
|
611
858
|
return /* @__PURE__ */ jsx("script", { type: "application/ld+json", nonce, dangerouslySetInnerHTML: { __html: serializeJsonLd(data) } });
|
|
612
859
|
}
|
|
860
|
+
var OPS_BUILTIN_SECTION_TYPES = ["hero", "product_grid", "collection_list", "rich_text", "faq", "image_with_text"];
|
|
861
|
+
var OPS_PROP_KINDS = ["text", "textarea", "richtext", "image", "link", "number", "select", "boolean", "collection", "collection_list", "product", "product_list", "list"];
|
|
862
|
+
var SectionTypesValidationError = class extends Error {
|
|
863
|
+
issues;
|
|
864
|
+
constructor(issues) {
|
|
865
|
+
super(`Invalid section types for OPS:
|
|
866
|
+
${issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`);
|
|
867
|
+
this.name = "SectionTypesValidationError";
|
|
868
|
+
this.issues = issues;
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
var SNAKE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|
|
872
|
+
var KIND_MAP = {
|
|
873
|
+
text: "text",
|
|
874
|
+
textarea: "textarea",
|
|
875
|
+
richtext: "richtext",
|
|
876
|
+
url: "text",
|
|
877
|
+
color: "text",
|
|
878
|
+
image: "image",
|
|
879
|
+
number: "number",
|
|
880
|
+
boolean: "boolean",
|
|
881
|
+
select: "select",
|
|
882
|
+
collection: "collection",
|
|
883
|
+
product: "product",
|
|
884
|
+
products: "product_list",
|
|
885
|
+
list: "list"
|
|
886
|
+
};
|
|
887
|
+
var LIST_KINDS = /* @__PURE__ */ new Set(["list"]);
|
|
888
|
+
function convertProps(schema, path, issues) {
|
|
889
|
+
const out = [];
|
|
890
|
+
for (const [key, spec] of Object.entries(schema ?? {})) {
|
|
891
|
+
const p = `${path}.${key}`;
|
|
892
|
+
const kind = KIND_MAP[spec.kind];
|
|
893
|
+
if (!kind) {
|
|
894
|
+
issues.push({ path: `${p}.kind`, message: `unknown kind "${String(spec.kind)}"` });
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const prop = { key, kind, label: spec.label };
|
|
898
|
+
if (spec.required) prop.required = true;
|
|
899
|
+
if ("default" in spec && spec.default !== void 0) prop.default = spec.default;
|
|
900
|
+
if (spec.options) prop.options = spec.options.map((o) => ({ value: o.value, label: o.label }));
|
|
901
|
+
if (spec.min !== void 0) prop.min = spec.min;
|
|
902
|
+
if (spec.max !== void 0) prop.max = spec.max;
|
|
903
|
+
if (LIST_KINDS.has(kind)) prop.fields = convertProps(spec.items, `${p}.fields`, issues);
|
|
904
|
+
out.push(prop);
|
|
905
|
+
}
|
|
906
|
+
return out;
|
|
907
|
+
}
|
|
908
|
+
function validateOpsSectionTypes(types) {
|
|
909
|
+
const issues = [];
|
|
910
|
+
const seen = /* @__PURE__ */ new Set();
|
|
911
|
+
const walk = (props, path) => {
|
|
912
|
+
if (!Array.isArray(props)) {
|
|
913
|
+
issues.push({ path, message: "must be a list" });
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const keys = /* @__PURE__ */ new Set();
|
|
917
|
+
props.forEach((prop, i) => {
|
|
918
|
+
const p = `${path}[${i}]`;
|
|
919
|
+
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, _)` });
|
|
920
|
+
else if (keys.has(prop.key)) issues.push({ path: `${p}.key`, message: `duplicate key "${prop.key}"` });
|
|
921
|
+
else keys.add(prop.key);
|
|
922
|
+
if (typeof prop.label !== "string" || !prop.label.trim()) issues.push({ path: `${p}.label`, message: "label is required" });
|
|
923
|
+
if (!OPS_PROP_KINDS.includes(prop.kind)) {
|
|
924
|
+
issues.push({ path: `${p}.kind`, message: `unknown kind "${String(prop.kind)}"` });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (prop.kind === "list") {
|
|
928
|
+
if (!prop.fields || prop.fields.length === 0) issues.push({ path: `${p}.fields`, message: 'list requires a non-empty "fields"' });
|
|
929
|
+
else walk(prop.fields, `${p}.fields`);
|
|
930
|
+
}
|
|
931
|
+
if (prop.kind === "select" && (!prop.options || prop.options.length === 0)) issues.push({ path: `${p}.options`, message: 'select requires non-empty "options"' });
|
|
932
|
+
if (prop.min !== void 0 && prop.max !== void 0 && prop.min > prop.max) issues.push({ path: `${p}.max`, message: "max must be >= min" });
|
|
933
|
+
});
|
|
934
|
+
};
|
|
935
|
+
types.forEach((t, i) => {
|
|
936
|
+
const p = t.type ? t.type : `[${i}]`;
|
|
937
|
+
if (!t.type || !t.type.trim()) issues.push({ path: `${p}.type`, message: "type is required" });
|
|
938
|
+
else if (seen.has(t.type)) issues.push({ path: `${p}.type`, message: `duplicate type "${t.type}"` });
|
|
939
|
+
else seen.add(t.type);
|
|
940
|
+
if (!t.label || !t.label.trim()) issues.push({ path: `${p}.label`, message: "label is required" });
|
|
941
|
+
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" });
|
|
942
|
+
walk(t.schema?.props, `${p}.schema.props`);
|
|
943
|
+
});
|
|
944
|
+
return issues;
|
|
945
|
+
}
|
|
946
|
+
function sectionTypesToOps(source, options = {}) {
|
|
947
|
+
const excludeBuiltin = options.excludeBuiltin ?? true;
|
|
948
|
+
const defs = Array.isArray(source) ? source : source.definitions();
|
|
949
|
+
const builtin = new Set(OPS_BUILTIN_SECTION_TYPES);
|
|
950
|
+
const issues = [];
|
|
951
|
+
const warnings = [];
|
|
952
|
+
const types = [];
|
|
953
|
+
for (const def of defs) {
|
|
954
|
+
if (excludeBuiltin && builtin.has(def.type)) {
|
|
955
|
+
const known = options.builtinProps?.[def.type];
|
|
956
|
+
if (known) {
|
|
957
|
+
for (const name of Object.keys(def.schema ?? {})) {
|
|
958
|
+
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` });
|
|
959
|
+
}
|
|
960
|
+
} else {
|
|
961
|
+
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` });
|
|
962
|
+
}
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
const entryIssues = [];
|
|
966
|
+
const props = convertProps(def.schema, `${def.type}.schema.props`, entryIssues);
|
|
967
|
+
issues.push(...entryIssues);
|
|
968
|
+
const out = { type: def.type, label: def.label ?? def.type, schema: { props } };
|
|
969
|
+
if (def.description) out.description = def.description;
|
|
970
|
+
if (def.category) out.category = def.category;
|
|
971
|
+
if (def.maxPerPage !== void 0) out.max_per_page = def.maxPerPage;
|
|
972
|
+
types.push(out);
|
|
973
|
+
}
|
|
974
|
+
issues.push(...validateOpsSectionTypes(types));
|
|
975
|
+
if (issues.length) throw new SectionTypesValidationError(issues);
|
|
976
|
+
return { types, warnings };
|
|
977
|
+
}
|
|
978
|
+
var SYNC_PATH = "/storefront/content/section-types";
|
|
979
|
+
var count = (v) => Array.isArray(v) ? v.length : typeof v === "number" ? v : 0;
|
|
980
|
+
async function syncSectionTypes(target, source, options = {}) {
|
|
981
|
+
const { types, warnings } = sectionTypesToOps(source, options);
|
|
982
|
+
let raw;
|
|
983
|
+
if ("request" in target && typeof target.request === "function") {
|
|
984
|
+
raw = await target.request({ method: "PUT", path: SYNC_PATH, body: { section_types: types } });
|
|
985
|
+
} else {
|
|
986
|
+
const t = target;
|
|
987
|
+
if (!t.token.startsWith("sk_")) throw new TypeError("syncSectionTypes: an sk_ token with content:write is required");
|
|
988
|
+
const f = t.fetch ?? globalThis.fetch.bind(globalThis);
|
|
989
|
+
const url = t.baseUrl.replace(/\/+$/, "") + SYNC_PATH;
|
|
990
|
+
const res = await f(url, {
|
|
991
|
+
method: "PUT",
|
|
992
|
+
headers: { Authorization: `Bearer ${t.token}`, "Content-Type": "application/json", Accept: "application/json" },
|
|
993
|
+
body: JSON.stringify({ section_types: types })
|
|
994
|
+
});
|
|
995
|
+
const text = await res.text();
|
|
996
|
+
let body;
|
|
997
|
+
try {
|
|
998
|
+
body = text ? JSON.parse(text) : void 0;
|
|
999
|
+
} catch {
|
|
1000
|
+
body = void 0;
|
|
1001
|
+
}
|
|
1002
|
+
if (!res.ok) {
|
|
1003
|
+
throw new StorefrontError({ status: res.status, code: body?.code ?? body?.error?.code ?? "unknown", message: body?.message ?? body?.error?.message ?? `Section types sync failed (${res.status})`, url });
|
|
1004
|
+
}
|
|
1005
|
+
raw = body;
|
|
1006
|
+
}
|
|
1007
|
+
const data = raw && typeof raw === "object" && "data" in raw ? raw.data : raw;
|
|
1008
|
+
return { created: count(data?.created), updated: count(data?.updated), warnings };
|
|
1009
|
+
}
|
|
613
1010
|
function defineSection(definition) {
|
|
614
1011
|
return definition;
|
|
615
1012
|
}
|
|
@@ -633,6 +1030,9 @@ function resolveSectionProps(schema, raw, path = "") {
|
|
|
633
1030
|
else if (spec.required) errors.push({ prop: key, message: "is required" });
|
|
634
1031
|
continue;
|
|
635
1032
|
}
|
|
1033
|
+
if (spec.kind === "image" && typeof value === "object" && value !== null && typeof value.url === "string") {
|
|
1034
|
+
value = value.url;
|
|
1035
|
+
}
|
|
636
1036
|
if (STRING_KINDS.has(spec.kind)) {
|
|
637
1037
|
if (typeof value !== "string") {
|
|
638
1038
|
fail2("must be a string");
|
|
@@ -756,6 +1156,14 @@ var SectionRegistry = class {
|
|
|
756
1156
|
toJSONSchema() {
|
|
757
1157
|
return Object.fromEntries(this.definitions().map((d) => [d.type, sectionJsonSchema(d)]));
|
|
758
1158
|
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Section types in the OPS `storefront_content` format, ready for
|
|
1161
|
+
* `PUT /storefront/content/section-types`. Throws `SectionTypesValidationError`
|
|
1162
|
+
* on anything OPS would reject; `warnings` flags built-in types.
|
|
1163
|
+
*/
|
|
1164
|
+
toOpsSectionTypes(options) {
|
|
1165
|
+
return sectionTypesToOps(this, options);
|
|
1166
|
+
}
|
|
759
1167
|
};
|
|
760
1168
|
function createSectionRegistry(definitions = []) {
|
|
761
1169
|
return new SectionRegistry(definitions);
|
|
@@ -783,59 +1191,6 @@ function Sections({ sections, registry, context, renderUnknown, onInvalid }) {
|
|
|
783
1191
|
return /* @__PURE__ */ jsx(Component, { ...props, section: { id: section.id, type: section.type }, context }, key);
|
|
784
1192
|
}) });
|
|
785
1193
|
}
|
|
786
|
-
var DEFAULT_FONT_FALLBACK = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
|
|
787
|
-
var MONO_FALLBACK = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
788
|
-
function tokenName(key) {
|
|
789
|
-
return key.trim().replace(/^-+/, "").replace(/^color-/, "").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.]+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
790
|
-
}
|
|
791
|
-
function isSafeCssValue(value) {
|
|
792
|
-
if (!value || value.length > 200) return false;
|
|
793
|
-
if (/[;{}<>\\]/.test(value)) return false;
|
|
794
|
-
if (/url\s*\(|expression\s*\(|@import/i.test(value)) return false;
|
|
795
|
-
return true;
|
|
796
|
-
}
|
|
797
|
-
function fontStack(family, role, fallback) {
|
|
798
|
-
const name = family.replace(/["']/g, "").trim();
|
|
799
|
-
if (!name || !isSafeCssValue(name)) return null;
|
|
800
|
-
return `"${name}", ${role === "mono" ? MONO_FALLBACK : fallback}`;
|
|
801
|
-
}
|
|
802
|
-
function themeToCssVariables(theme, options = {}) {
|
|
803
|
-
const naming = options.naming ?? "both";
|
|
804
|
-
const vars = {};
|
|
805
|
-
for (const [key, raw] of Object.entries(theme?.tokens ?? {})) {
|
|
806
|
-
const name = tokenName(key);
|
|
807
|
-
const value = String(raw).trim();
|
|
808
|
-
if (!name || !isSafeCssValue(value)) continue;
|
|
809
|
-
if (naming !== "metacore") vars[`--color-${name}`] = value;
|
|
810
|
-
if (naming !== "color") vars[`--${name}`] = value;
|
|
811
|
-
}
|
|
812
|
-
const fallback = options.fontFallback ?? DEFAULT_FONT_FALLBACK;
|
|
813
|
-
for (const [role, family] of Object.entries(theme?.fonts ?? {})) {
|
|
814
|
-
const name = tokenName(role);
|
|
815
|
-
const stack = typeof family === "string" ? fontStack(family, name, fallback) : null;
|
|
816
|
-
if (name && stack) vars[`--font-${name}`] = stack;
|
|
817
|
-
}
|
|
818
|
-
if (theme?.radius && isSafeCssValue(theme.radius.trim())) vars["--radius"] = theme.radius.trim();
|
|
819
|
-
return vars;
|
|
820
|
-
}
|
|
821
|
-
function themeToCss(theme, options = {}) {
|
|
822
|
-
const vars = themeToCssVariables(theme, options);
|
|
823
|
-
const body = Object.entries(vars).map(([k, v]) => `${k}:${v}`).join(";");
|
|
824
|
-
if (!body) return "";
|
|
825
|
-
const selector = (options.selector ?? ":root").replace(/[{}<>]/g, "");
|
|
826
|
-
return `${selector}{${body}}`;
|
|
827
|
-
}
|
|
828
|
-
function themeFontsHref(theme, weights = [400, 500, 600, 700]) {
|
|
829
|
-
const families = [...new Set(Object.values(theme?.fonts ?? {}).map((f) => String(f).replace(/["']/g, "").trim()).filter((f) => f && isSafeCssValue(f)))];
|
|
830
|
-
if (!families.length) return null;
|
|
831
|
-
const params = families.map((f) => `family=${encodeURIComponent(f).replace(/%20/g, "+")}:wght@${weights.join(";")}`).join("&");
|
|
832
|
-
return `https://fonts.googleapis.com/css2?${params}&display=swap`;
|
|
833
|
-
}
|
|
834
|
-
function ThemeStyle({ theme, nonce, id = "storefront-theme", ...options }) {
|
|
835
|
-
const css = themeToCss(theme, options);
|
|
836
|
-
if (!css) return null;
|
|
837
|
-
return /* @__PURE__ */ jsx("style", { id, nonce, "data-storefront-theme": "", dangerouslySetInnerHTML: { __html: css } });
|
|
838
|
-
}
|
|
839
1194
|
var MESSAGES = {
|
|
840
1195
|
unsupported: "Tu navegador no permite obtener tu ubicaci\xF3n.",
|
|
841
1196
|
insecure: "La ubicaci\xF3n solo est\xE1 disponible en conexiones seguras (HTTPS).",
|
|
@@ -1183,6 +1538,6 @@ function NearestLocationForm({
|
|
|
1183
1538
|
);
|
|
1184
1539
|
}
|
|
1185
1540
|
|
|
1186
|
-
export { AddToCartButton, CART_COOKIE_NAME, CartProvider, DirectionsLink, JsonLd, LocationList, LocationPicker, NearestLocationForm, OpenStatus, PickupOption, SELECTED_LOCATION_PARAM, SELECTED_LOCATION_STORAGE_KEY, SectionRegistry, Sections,
|
|
1541
|
+
export { AddToCartButton, AvailabilityBadge, CART_COOKIE_NAME, CartLineProblem, CartProvider, DirectionsLink, JsonLd, LocationList, LocationPicker, NearestLocationForm, OPS_BUILTIN_SECTION_TYPES, OPS_PROP_KINDS, OpenStatus, PickupOption, RestockNotifyForm, SELECTED_LOCATION_PARAM, SELECTED_LOCATION_STORAGE_KEY, SectionRegistry, SectionTypesValidationError, Sections, SrOnly, StockConflictAlert, VariantOptionState, VariantSelector, breadcrumbJsonLd, collectionJsonLd, collectionMeta, createSectionRegistry, defineSection, faqJsonLd, getCartIdFromCookie, localBusinessJsonLd, openingHoursJsonLd, optimisticCart, organizationJsonLd, productJsonLd, productMeta, readCartCookie, readSelectedLocationId, resolveSectionProps, sectionJsonSchema, sectionTypesToOps, seoMeta, serializeCartCookie, serializeJsonLd, syncSectionTypes, toMerchandise, useAvailability, useCart, useGeolocation, useNearestLocations, useOptionalCart, useProductOptions, useSelectedLocation, validateOpsSectionTypes, writeCartCookie };
|
|
1187
1542
|
//# sourceMappingURL=index.js.map
|
|
1188
1543
|
//# sourceMappingURL=index.js.map
|