@godaddy/gd-commerce-storefront 0.3.0 → 0.4.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 +51 -2
- package/dist/index.d.ts +120 -7
- package/dist/index.js +516 -243
- package/dist/styles.css +63 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Dialog from "@radix-ui/react-dialog";
|
|
2
|
-
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Children, createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Link, useParams, useSearchParams } from "react-router";
|
|
4
4
|
import { useQuery } from "@tanstack/react-query";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -148,6 +148,10 @@ function getProductAttributes(skuGroup) {
|
|
|
148
148
|
};
|
|
149
149
|
}) ?? [];
|
|
150
150
|
}
|
|
151
|
+
/** The product's display name: its label, then its name. */
|
|
152
|
+
function getProductName(skuGroup) {
|
|
153
|
+
return skuGroup.label ?? skuGroup.name ?? "Product";
|
|
154
|
+
}
|
|
151
155
|
|
|
152
156
|
//#endregion
|
|
153
157
|
//#region src/sample-cart.tsx
|
|
@@ -652,9 +656,9 @@ function CartDrawer() {
|
|
|
652
656
|
subtotal: item.totals?.subTotal?.value,
|
|
653
657
|
currency: item.totals?.subTotal?.currencyCode
|
|
654
658
|
}));
|
|
655
|
-
const summary = getCartSummaryTotals(cart);
|
|
659
|
+
const summary$1 = getCartSummaryTotals(cart);
|
|
656
660
|
const currency = isSample ? "USD" : cart?.totals?.total?.currencyCode ?? config.currencyCode;
|
|
657
|
-
const subtotal = isSample ? sample.items.reduce((sum, item) => sum + item.price * item.quantity, 0) : summary.subtotal;
|
|
661
|
+
const subtotal = isSample ? sample.items.reduce((sum, item) => sum + item.price * item.quantity, 0) : summary$1.subtotal;
|
|
658
662
|
async function handleAction(action, operation) {
|
|
659
663
|
if (locked) return;
|
|
660
664
|
setDrawerAction(action);
|
|
@@ -850,7 +854,35 @@ function CartDrawer() {
|
|
|
850
854
|
}
|
|
851
855
|
|
|
852
856
|
//#endregion
|
|
853
|
-
//#region src/
|
|
857
|
+
//#region src/part.ts
|
|
858
|
+
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
859
|
+
/** Classes for the element that receives a part's `className`. The package CSS reset skips `commerce-part`. */
|
|
860
|
+
const partClass = (...classes) => cx("commerce-part", ...classes);
|
|
861
|
+
/**
|
|
862
|
+
* Counts the parts mounted under a root, by key. Parts register in an effect, and a parent's effects run after its
|
|
863
|
+
* children's, so the root can read `has` in its own effect. `onChange` must be stable; it runs after each change.
|
|
864
|
+
*/
|
|
865
|
+
function usePartRegistry(onChange) {
|
|
866
|
+
const counts = useRef(/* @__PURE__ */ new Map());
|
|
867
|
+
const has = useCallback((key) => counts.current.has(key), []);
|
|
868
|
+
const register = useCallback((key) => {
|
|
869
|
+
counts.current.set(key, (counts.current.get(key) ?? 0) + 1);
|
|
870
|
+
onChange?.(has);
|
|
871
|
+
return () => {
|
|
872
|
+
const count = (counts.current.get(key) ?? 1) - 1;
|
|
873
|
+
if (count > 0) counts.current.set(key, count);
|
|
874
|
+
else counts.current.delete(key);
|
|
875
|
+
onChange?.(has);
|
|
876
|
+
};
|
|
877
|
+
}, [has, onChange]);
|
|
878
|
+
return useMemo(() => ({
|
|
879
|
+
register,
|
|
880
|
+
has
|
|
881
|
+
}), [register, has]);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/product-card.tsx
|
|
854
886
|
function ProductImage({ url, name, className = "" }) {
|
|
855
887
|
const [failed, setFailed] = useState(false);
|
|
856
888
|
return url && !failed ? /* @__PURE__ */ jsx("img", {
|
|
@@ -866,74 +898,257 @@ function ProductImage({ url, name, className = "" }) {
|
|
|
866
898
|
children: "No image available"
|
|
867
899
|
});
|
|
868
900
|
}
|
|
869
|
-
|
|
870
|
-
|
|
901
|
+
/** Set by `CatalogProducts` for each entry it renders, so the card template needs no product prop. */
|
|
902
|
+
const CatalogItemContext = createContext(null);
|
|
903
|
+
const titleLink = "title-link";
|
|
904
|
+
function cardValue(item, productPath, links) {
|
|
905
|
+
if (item.source === "sample") return {
|
|
906
|
+
...links,
|
|
907
|
+
source: "sample",
|
|
908
|
+
sample: item.sample,
|
|
909
|
+
product: null,
|
|
910
|
+
name: item.sample.name,
|
|
911
|
+
href: null,
|
|
912
|
+
isSample: true
|
|
913
|
+
};
|
|
914
|
+
return {
|
|
915
|
+
...links,
|
|
916
|
+
source: "live",
|
|
917
|
+
product: item.product,
|
|
918
|
+
name: getProductName(item.product),
|
|
919
|
+
href: `${productPath}/${encodeURIComponent(item.product.id ?? "")}`,
|
|
920
|
+
isSample: false
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
const ProductCardContext = createContext(null);
|
|
924
|
+
function useProductCardInternal(part) {
|
|
925
|
+
const context = useContext(ProductCardContext);
|
|
926
|
+
if (!context) throw new Error(`${part} must be rendered inside <ProductCardRoot>.`);
|
|
927
|
+
return context;
|
|
928
|
+
}
|
|
929
|
+
/** Read the card's product, name, and link from inside `ProductCardRoot`. */
|
|
930
|
+
function useProductCard() {
|
|
931
|
+
const { product, name, href, isSample } = useProductCardInternal("useProductCard");
|
|
932
|
+
return useMemo(() => ({
|
|
933
|
+
product,
|
|
934
|
+
name,
|
|
935
|
+
href,
|
|
936
|
+
isSample
|
|
937
|
+
}), [
|
|
938
|
+
product,
|
|
939
|
+
name,
|
|
940
|
+
href,
|
|
941
|
+
isSample
|
|
942
|
+
]);
|
|
871
943
|
}
|
|
872
|
-
|
|
944
|
+
/** Provides one product to card parts. Inside `CatalogProducts` it renders the current catalog entry. */
|
|
945
|
+
function ProductCardRoot({ children, product, className }) {
|
|
946
|
+
const catalogItem = useContext(CatalogItemContext);
|
|
873
947
|
const { config } = useCommerce();
|
|
874
|
-
const
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
948
|
+
const [imageLinked, setImageLinked] = useState(false);
|
|
949
|
+
const { register, has } = usePartRegistry(useCallback((registered) => setImageLinked(!registered(titleLink)), []));
|
|
950
|
+
useEffect(() => {
|
|
951
|
+
setImageLinked(!has(titleLink));
|
|
952
|
+
}, [has]);
|
|
953
|
+
const value = useMemo(() => {
|
|
954
|
+
const item = catalogItem ?? (product ? {
|
|
955
|
+
source: "live",
|
|
956
|
+
product
|
|
957
|
+
} : null);
|
|
958
|
+
return item && cardValue(item, config.productPath, {
|
|
959
|
+
imageLinked,
|
|
960
|
+
register
|
|
961
|
+
});
|
|
962
|
+
}, [
|
|
963
|
+
catalogItem,
|
|
964
|
+
product,
|
|
965
|
+
config.productPath,
|
|
966
|
+
imageLinked,
|
|
967
|
+
register
|
|
968
|
+
]);
|
|
969
|
+
if (catalogItem && product) throw new Error("Inside <CatalogProducts>, ProductCardRoot shows each catalog product. Remove its product prop.");
|
|
970
|
+
if (!value) throw new Error("ProductCardRoot needs a product prop outside <CatalogProducts>.");
|
|
971
|
+
const card = /* @__PURE__ */ jsx(ProductCardContext.Provider, {
|
|
972
|
+
value,
|
|
973
|
+
children: /* @__PURE__ */ jsx("article", {
|
|
974
|
+
className: partClass("group", className),
|
|
975
|
+
children: /* @__PURE__ */ jsx(CatalogItemContext.Provider, {
|
|
976
|
+
value: null,
|
|
977
|
+
children
|
|
978
|
+
})
|
|
979
|
+
})
|
|
980
|
+
});
|
|
981
|
+
return catalogItem ? card : /* @__PURE__ */ jsx(StorefrontSurface, { children: card });
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* The product's first image, linked to its page. Samples show a placeholder. With a `ProductCardTitle` in the card,
|
|
985
|
+
* the title is the one link keyboard and screen reader users get; without one, the image link is.
|
|
986
|
+
*/
|
|
987
|
+
function ProductCardImage({ className }) {
|
|
988
|
+
const card = useProductCardInternal("ProductCardImage");
|
|
989
|
+
return /* @__PURE__ */ jsx("div", {
|
|
990
|
+
className: partClass(className),
|
|
991
|
+
children: card.source === "live" ? /* @__PURE__ */ jsx(Link, {
|
|
992
|
+
to: card.href,
|
|
993
|
+
tabIndex: card.imageLinked ? void 0 : -1,
|
|
994
|
+
"aria-hidden": card.imageLinked ? void 0 : "true",
|
|
995
|
+
className: "block aspect-square overflow-hidden focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-4",
|
|
888
996
|
children: /* @__PURE__ */ jsx(ProductImage, {
|
|
889
|
-
url: getPrimaryImageUrl(product),
|
|
890
|
-
name,
|
|
997
|
+
url: getPrimaryImageUrl(card.product),
|
|
998
|
+
name: card.name,
|
|
891
999
|
className: "motion-safe:transition-transform motion-safe:duration-200 motion-safe:group-hover:scale-105"
|
|
892
1000
|
})
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1001
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1002
|
+
"aria-hidden": "true",
|
|
1003
|
+
className: "flex aspect-square items-center justify-center bg-neutral-100",
|
|
1004
|
+
children: /* @__PURE__ */ jsx("div", { className: "h-20 w-20 rounded-lg border border-neutral-300" })
|
|
1005
|
+
})
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
/** The product name as an H2, linked to its page. */
|
|
1009
|
+
function ProductCardTitle({ className }) {
|
|
1010
|
+
const { name, href, register } = useProductCardInternal("ProductCardTitle");
|
|
1011
|
+
useEffect(() => href ? register(titleLink) : void 0, [href, register]);
|
|
1012
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1013
|
+
className: partClass(className),
|
|
1014
|
+
children: /* @__PURE__ */ jsx("h2", {
|
|
1015
|
+
className: "break-words text-2xl font-semibold leading-tight",
|
|
1016
|
+
children: href ? /* @__PURE__ */ jsx(Link, {
|
|
1017
|
+
className: "rounded text-inherit hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
1018
|
+
to: href,
|
|
1019
|
+
children: name
|
|
1020
|
+
}) : name
|
|
1021
|
+
})
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
/** Up to three lines of the product description. Renders nothing without one. */
|
|
1025
|
+
function ProductCardDescription({ className }) {
|
|
1026
|
+
const { product } = useProductCardInternal("ProductCardDescription");
|
|
1027
|
+
if (!product?.description) return null;
|
|
1028
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1029
|
+
className: partClass(className),
|
|
1030
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1031
|
+
className: "line-clamp-3 text-sm leading-6",
|
|
1032
|
+
children: product.description
|
|
1033
|
+
})
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
/** The product's price, or its price range when variants differ. */
|
|
1037
|
+
function ProductCardPrice({ className }) {
|
|
1038
|
+
const card = useProductCardInternal("ProductCardPrice");
|
|
1039
|
+
const { config } = useCommerce();
|
|
1040
|
+
let text;
|
|
1041
|
+
if (card.source === "sample") text = money(card.sample.price, "USD");
|
|
1042
|
+
else {
|
|
1043
|
+
const min = card.product.priceRange?.min;
|
|
1044
|
+
const max = card.product.priceRange?.max;
|
|
1045
|
+
text = typeof min === "number" ? `${money(min, config.currencyCode)}${typeof max === "number" && max !== min ? ` – ${money(max, config.currencyCode)}` : ""}` : "Price unavailable";
|
|
1046
|
+
}
|
|
1047
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1048
|
+
className: partClass(className),
|
|
1049
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1050
|
+
className: "text-lg font-semibold leading-7",
|
|
1051
|
+
children: text
|
|
1052
|
+
})
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Adds the product directly when it has exactly one SKU and no options; otherwise links to its page to choose.
|
|
1057
|
+
* Samples add to the sample cart.
|
|
1058
|
+
*/
|
|
1059
|
+
function ProductCardAction({ className }) {
|
|
1060
|
+
const card = useProductCardInternal("ProductCardAction");
|
|
1061
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1062
|
+
className: partClass(className),
|
|
1063
|
+
children: card.source === "live" ? /* @__PURE__ */ jsx(LiveAction, {
|
|
1064
|
+
product: card.product,
|
|
1065
|
+
name: card.name,
|
|
1066
|
+
href: card.href
|
|
1067
|
+
}) : /* @__PURE__ */ jsx(SampleAction, { sample: card.sample })
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
function LiveAction({ product, name, href }) {
|
|
1071
|
+
const selectedSku = getSingleMatchedSku(product);
|
|
1072
|
+
const hasVariants = getProductAttributes(product).length > 0;
|
|
1073
|
+
if (!hasVariants && selectedSku) return /* @__PURE__ */ jsx(AddToCartButton, {
|
|
1074
|
+
sku: selectedSku,
|
|
1075
|
+
name
|
|
1076
|
+
});
|
|
1077
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
1078
|
+
className: buttonClass,
|
|
1079
|
+
to: href,
|
|
1080
|
+
children: hasVariants ? "Choose options" : "View product"
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
function SampleAction({ sample }) {
|
|
1084
|
+
const { add } = useSampleCart();
|
|
1085
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1086
|
+
type: "button",
|
|
1087
|
+
className: buttonClass,
|
|
1088
|
+
"aria-label": `Add ${sample.name} to sample cart`,
|
|
1089
|
+
onClick: () => add(sample.key),
|
|
1090
|
+
children: "Add to cart"
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
const packagedCardClass = "flex min-w-0 flex-col overflow-hidden rounded-lg border border-neutral-200";
|
|
1094
|
+
/** The packaged card's parts, inside a root that has `packagedCardClass`. */
|
|
1095
|
+
function PackagedCardParts() {
|
|
1096
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ProductCardImage, {}), /* @__PURE__ */ jsxs("div", {
|
|
1097
|
+
className: "flex flex-1 flex-col p-6",
|
|
1098
|
+
children: [
|
|
1099
|
+
/* @__PURE__ */ jsx(ProductCardTitle, {}),
|
|
1100
|
+
/* @__PURE__ */ jsx(ProductCardDescription, { className: "mt-3" }),
|
|
1101
|
+
/* @__PURE__ */ jsx("div", {
|
|
1102
|
+
className: "mt-auto pt-6",
|
|
1103
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1104
|
+
className: "flex flex-wrap items-end justify-between gap-4 border-t border-neutral-200 pt-5",
|
|
1105
|
+
children: [/* @__PURE__ */ jsx(ProductCardPrice, { className: "min-w-0 flex-1" }), /* @__PURE__ */ jsx(ProductCardAction, { className: "max-w-full" })]
|
|
927
1106
|
})
|
|
928
|
-
|
|
929
|
-
|
|
1107
|
+
})
|
|
1108
|
+
]
|
|
1109
|
+
})] });
|
|
1110
|
+
}
|
|
1111
|
+
/** The packaged card for one `SKUGroup`. Compose `ProductCardRoot` and the card parts for a custom card. */
|
|
1112
|
+
function ProductCard({ product }) {
|
|
1113
|
+
return /* @__PURE__ */ jsx(ProductCardRoot, {
|
|
1114
|
+
product,
|
|
1115
|
+
className: packagedCardClass,
|
|
1116
|
+
children: /* @__PURE__ */ jsx(PackagedCardParts, {})
|
|
930
1117
|
});
|
|
931
1118
|
}
|
|
932
|
-
|
|
1119
|
+
|
|
1120
|
+
//#endregion
|
|
1121
|
+
//#region src/catalog.tsx
|
|
1122
|
+
const CatalogContext = createContext(null);
|
|
1123
|
+
const sampleCatalog = { source: "sample" };
|
|
1124
|
+
function useCatalogInternal(part) {
|
|
1125
|
+
const context = useContext(CatalogContext);
|
|
1126
|
+
if (!context) throw new Error(`${part} must be rendered inside <CatalogRoot>.`);
|
|
1127
|
+
return context;
|
|
1128
|
+
}
|
|
1129
|
+
/** Loads one cursor page of products, or the built-in samples, and provides them to catalog parts. */
|
|
1130
|
+
function CatalogRoot({ children, className, sampleProducts: sampleProducts$1 = false }) {
|
|
933
1131
|
const { connection } = useCommerce();
|
|
934
|
-
|
|
1132
|
+
let content;
|
|
1133
|
+
if (connection === "ready") content = /* @__PURE__ */ jsx(CatalogLoader, {
|
|
1134
|
+
className,
|
|
1135
|
+
children
|
|
1136
|
+
});
|
|
1137
|
+
else if (connection === "unbound" && sampleProducts$1) content = /* @__PURE__ */ jsx(CatalogContext.Provider, {
|
|
1138
|
+
value: sampleCatalog,
|
|
1139
|
+
children: /* @__PURE__ */ jsx("section", {
|
|
1140
|
+
className: partClass(className),
|
|
1141
|
+
"data-commerce-source": "sample",
|
|
1142
|
+
children
|
|
1143
|
+
})
|
|
1144
|
+
});
|
|
1145
|
+
else content = /* @__PURE__ */ jsx("div", {
|
|
1146
|
+
className: partClass(className),
|
|
1147
|
+
children: /* @__PURE__ */ jsx(CommerceStatus, {})
|
|
1148
|
+
});
|
|
1149
|
+
return /* @__PURE__ */ jsx(StorefrontSurface, { children: content });
|
|
935
1150
|
}
|
|
936
|
-
function
|
|
1151
|
+
function CatalogLoader({ children, className }) {
|
|
937
1152
|
const { config } = useCommerce();
|
|
938
1153
|
const [params, setParams] = useSearchParams();
|
|
939
1154
|
const after = params.get("after") ?? "";
|
|
@@ -952,34 +1167,94 @@ function CatalogContent({ title = "Shop all products", description = "Explore th
|
|
|
952
1167
|
headers: { "X-Commerce-Scope": config.cartScope }
|
|
953
1168
|
})
|
|
954
1169
|
});
|
|
955
|
-
|
|
1170
|
+
const goToPage = useCallback((cursor) => {
|
|
956
1171
|
const next = new URLSearchParams(params);
|
|
957
1172
|
if (cursor) next.set("after", cursor);
|
|
958
1173
|
else next.delete("after");
|
|
959
1174
|
setParams(next);
|
|
1175
|
+
}, [params, setParams]);
|
|
1176
|
+
const nodes = useMemo(() => products.data?.skuGroups?.edges?.flatMap((edge) => edge?.node?.id ? [edge.node] : []) ?? [], [products.data]);
|
|
1177
|
+
const value = useMemo(() => ({
|
|
1178
|
+
source: "live",
|
|
1179
|
+
products,
|
|
1180
|
+
nodes,
|
|
1181
|
+
after,
|
|
1182
|
+
goToPage
|
|
1183
|
+
}), [
|
|
1184
|
+
products,
|
|
1185
|
+
nodes,
|
|
1186
|
+
after,
|
|
1187
|
+
goToPage
|
|
1188
|
+
]);
|
|
1189
|
+
return /* @__PURE__ */ jsx(CatalogContext.Provider, {
|
|
1190
|
+
value,
|
|
1191
|
+
children: /* @__PURE__ */ jsx("section", {
|
|
1192
|
+
className: partClass(className),
|
|
1193
|
+
children
|
|
1194
|
+
})
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
/** The catalog title as the page's H1, with a description. */
|
|
1198
|
+
function CatalogHeader({ title = "Shop all products", description = "Explore the collection and find your favorites.", className }) {
|
|
1199
|
+
useCatalogInternal("CatalogHeader");
|
|
1200
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
1201
|
+
className: partClass(className),
|
|
1202
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
1203
|
+
className: "max-w-3xl text-4xl font-semibold leading-tight sm:text-5xl lg:text-6xl",
|
|
1204
|
+
children: title
|
|
1205
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1206
|
+
className: "mt-5 max-w-xl text-base leading-7 sm:text-lg",
|
|
1207
|
+
children: description
|
|
1208
|
+
})]
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
/** Announces loading, failure, or how many products the page shows. */
|
|
1212
|
+
function CatalogSummary({ className }) {
|
|
1213
|
+
const catalog = useCatalogInternal("CatalogSummary");
|
|
1214
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1215
|
+
className: partClass(className),
|
|
1216
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1217
|
+
className: "text-sm",
|
|
1218
|
+
role: "status",
|
|
1219
|
+
children: summary(catalog)
|
|
1220
|
+
})
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
function summary(catalog) {
|
|
1224
|
+
if (catalog.source === "sample") return "Sample products · Example prices in USD";
|
|
1225
|
+
if (catalog.products.isPending) return "Loading products…";
|
|
1226
|
+
if (catalog.products.isError) return "Products unavailable";
|
|
1227
|
+
return `Showing ${catalog.nodes.length} product${catalog.nodes.length === 1 ? "" : "s"}`;
|
|
1228
|
+
}
|
|
1229
|
+
const sampleItems = sampleProducts.map((sample) => ({
|
|
1230
|
+
key: sample.key,
|
|
1231
|
+
item: {
|
|
1232
|
+
source: "sample",
|
|
1233
|
+
sample
|
|
960
1234
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1235
|
+
}));
|
|
1236
|
+
const packagedCard = /* @__PURE__ */ jsx(ProductCardRoot, {
|
|
1237
|
+
className: packagedCardClass,
|
|
1238
|
+
children: /* @__PURE__ */ jsx(PackagedCardParts, {})
|
|
1239
|
+
});
|
|
1240
|
+
/**
|
|
1241
|
+
* The product cards. `className` goes on the element that contains the cards, such as a grid. The loading
|
|
1242
|
+
* placeholder, the error with its retry, and the empty message render just before that element.
|
|
1243
|
+
*/
|
|
1244
|
+
function CatalogProducts({ className, children }) {
|
|
1245
|
+
const catalog = useCatalogInternal("CatalogProducts");
|
|
1246
|
+
const card = Children.toArray(children).some((child) => child !== "") ? children : packagedCard;
|
|
1247
|
+
const nodes = catalog.source === "live" ? catalog.nodes : null;
|
|
1248
|
+
const items = useMemo(() => nodes ? nodes.map((product) => ({
|
|
1249
|
+
key: product.id ?? "",
|
|
1250
|
+
item: {
|
|
1251
|
+
source: "live",
|
|
1252
|
+
product
|
|
1253
|
+
}
|
|
1254
|
+
})) : sampleItems, [nodes]);
|
|
1255
|
+
const products = catalog.source === "live" ? catalog.products : null;
|
|
1256
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1257
|
+
products?.isPending && /* @__PURE__ */ jsx("div", {
|
|
983
1258
|
"aria-hidden": "true",
|
|
984
1259
|
className: "grid grid-cols-1 gap-6 py-8 sm:grid-cols-2 lg:grid-cols-3",
|
|
985
1260
|
children: [
|
|
@@ -998,7 +1273,7 @@ function CatalogContent({ title = "Shop all products", description = "Explore th
|
|
|
998
1273
|
})]
|
|
999
1274
|
}, item))
|
|
1000
1275
|
}),
|
|
1001
|
-
products
|
|
1276
|
+
products?.isError && /* @__PURE__ */ jsxs("div", {
|
|
1002
1277
|
role: "alert",
|
|
1003
1278
|
className: "rounded-lg bg-red-50 p-5 text-red-800",
|
|
1004
1279
|
children: [/* @__PURE__ */ jsx("p", { children: message(products.error) }), /* @__PURE__ */ jsx("button", {
|
|
@@ -1008,17 +1283,30 @@ function CatalogContent({ title = "Shop all products", description = "Explore th
|
|
|
1008
1283
|
children: "Retry products"
|
|
1009
1284
|
})]
|
|
1010
1285
|
}),
|
|
1011
|
-
products
|
|
1286
|
+
products?.isSuccess && !items.length && /* @__PURE__ */ jsx("p", {
|
|
1012
1287
|
className: "py-16 text-center",
|
|
1013
1288
|
children: "No products available."
|
|
1014
1289
|
}),
|
|
1015
1290
|
/* @__PURE__ */ jsx("div", {
|
|
1016
|
-
className:
|
|
1017
|
-
children:
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1291
|
+
className: partClass(className),
|
|
1292
|
+
children: items.map(({ key, item }) => /* @__PURE__ */ jsx(CatalogItemContext.Provider, {
|
|
1293
|
+
value: item,
|
|
1294
|
+
children: card
|
|
1295
|
+
}, key))
|
|
1296
|
+
})
|
|
1297
|
+
] });
|
|
1298
|
+
}
|
|
1299
|
+
/** First-page and next-page buttons. Renders nothing for samples, which have one page. */
|
|
1300
|
+
function CatalogPagination({ className }) {
|
|
1301
|
+
const catalog = useCatalogInternal("CatalogPagination");
|
|
1302
|
+
if (catalog.source === "sample") return null;
|
|
1303
|
+
const { after, goToPage } = catalog;
|
|
1304
|
+
const pageInfo = catalog.products.data?.skuGroups?.pageInfo;
|
|
1305
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
1306
|
+
"aria-label": "Product pages",
|
|
1307
|
+
className: partClass(className),
|
|
1308
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1309
|
+
className: "flex justify-between",
|
|
1022
1310
|
children: [after ? /* @__PURE__ */ jsx("button", {
|
|
1023
1311
|
type: "button",
|
|
1024
1312
|
className: buttonClass,
|
|
@@ -1031,60 +1319,21 @@ function CatalogContent({ title = "Shop all products", description = "Explore th
|
|
|
1031
1319
|
children: "Next page"
|
|
1032
1320
|
})]
|
|
1033
1321
|
})
|
|
1034
|
-
|
|
1322
|
+
});
|
|
1035
1323
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
return /* @__PURE__ */ jsxs(
|
|
1039
|
-
|
|
1324
|
+
/** The packaged catalog. Compose `CatalogRoot` and the catalog parts for a custom layout. */
|
|
1325
|
+
function Catalog({ title, description, showHeader = true, sampleProducts: sampleProducts$1 }) {
|
|
1326
|
+
return /* @__PURE__ */ jsxs(CatalogRoot, {
|
|
1327
|
+
sampleProducts: sampleProducts$1,
|
|
1040
1328
|
children: [
|
|
1041
|
-
showHeader && /* @__PURE__ */
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
children: title
|
|
1046
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
1047
|
-
className: "mt-5 max-w-xl text-base leading-7 sm:text-lg",
|
|
1048
|
-
children: description
|
|
1049
|
-
})]
|
|
1329
|
+
showHeader && /* @__PURE__ */ jsx(CatalogHeader, {
|
|
1330
|
+
title,
|
|
1331
|
+
description,
|
|
1332
|
+
className: "border-b border-neutral-200 pb-10 pt-4 sm:pb-14 sm:pt-8"
|
|
1050
1333
|
}),
|
|
1051
|
-
/* @__PURE__ */ jsx("
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
children: "Sample products · Example prices in USD"
|
|
1055
|
-
}),
|
|
1056
|
-
/* @__PURE__ */ jsx("div", {
|
|
1057
|
-
className: "grid grid-cols-1 gap-6 py-8 sm:grid-cols-2 lg:grid-cols-3 lg:gap-8 lg:py-10",
|
|
1058
|
-
children: sampleProducts.map((product) => /* @__PURE__ */ jsxs("article", {
|
|
1059
|
-
className: "flex min-w-0 flex-col overflow-hidden rounded-lg border border-neutral-200",
|
|
1060
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1061
|
-
"aria-hidden": "true",
|
|
1062
|
-
className: "flex aspect-square items-center justify-center bg-neutral-100",
|
|
1063
|
-
children: /* @__PURE__ */ jsx("div", { className: "h-20 w-20 rounded-lg border border-neutral-300" })
|
|
1064
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
1065
|
-
className: "flex flex-1 flex-col p-6",
|
|
1066
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
1067
|
-
className: "break-words text-2xl font-semibold leading-tight",
|
|
1068
|
-
children: product.name
|
|
1069
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1070
|
-
className: "mt-auto pt-6",
|
|
1071
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1072
|
-
className: "flex flex-wrap items-end justify-between gap-4 border-t border-neutral-200 pt-5",
|
|
1073
|
-
children: [/* @__PURE__ */ jsx("p", {
|
|
1074
|
-
className: "text-lg font-semibold leading-7",
|
|
1075
|
-
children: money(product.price, "USD")
|
|
1076
|
-
}), /* @__PURE__ */ jsx("button", {
|
|
1077
|
-
type: "button",
|
|
1078
|
-
className: buttonClass,
|
|
1079
|
-
"aria-label": `Add ${product.name} to sample cart`,
|
|
1080
|
-
onClick: () => add(product.key),
|
|
1081
|
-
children: "Add to cart"
|
|
1082
|
-
})]
|
|
1083
|
-
})
|
|
1084
|
-
})]
|
|
1085
|
-
})]
|
|
1086
|
-
}, product.key))
|
|
1087
|
-
})
|
|
1334
|
+
/* @__PURE__ */ jsx(CatalogSummary, { className: "border-b border-neutral-200 py-6 sm:py-8" }),
|
|
1335
|
+
/* @__PURE__ */ jsx(CatalogProducts, { className: "grid grid-cols-1 gap-6 py-8 sm:grid-cols-2 lg:grid-cols-3 lg:gap-8 lg:py-10" }),
|
|
1336
|
+
/* @__PURE__ */ jsx(CatalogPagination, { className: "border-t border-neutral-200 pt-6" })
|
|
1088
1337
|
]
|
|
1089
1338
|
});
|
|
1090
1339
|
}
|
|
@@ -1101,9 +1350,6 @@ function CommerceStorefront({ children,...props }) {
|
|
|
1101
1350
|
|
|
1102
1351
|
//#endregion
|
|
1103
1352
|
//#region src/product-details-context.tsx
|
|
1104
|
-
const cx = (...classes) => classes.filter(Boolean).join(" ");
|
|
1105
|
-
/** Classes for the element that receives a part's `className`. The package CSS reset skips `commerce-part`. */
|
|
1106
|
-
const partClass = (...classes) => cx("commerce-part", ...classes);
|
|
1107
1353
|
const ProductDetailsContext = createContext(null);
|
|
1108
1354
|
function useProductDetailsInternal(part) {
|
|
1109
1355
|
const context = useContext(ProductDetailsContext);
|
|
@@ -1162,7 +1408,10 @@ function ProductDetailsRoot({ children, className }) {
|
|
|
1162
1408
|
productId,
|
|
1163
1409
|
className,
|
|
1164
1410
|
children
|
|
1165
|
-
}, productId) : /* @__PURE__ */ jsx(
|
|
1411
|
+
}, productId) : /* @__PURE__ */ jsx("div", {
|
|
1412
|
+
className: partClass(className),
|
|
1413
|
+
children: /* @__PURE__ */ jsx(CommerceStatus, {})
|
|
1414
|
+
}) });
|
|
1166
1415
|
}
|
|
1167
1416
|
function ProductDetailsLoader({ productId, className, children }) {
|
|
1168
1417
|
const { config } = useCommerce();
|
|
@@ -1221,29 +1470,24 @@ function ProductDetailsLoader({ productId, className, children }) {
|
|
|
1221
1470
|
setQuantity(1);
|
|
1222
1471
|
setImageIndex(0);
|
|
1223
1472
|
};
|
|
1224
|
-
const
|
|
1225
|
-
const register = useCallback((control) => {
|
|
1226
|
-
const counts = registered.current;
|
|
1227
|
-
counts.set(control, (counts.get(control) ?? 0) + 1);
|
|
1228
|
-
return () => {
|
|
1229
|
-
const count = (counts.get(control) ?? 1) - 1;
|
|
1230
|
-
if (count > 0) counts.set(control, count);
|
|
1231
|
-
else counts.delete(control);
|
|
1232
|
-
};
|
|
1233
|
-
}, []);
|
|
1473
|
+
const { register, has } = usePartRegistry();
|
|
1234
1474
|
const requiredControls = [...attributes.map((attribute) => `attribute:${attribute.name}`), ...skuOptions.length ? ["sku"] : []].join("\n");
|
|
1235
1475
|
useEffect(() => {
|
|
1236
1476
|
if (process.env.NODE_ENV === "production") return;
|
|
1237
|
-
const missing = requiredControls.split("\n").filter((control) => control && !
|
|
1477
|
+
const missing = requiredControls.split("\n").filter((control) => control && !has(control));
|
|
1238
1478
|
if (missing.length === 0) return;
|
|
1239
1479
|
console.warn(`[gd-commerce-storefront] No control for ${missing.map((control) => control === "sku" ? "the SKU option" : `"${control.slice(10)}"`).join(", ")} is rendered right now, so this product cannot be added to the cart until one is. Render <ProductDetailsOptions />, <ProductDetailsOption attribute='…' /> for each attribute, or <ProductDetailsPurchase />, or ignore this if the control mounts later.`);
|
|
1240
|
-
}, [requiredControls]);
|
|
1241
|
-
if (product.isPending) return /* @__PURE__ */ jsx("
|
|
1242
|
-
|
|
1243
|
-
children: "
|
|
1480
|
+
}, [requiredControls, has]);
|
|
1481
|
+
if (product.isPending) return /* @__PURE__ */ jsx("div", {
|
|
1482
|
+
className: partClass(className),
|
|
1483
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
1484
|
+
role: "status",
|
|
1485
|
+
children: "Loading product…"
|
|
1486
|
+
})
|
|
1244
1487
|
});
|
|
1245
1488
|
if (product.isError && !productNotFound) return /* @__PURE__ */ jsxs("div", {
|
|
1246
1489
|
role: "alert",
|
|
1490
|
+
className: partClass(className),
|
|
1247
1491
|
children: [
|
|
1248
1492
|
/* @__PURE__ */ jsx("h1", {
|
|
1249
1493
|
className: "text-2xl font-semibold",
|
|
@@ -1261,17 +1505,20 @@ function ProductDetailsLoader({ productId, className, children }) {
|
|
|
1261
1505
|
})
|
|
1262
1506
|
]
|
|
1263
1507
|
});
|
|
1264
|
-
if (productNotFound || !group) return /* @__PURE__ */ jsxs("div", {
|
|
1265
|
-
className:
|
|
1266
|
-
children:
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1508
|
+
if (productNotFound || !group) return /* @__PURE__ */ jsxs("div", {
|
|
1509
|
+
className: partClass(className),
|
|
1510
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
1511
|
+
className: "text-2xl font-semibold",
|
|
1512
|
+
children: "Product not found"
|
|
1513
|
+
}), /* @__PURE__ */ jsx(Link, {
|
|
1514
|
+
className: `${buttonClass} mt-4`,
|
|
1515
|
+
to: config.catalogPath,
|
|
1516
|
+
children: "Back to shop"
|
|
1517
|
+
})]
|
|
1518
|
+
});
|
|
1272
1519
|
const value = {
|
|
1273
1520
|
product: group,
|
|
1274
|
-
name: group
|
|
1521
|
+
name: getProductName(group),
|
|
1275
1522
|
selectedSku,
|
|
1276
1523
|
quantity,
|
|
1277
1524
|
availableQuantity: getAvailableInventoryQuantity(selectedSku),
|
|
@@ -1313,16 +1560,16 @@ const disabledReasons = {
|
|
|
1313
1560
|
unavailable: "This combination is unavailable. Choose different options.",
|
|
1314
1561
|
"not-purchasable": "This product is not currently available to purchase."
|
|
1315
1562
|
};
|
|
1316
|
-
const flush = "mb-0";
|
|
1317
1563
|
/** The packaged purchase area: options, status, availability, quantity, and add to cart. */
|
|
1318
1564
|
function ProductDetailsPurchase({ className }) {
|
|
1319
|
-
useProductDetailsInternal("ProductDetailsPurchase");
|
|
1565
|
+
const { attributes, skuOptions } = useProductDetailsInternal("ProductDetailsPurchase");
|
|
1566
|
+
const belowOptions = attributes.length || skuOptions.length ? "-mt-4" : void 0;
|
|
1320
1567
|
return /* @__PURE__ */ jsxs("div", {
|
|
1321
1568
|
className: partClass("space-y-6", className),
|
|
1322
1569
|
children: [
|
|
1323
1570
|
/* @__PURE__ */ jsx(OptionControls, {}),
|
|
1324
|
-
/* @__PURE__ */ jsx(
|
|
1325
|
-
/* @__PURE__ */ jsx(ProductDetailsAvailability, { className:
|
|
1571
|
+
/* @__PURE__ */ jsx(ProductDetailsPurchaseStatus, { className: belowOptions }),
|
|
1572
|
+
/* @__PURE__ */ jsx(ProductDetailsAvailability, { className: belowOptions }),
|
|
1326
1573
|
/* @__PURE__ */ jsx(ProductDetailsQuantity, {}),
|
|
1327
1574
|
/* @__PURE__ */ jsx(ProductDetailsAddToCart, {})
|
|
1328
1575
|
]
|
|
@@ -1339,32 +1586,53 @@ function ProductDetailsOptions({ className }) {
|
|
|
1339
1586
|
}
|
|
1340
1587
|
function OptionControls() {
|
|
1341
1588
|
const { attributes, skuOptions } = useProductDetailsInternal("ProductDetailsOptions");
|
|
1342
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [attributes.map((attribute) => /* @__PURE__ */ jsx(ProductDetailsOption, {
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1589
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [attributes.map((attribute) => /* @__PURE__ */ jsx(ProductDetailsOption, { attribute: attribute.name }, attribute.id || attribute.name)), skuOptions.length > 0 && /* @__PURE__ */ jsx(SkuOption, {})] });
|
|
1590
|
+
}
|
|
1591
|
+
function Select({ children,...props }) {
|
|
1592
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1593
|
+
className: "relative",
|
|
1594
|
+
children: [/* @__PURE__ */ jsx("select", {
|
|
1595
|
+
className: `${inputClass} w-full appearance-none pr-10`,
|
|
1596
|
+
...props,
|
|
1597
|
+
children
|
|
1598
|
+
}), /* @__PURE__ */ jsx("svg", {
|
|
1599
|
+
"aria-hidden": "true",
|
|
1600
|
+
viewBox: "0 0 16 16",
|
|
1601
|
+
className: "pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-neutral-600",
|
|
1602
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
1603
|
+
d: "M4 6l4 4 4-4",
|
|
1604
|
+
fill: "none",
|
|
1605
|
+
stroke: "currentColor",
|
|
1606
|
+
strokeWidth: "1.5",
|
|
1607
|
+
strokeLinecap: "round",
|
|
1608
|
+
strokeLinejoin: "round"
|
|
1609
|
+
})
|
|
1610
|
+
})]
|
|
1611
|
+
});
|
|
1346
1612
|
}
|
|
1347
1613
|
function SkuOption() {
|
|
1348
1614
|
const { skuOptions, explicitSkuId, selectSku } = useProductDetailsInternal("ProductDetailsOptions");
|
|
1349
1615
|
const id = useId();
|
|
1350
1616
|
useSkuOptionRegistration();
|
|
1351
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1617
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1618
|
+
className: partClass(),
|
|
1619
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
1620
|
+
htmlFor: id,
|
|
1621
|
+
className: "mb-2 block font-medium",
|
|
1622
|
+
children: "Option"
|
|
1623
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
1624
|
+
id,
|
|
1625
|
+
value: explicitSkuId,
|
|
1626
|
+
onChange: (event) => selectSku(event.target.value),
|
|
1627
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
1628
|
+
value: "",
|
|
1629
|
+
children: "Select an option"
|
|
1630
|
+
}), skuOptions.map((sku) => /* @__PURE__ */ jsx("option", {
|
|
1631
|
+
value: sku.id ?? "",
|
|
1632
|
+
children: sku.label?.trim() || sku.name?.trim()
|
|
1633
|
+
}, sku.id))]
|
|
1634
|
+
})]
|
|
1635
|
+
});
|
|
1368
1636
|
}
|
|
1369
1637
|
/** A select for one product attribute. Use `useProductDetailsOption` for swatches or other custom controls. */
|
|
1370
1638
|
function ProductDetailsOption({ attribute, className }) {
|
|
@@ -1375,8 +1643,7 @@ function ProductDetailsOption({ attribute, className }) {
|
|
|
1375
1643
|
children: /* @__PURE__ */ jsxs("fieldset", { children: [/* @__PURE__ */ jsx("legend", {
|
|
1376
1644
|
className: "mb-2 font-medium",
|
|
1377
1645
|
children: option.label
|
|
1378
|
-
}), /* @__PURE__ */ jsxs(
|
|
1379
|
-
className: `${inputClass} w-full`,
|
|
1646
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
1380
1647
|
"aria-label": option.label,
|
|
1381
1648
|
value: option.selected ?? "",
|
|
1382
1649
|
onChange: (event) => option.select(event.target.value || null),
|
|
@@ -1392,45 +1659,48 @@ function ProductDetailsOption({ attribute, className }) {
|
|
|
1392
1659
|
}
|
|
1393
1660
|
/** The single message explaining why the selection is not yet purchasable. Renders nothing when ready. */
|
|
1394
1661
|
function ProductDetailsPurchaseStatus({ className }) {
|
|
1395
|
-
return /* @__PURE__ */ jsx(PurchaseStatusMessage, { className });
|
|
1396
|
-
}
|
|
1397
|
-
function PurchaseStatusMessage({ className, textClassName }) {
|
|
1398
1662
|
const { status, variantError, retryVariant } = useProductDetailsInternal("ProductDetailsPurchaseStatus");
|
|
1399
|
-
|
|
1400
|
-
className: partClass(partClassName, className),
|
|
1401
|
-
children
|
|
1402
|
-
});
|
|
1663
|
+
let content;
|
|
1403
1664
|
switch (status) {
|
|
1404
1665
|
case "ready": return null;
|
|
1405
|
-
case "variant-error":
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
case "
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1666
|
+
case "variant-error":
|
|
1667
|
+
content = /* @__PURE__ */ jsxs("div", {
|
|
1668
|
+
role: "alert",
|
|
1669
|
+
className: "text-red-700",
|
|
1670
|
+
children: [/* @__PURE__ */ jsx("p", { children: message(variantError) }), /* @__PURE__ */ jsx("button", {
|
|
1671
|
+
type: "button",
|
|
1672
|
+
className: "min-h-11 bg-white text-red-700 underline",
|
|
1673
|
+
onClick: retryVariant,
|
|
1674
|
+
children: "Retry availability"
|
|
1675
|
+
})]
|
|
1676
|
+
});
|
|
1677
|
+
break;
|
|
1678
|
+
case "unavailable":
|
|
1679
|
+
content = /* @__PURE__ */ jsx("p", {
|
|
1680
|
+
role: "status",
|
|
1681
|
+
className: "text-sm text-red-700",
|
|
1682
|
+
children: disabledReasons[status]
|
|
1683
|
+
});
|
|
1684
|
+
break;
|
|
1685
|
+
case "not-purchasable":
|
|
1686
|
+
content = /* @__PURE__ */ jsx("p", {
|
|
1687
|
+
className: "text-sm",
|
|
1688
|
+
children: disabledReasons[status]
|
|
1689
|
+
});
|
|
1690
|
+
break;
|
|
1691
|
+
case "incomplete":
|
|
1692
|
+
case "checking":
|
|
1693
|
+
content = /* @__PURE__ */ jsx("p", {
|
|
1694
|
+
role: "status",
|
|
1695
|
+
className: "text-sm",
|
|
1696
|
+
children: disabledReasons[status]
|
|
1697
|
+
});
|
|
1698
|
+
break;
|
|
1433
1699
|
}
|
|
1700
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1701
|
+
className: partClass(className),
|
|
1702
|
+
children: content
|
|
1703
|
+
});
|
|
1434
1704
|
}
|
|
1435
1705
|
/** Stock for the verified SKU. Renders nothing until a SKU is verified. */
|
|
1436
1706
|
function ProductDetailsAvailability({ className }) {
|
|
@@ -1563,14 +1833,17 @@ function ProductDetailsTitle({ className }) {
|
|
|
1563
1833
|
function ProductDetailsPrice({ className }) {
|
|
1564
1834
|
const { price, product } = useProductDetailsInternal("ProductDetailsPrice");
|
|
1565
1835
|
const { config } = useCommerce();
|
|
1566
|
-
return /* @__PURE__ */
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
children: money(price.
|
|
1573
|
-
|
|
1836
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1837
|
+
className: partClass(className),
|
|
1838
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1839
|
+
"aria-live": "polite",
|
|
1840
|
+
className: "flex flex-wrap items-baseline gap-x-3 gap-y-1 text-2xl font-medium",
|
|
1841
|
+
"data-testid": "product-price",
|
|
1842
|
+
children: [price?.value?.value != null ? money(price.value.value, price.value.currencyCode ?? config.currencyCode) : product.priceRange?.min != null ? `From ${money(product.priceRange.min, config.currencyCode)}` : "Price unavailable", price?.compareAtValue?.value != null && price.value?.value != null && price.compareAtValue.value > price.value.value && /* @__PURE__ */ jsx("del", {
|
|
1843
|
+
className: "text-lg font-normal",
|
|
1844
|
+
children: money(price.compareAtValue.value, price.compareAtValue.currencyCode ?? config.currencyCode)
|
|
1845
|
+
})]
|
|
1846
|
+
})
|
|
1574
1847
|
});
|
|
1575
1848
|
}
|
|
1576
1849
|
/** The verified SKU's description, or the product's. */
|
|
@@ -1586,4 +1859,4 @@ function ProductDetailsDescription({ className }) {
|
|
|
1586
1859
|
}
|
|
1587
1860
|
|
|
1588
1861
|
//#endregion
|
|
1589
|
-
export { AddToCartButton, ApiError, CartButton, CartDrawer, Catalog, CommerceProvider, CommerceStatus, CommerceStorefront, ProductCard, ProductDetails, ProductDetailsAddToCart, ProductDetailsAvailability, ProductDetailsBackLink, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsOption, ProductDetailsOptions, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, ProductDetailsTitle, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getSingleMatchedSku, money, useCommerce, useProductDetails, useProductDetailsOption };
|
|
1862
|
+
export { AddToCartButton, ApiError, CartButton, CartDrawer, Catalog, CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, CommerceProvider, CommerceStatus, CommerceStorefront, ProductCard, ProductCardAction, ProductCardDescription, ProductCardImage, ProductCardPrice, ProductCardRoot, ProductCardTitle, ProductDetails, ProductDetailsAddToCart, ProductDetailsAvailability, ProductDetailsBackLink, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsOption, ProductDetailsOptions, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, ProductDetailsTitle, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getSingleMatchedSku, money, useCommerce, useProductCard, useProductDetails, useProductDetailsOption };
|