@godaddy/gd-commerce-storefront 0.9.0 → 0.10.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 +29 -4
- package/dist/index.d.ts +48 -9
- package/dist/index.js +543 -186
- package/dist/messages/fr-FR.d.ts +7 -0
- package/dist/messages/fr-FR.js +143 -0
- package/dist/messages-CHl-bWx9.d.ts +152 -0
- package/dist/styles.css +3 -0
- package/package.json +13 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,190 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
import * as Dialog from "@radix-ui/react-dialog";
|
|
5
5
|
import { Link, useParams, useSearchParams } from "react-router";
|
|
6
6
|
|
|
7
|
+
//#region src/messages.ts
|
|
8
|
+
const englishText = {
|
|
9
|
+
connectPrompt: "Connect a store to show products.",
|
|
10
|
+
connecting: "Connecting to the store…",
|
|
11
|
+
retryConnection: "Retry connection",
|
|
12
|
+
connectionSlow: "The store is taking too long to connect.",
|
|
13
|
+
invalidConfiguration: "The store returned invalid configuration.",
|
|
14
|
+
requestFailed: "Commerce request failed. Please retry.",
|
|
15
|
+
requestFailedStatus: "Commerce request failed ({status}). Please retry.",
|
|
16
|
+
requestTimeout: "Commerce information took too long to load. Please retry.",
|
|
17
|
+
unknownError: "Something went wrong. Please retry.",
|
|
18
|
+
storageUnavailable: "Your browser cannot save the cart. Keep this page open while shopping.",
|
|
19
|
+
invalidAddItem: "Select a product variant and a positive quantity.",
|
|
20
|
+
addReturnedNoCart: "Commerce did not return a cart with items. Refresh before retrying.",
|
|
21
|
+
itemNotInCart: "This item is no longer in the cart.",
|
|
22
|
+
invalidQuantity: "Quantity must be a positive whole number.",
|
|
23
|
+
updateReturnedNoCart: "Commerce did not return the updated cart. Refresh before retrying.",
|
|
24
|
+
discountMissing: "Add an item and enter a promotion code.",
|
|
25
|
+
discountReturnedNoCart: "Commerce did not return the updated cart.",
|
|
26
|
+
checkoutDisabled: "Checkout has not been enabled for this store.",
|
|
27
|
+
checkoutEmpty: "Add an item before checking out.",
|
|
28
|
+
checkoutNoUrl: "Commerce did not return a checkout URL.",
|
|
29
|
+
checkoutInvalidUrl: "Commerce returned an invalid checkout URL.",
|
|
30
|
+
itemAdded: "{name} added to your cart.",
|
|
31
|
+
sampleItemAdded: "{name} added to your sample cart.",
|
|
32
|
+
itemRemoved: "Item removed from cart.",
|
|
33
|
+
quantityUpdated: "Cart quantity updated.",
|
|
34
|
+
discountsUpdated: "Promotion codes updated.",
|
|
35
|
+
sampleQuantityUpdated: "Sample cart quantity updated.",
|
|
36
|
+
sampleItemRemoved: "Sample item removed from cart.",
|
|
37
|
+
cartButtonLabel: {
|
|
38
|
+
one: "Cart, {count} item",
|
|
39
|
+
other: "Cart, {count} items"
|
|
40
|
+
},
|
|
41
|
+
addToCart: "Add to cart",
|
|
42
|
+
addToCartWithPrice: "Add to cart · {price}",
|
|
43
|
+
addToCartWithPriceName: "Add to cart, {price}",
|
|
44
|
+
adding: "Adding…",
|
|
45
|
+
outOfStock: "Out of stock",
|
|
46
|
+
addReasonUnbound: "Connect a store to add items to the cart.",
|
|
47
|
+
addReasonError: "The store could not be reached.",
|
|
48
|
+
addReasonNoSku: "This item cannot be added to the cart.",
|
|
49
|
+
loadingCart: "Loading your cart…",
|
|
50
|
+
quantityNotWhole: "Enter a whole number of 1 or more.",
|
|
51
|
+
quantityOverStock: "Only {count} available.",
|
|
52
|
+
cartTitle: "Your cart",
|
|
53
|
+
sampleCartTitle: "Sample cart",
|
|
54
|
+
cartDescription: "Review your items before checkout.",
|
|
55
|
+
sampleCartDescription: "Example products and prices in {currency}. Nothing can be purchased.",
|
|
56
|
+
closeCart: "Close cart",
|
|
57
|
+
refreshCart: "Refresh cart",
|
|
58
|
+
priceUnavailable: "Price unavailable",
|
|
59
|
+
decreaseQuantity: "Decrease quantity of {name}",
|
|
60
|
+
increaseQuantity: "Increase quantity of {name}",
|
|
61
|
+
quantityPrefix: "Quantity: ",
|
|
62
|
+
remove: "Remove",
|
|
63
|
+
itemOption: "{attribute}: {values}",
|
|
64
|
+
cartEmpty: "Your cart is empty",
|
|
65
|
+
cartEmptyHint: "Add something you love and it will show up here.",
|
|
66
|
+
sampleCartEmptyHint: "Add a sample product to try the cart.",
|
|
67
|
+
browseProducts: "Browse products",
|
|
68
|
+
subtotal: "Subtotal",
|
|
69
|
+
sampleSubtotal: "Sample subtotal",
|
|
70
|
+
checkoutAdjustments: "Shipping, taxes & discounts",
|
|
71
|
+
checkoutAdjustmentsValue: "Calculated at checkout",
|
|
72
|
+
sampleCheckoutNote: "Example total in {currency}. Checkout is not set up yet.",
|
|
73
|
+
checkoutUnavailable: "Checkout is not available yet.",
|
|
74
|
+
checkout: "Proceed to checkout",
|
|
75
|
+
openingCheckout: "Opening checkout…",
|
|
76
|
+
continueShopping: "Continue shopping",
|
|
77
|
+
catalogTitle: "Shop all products",
|
|
78
|
+
catalogDescription: "Explore the collection and find your favorites.",
|
|
79
|
+
sampleCatalogSummary: "Sample products · Example prices in {currency}",
|
|
80
|
+
loadingProducts: "Loading products…",
|
|
81
|
+
productsUnavailable: "Products unavailable",
|
|
82
|
+
productCount: {
|
|
83
|
+
one: "Showing {count} product",
|
|
84
|
+
other: "Showing {count} products"
|
|
85
|
+
},
|
|
86
|
+
retryProducts: "Retry products",
|
|
87
|
+
noProducts: "No products available.",
|
|
88
|
+
productPages: "Product pages",
|
|
89
|
+
firstPage: "First page",
|
|
90
|
+
nextPage: "Next page",
|
|
91
|
+
sampleProductName: "Product {number}",
|
|
92
|
+
relatedTitle: "You may also like",
|
|
93
|
+
imageUnavailable: "{name}: image unavailable",
|
|
94
|
+
noImage: "No image available",
|
|
95
|
+
sale: "Sale",
|
|
96
|
+
wasPrice: "Was",
|
|
97
|
+
swatchSummary: {
|
|
98
|
+
one: "{count} {attribute} option: {values}",
|
|
99
|
+
other: "{count} {attribute} options: {values}"
|
|
100
|
+
},
|
|
101
|
+
chooseOptions: "Choose options",
|
|
102
|
+
viewProduct: "View product",
|
|
103
|
+
addSampleToCart: "Add {name} to sample cart",
|
|
104
|
+
sampleItemName: "({name}, sample)",
|
|
105
|
+
loadingProduct: "Loading product…",
|
|
106
|
+
productUnavailable: "Product unavailable",
|
|
107
|
+
retryProduct: "Retry product",
|
|
108
|
+
productNotFound: "Product not found",
|
|
109
|
+
backToShop: "Back to shop",
|
|
110
|
+
viewImage: "View image {number}",
|
|
111
|
+
priceFrom: "From {price}",
|
|
112
|
+
selectAllOptions: "Select all options to add this item to your cart.",
|
|
113
|
+
checkingVariant: "Checking this variant…",
|
|
114
|
+
variantCheckFailed: "Availability for this selection could not be checked.",
|
|
115
|
+
combinationUnavailable: "This combination is unavailable. Choose different options.",
|
|
116
|
+
notPurchasable: "This product is not currently available to purchase.",
|
|
117
|
+
selectAttribute: "Select {attribute}",
|
|
118
|
+
selectAttributeError: "Select {attribute}.",
|
|
119
|
+
selectOption: "Select an option",
|
|
120
|
+
skuOptionLabel: "Option",
|
|
121
|
+
soldOutOption: "{label} (sold out)",
|
|
122
|
+
soldOutChoice: "{label}, sold out",
|
|
123
|
+
retryAvailability: "Retry availability",
|
|
124
|
+
availableToOrder: "Available to order",
|
|
125
|
+
variantOutOfStock: "This variant is out of stock",
|
|
126
|
+
availableCount: "{count} available",
|
|
127
|
+
quantity: "Quantity"
|
|
128
|
+
};
|
|
129
|
+
/** The package's English text, the default for every key a site does not set. */
|
|
130
|
+
const enUS = {
|
|
131
|
+
locale: "en-US",
|
|
132
|
+
errorCodes: {},
|
|
133
|
+
...englishText
|
|
134
|
+
};
|
|
135
|
+
/** `key` from `messages`, with its plural form chosen by `values.count` and its placeholders filled in. */
|
|
136
|
+
function formatMessage(messages, key, values = {}) {
|
|
137
|
+
return (pickText(messages[key], values.count, messages.locale) ?? pickText(enUS[key], values.count, enUS.locale) ?? key).replace(/\{(\w+)\}/g, (placeholder, name) => {
|
|
138
|
+
const value = values[name];
|
|
139
|
+
if (value === void 0) return placeholder;
|
|
140
|
+
return typeof value === "number" ? numberFormat(messages.locale).format(value) : value;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function pickText(message, count, locale) {
|
|
144
|
+
if (typeof message === "string") return message;
|
|
145
|
+
if (typeof message !== "object" || message === null) return void 0;
|
|
146
|
+
const forms = message;
|
|
147
|
+
const form = typeof count === "number" ? pluralRules(locale).select(count) : void 0;
|
|
148
|
+
const chosen = form && forms[form];
|
|
149
|
+
if (typeof chosen === "string" && chosen) return chosen;
|
|
150
|
+
return typeof forms.other === "string" ? forms.other : void 0;
|
|
151
|
+
}
|
|
152
|
+
/** Whether the text is English, the language of the server's own error messages. */
|
|
153
|
+
const isEnglish = (messages) => /^en(?:-|$)/i.test(messages.locale);
|
|
154
|
+
/** An error whose text is a package message, so it shows in the provider's language. */
|
|
155
|
+
var StorefrontError = class extends Error {
|
|
156
|
+
constructor(key, values = {}, options) {
|
|
157
|
+
super(formatMessage(enUS, key, values), options);
|
|
158
|
+
this.key = key;
|
|
159
|
+
this.values = values;
|
|
160
|
+
this.name = "StorefrontError";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const pluralRulesCache = /* @__PURE__ */ new Map();
|
|
164
|
+
const numberFormatCache = /* @__PURE__ */ new Map();
|
|
165
|
+
function pluralRules(locale) {
|
|
166
|
+
let rules = pluralRulesCache.get(locale);
|
|
167
|
+
if (!rules) {
|
|
168
|
+
rules = new Intl.PluralRules(safeLocale(locale));
|
|
169
|
+
pluralRulesCache.set(locale, rules);
|
|
170
|
+
}
|
|
171
|
+
return rules;
|
|
172
|
+
}
|
|
173
|
+
function numberFormat(locale) {
|
|
174
|
+
let format = numberFormatCache.get(locale);
|
|
175
|
+
if (!format) {
|
|
176
|
+
format = new Intl.NumberFormat(safeLocale(locale));
|
|
177
|
+
numberFormatCache.set(locale, format);
|
|
178
|
+
}
|
|
179
|
+
return format;
|
|
180
|
+
}
|
|
181
|
+
/** `locale`, or `en-US` when it is not a valid tag, so a typo in a message file cannot break rendering. */
|
|
182
|
+
function safeLocale(locale) {
|
|
183
|
+
try {
|
|
184
|
+
return Intl.getCanonicalLocales(locale)[0] ?? "en-US";
|
|
185
|
+
} catch {
|
|
186
|
+
return "en-US";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
7
191
|
//#region src/api.ts
|
|
8
192
|
var CartIdStorage = class {
|
|
9
193
|
currentId = null;
|
|
@@ -24,9 +208,15 @@ var CartIdStorage = class {
|
|
|
24
208
|
}
|
|
25
209
|
};
|
|
26
210
|
var ApiError = class extends Error {
|
|
27
|
-
|
|
28
|
-
|
|
211
|
+
/** The failure response's `code`, such as `not_found`, when the server sent one. */
|
|
212
|
+
code;
|
|
213
|
+
/** Package text to show in place of `message`, for failures the package describes itself. */
|
|
214
|
+
text;
|
|
215
|
+
constructor(message, status, options) {
|
|
216
|
+
super(message, options);
|
|
29
217
|
this.status = status;
|
|
218
|
+
this.code = options?.code;
|
|
219
|
+
this.text = options?.text;
|
|
30
220
|
}
|
|
31
221
|
};
|
|
32
222
|
const checkedFetch = async (input, init) => {
|
|
@@ -37,10 +227,23 @@ const checkedFetch = async (input, init) => {
|
|
|
37
227
|
try {
|
|
38
228
|
return JSON.parse(data);
|
|
39
229
|
} catch (cause) {
|
|
40
|
-
|
|
230
|
+
const text = {
|
|
231
|
+
key: "requestFailedStatus",
|
|
232
|
+
values: { status: response.status }
|
|
233
|
+
};
|
|
234
|
+
throw new ApiError(formatMessage(enUS, text.key, text.values), response.status, {
|
|
235
|
+
cause,
|
|
236
|
+
text
|
|
237
|
+
});
|
|
41
238
|
}
|
|
42
239
|
})();
|
|
43
|
-
|
|
240
|
+
const body = typeof parsed === "object" && parsed !== null ? { ...parsed } : {};
|
|
241
|
+
const code = typeof body.code === "string" ? body.code : void 0;
|
|
242
|
+
if (typeof body.error === "string") throw new ApiError(body.error, response.status, { code });
|
|
243
|
+
throw new ApiError(formatMessage(enUS, "requestFailed"), response.status, {
|
|
244
|
+
code,
|
|
245
|
+
text: { key: "requestFailed" }
|
|
246
|
+
});
|
|
44
247
|
}
|
|
45
248
|
return response;
|
|
46
249
|
};
|
|
@@ -57,22 +260,65 @@ async function request(path, init) {
|
|
|
57
260
|
cache: "no-store"
|
|
58
261
|
})).json();
|
|
59
262
|
} catch (cause) {
|
|
60
|
-
if (timeout?.aborted && !init?.signal?.aborted) throw new ApiError(
|
|
263
|
+
if (timeout?.aborted && !init?.signal?.aborted) throw new ApiError(formatMessage(enUS, "requestTimeout"), 408, {
|
|
264
|
+
cause,
|
|
265
|
+
text: { key: "requestTimeout" }
|
|
266
|
+
});
|
|
61
267
|
throw cause;
|
|
62
268
|
}
|
|
63
269
|
}
|
|
270
|
+
const currencyFormats = /* @__PURE__ */ new Map();
|
|
271
|
+
/** A page uses a few pairs; the limit bounds a long-running server rendering many locales and currencies. */
|
|
272
|
+
const maxCurrencyFormats = 64;
|
|
273
|
+
function currencyFormat(currency, locale) {
|
|
274
|
+
const key = `${locale}|${currency}`;
|
|
275
|
+
let cached = currencyFormats.get(key);
|
|
276
|
+
if (!cached) {
|
|
277
|
+
const format = new Intl.NumberFormat(locale, {
|
|
278
|
+
style: "currency",
|
|
279
|
+
currency
|
|
280
|
+
});
|
|
281
|
+
cached = {
|
|
282
|
+
format,
|
|
283
|
+
scale: 10 ** (format.resolvedOptions().maximumFractionDigits ?? 2)
|
|
284
|
+
};
|
|
285
|
+
if (currencyFormats.size >= maxCurrencyFormats) currencyFormats.delete(currencyFormats.keys().next().value ?? "");
|
|
286
|
+
currencyFormats.set(key, cached);
|
|
287
|
+
}
|
|
288
|
+
return cached;
|
|
289
|
+
}
|
|
64
290
|
/** Formats minor units, such as cents, as currency. Inside the provider, prefer `useMoney`, which follows its `locale`. */
|
|
65
291
|
function money(value, currency, locale = "en-US") {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
currency
|
|
69
|
-
});
|
|
70
|
-
const digits = formatter.resolvedOptions().maximumFractionDigits ?? 2;
|
|
71
|
-
return formatter.format(value / 10 ** digits);
|
|
292
|
+
const { format, scale } = currencyFormat(currency, locale);
|
|
293
|
+
return format.format(value / scale);
|
|
72
294
|
}
|
|
73
|
-
|
|
74
|
-
|
|
295
|
+
/** Formats a range of minor units the way `locale` writes ranges, such as `$20.00 – $25.00` or `20,00–25,00 €`. */
|
|
296
|
+
function moneyRange(min, max, currency, locale = "en-US") {
|
|
297
|
+
const { format, scale } = currencyFormat(currency, locale);
|
|
298
|
+
if (min === max) return format.format(min / scale);
|
|
299
|
+
try {
|
|
300
|
+
if (typeof format.formatRange === "function") return format.formatRange(min / scale, max / scale);
|
|
301
|
+
} catch {}
|
|
302
|
+
return `${format.format(min / scale)} – ${format.format(max / scale)}`;
|
|
75
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* The text to show for a failure, in the language of `messages`. Server messages are English, so other languages
|
|
306
|
+
* show the text for the response's `code`, or a generic message.
|
|
307
|
+
*/
|
|
308
|
+
function describeError(error, messages) {
|
|
309
|
+
const codes = messages.errorCodes;
|
|
310
|
+
const coded = error instanceof ApiError && error.code && codes && Object.hasOwn(codes, error.code) ? codes[error.code] : void 0;
|
|
311
|
+
if (typeof coded === "string" && coded) return coded;
|
|
312
|
+
const text = error instanceof StorefrontError || error instanceof ApiError ? textOf(error) : void 0;
|
|
313
|
+
if (text) return formatMessage(messages, text.key, text.values);
|
|
314
|
+
if (error instanceof ApiError) return isEnglish(messages) ? error.message : formatMessage(messages, "requestFailed");
|
|
315
|
+
if (error instanceof Error && isEnglish(messages)) return error.message;
|
|
316
|
+
return formatMessage(messages, "unknownError");
|
|
317
|
+
}
|
|
318
|
+
const textOf = (error) => error instanceof StorefrontError ? {
|
|
319
|
+
key: error.key,
|
|
320
|
+
values: error.values
|
|
321
|
+
} : error.text;
|
|
76
322
|
|
|
77
323
|
//#endregion
|
|
78
324
|
//#region src/cart-model.ts
|
|
@@ -85,9 +331,10 @@ async function addToCart(cartId, item, fetchFn = globalThis.fetch) {
|
|
|
85
331
|
body: JSON.stringify(body)
|
|
86
332
|
})).json();
|
|
87
333
|
}
|
|
88
|
-
|
|
334
|
+
/** The cart's totals in minor units. `currencyCode` is the cart's, or `fallbackCurrency`, such as the store's. */
|
|
335
|
+
function getCartSummaryTotals(order, fallbackCurrency = "USD") {
|
|
89
336
|
const lineItems = order?.lineItems ?? [];
|
|
90
|
-
const currencyCode = order?.totals?.total?.currencyCode ||
|
|
337
|
+
const currencyCode = order?.totals?.total?.currencyCode || fallbackCurrency;
|
|
91
338
|
return {
|
|
92
339
|
itemCount: lineItems.reduce((sum, item) => sum + (item.quantity || 0), 0),
|
|
93
340
|
currencyCode,
|
|
@@ -103,9 +350,13 @@ function getCartSummaryTotals(order) {
|
|
|
103
350
|
//#region src/sample-cart.tsx
|
|
104
351
|
const sampleProducts = Array.from({ length: 6 }, (_, index) => ({
|
|
105
352
|
key: `sample-${index + 1}`,
|
|
106
|
-
|
|
353
|
+
number: index + 1,
|
|
107
354
|
price: (index + 1) * 1e3
|
|
108
355
|
}));
|
|
356
|
+
/** Sample prices are examples, in this currency whatever the store's. */
|
|
357
|
+
const sampleCurrency = "USD";
|
|
358
|
+
/** A sample's name, such as "Product 1". The card and the cart both name samples with this. */
|
|
359
|
+
const sampleName = (text, sample) => text("sampleProductName", { number: sample.number });
|
|
109
360
|
const noQuantities = {};
|
|
110
361
|
const SampleCartContext = createContext(null);
|
|
111
362
|
function useSampleCart() {
|
|
@@ -113,7 +364,8 @@ function useSampleCart() {
|
|
|
113
364
|
if (!value) throw new Error("Mount CommerceProvider around sample products.");
|
|
114
365
|
return value;
|
|
115
366
|
}
|
|
116
|
-
function SampleCartProvider({ enabled, onAdd, announce, emit, children }) {
|
|
367
|
+
function SampleCartProvider({ enabled, messages, onAdd, announce, emit, children }) {
|
|
368
|
+
const text = useCallback((key, values) => formatMessage(messages, key, values), [messages]);
|
|
117
369
|
const session = useMemo(() => ({ enabled }), [enabled]);
|
|
118
370
|
const active = useRef(session);
|
|
119
371
|
active.current = session;
|
|
@@ -134,8 +386,9 @@ function SampleCartProvider({ enabled, onAdd, announce, emit, children }) {
|
|
|
134
386
|
const quantities = state.session === session && enabled ? state.quantities : noQuantities;
|
|
135
387
|
const items = useMemo(() => sampleProducts.flatMap((product) => quantities[product.key] ? [{
|
|
136
388
|
...product,
|
|
389
|
+
name: sampleName(text, product),
|
|
137
390
|
quantity: quantities[product.key]
|
|
138
|
-
}] : []), [quantities]);
|
|
391
|
+
}] : []), [quantities, text]);
|
|
139
392
|
function update(key, quantity) {
|
|
140
393
|
if (!enabled || active.current !== session || !sampleProducts.some((product) => product.key === key)) return false;
|
|
141
394
|
if (quantity !== void 0 && (!Number.isSafeInteger(quantity) || quantity < 0 || quantity > 999)) return false;
|
|
@@ -159,13 +412,13 @@ function SampleCartProvider({ enabled, onAdd, announce, emit, children }) {
|
|
|
159
412
|
add: (key) => {
|
|
160
413
|
const product = sampleProducts.find((product$1) => product$1.key === key);
|
|
161
414
|
if (!product || !update(key)) return false;
|
|
162
|
-
onAdd(product
|
|
415
|
+
onAdd(sampleName(text, product));
|
|
163
416
|
return true;
|
|
164
417
|
},
|
|
165
418
|
changeQuantity: async (key, quantity) => {
|
|
166
419
|
const updated = quantity >= 1 && update(key, quantity);
|
|
167
420
|
if (updated) {
|
|
168
|
-
announce("
|
|
421
|
+
announce("sampleQuantityUpdated");
|
|
169
422
|
emit({
|
|
170
423
|
type: "quantity-changed",
|
|
171
424
|
itemId: key,
|
|
@@ -178,7 +431,7 @@ function SampleCartProvider({ enabled, onAdd, announce, emit, children }) {
|
|
|
178
431
|
remove: async (key) => {
|
|
179
432
|
const updated = update(key, 0);
|
|
180
433
|
if (updated) {
|
|
181
|
-
announce("
|
|
434
|
+
announce("sampleItemRemoved");
|
|
182
435
|
emit({
|
|
183
436
|
type: "item-removed",
|
|
184
437
|
itemId: key,
|
|
@@ -211,12 +464,50 @@ function useMoney() {
|
|
|
211
464
|
const { currencyCode } = config;
|
|
212
465
|
return useCallback((value, currency = currencyCode) => money(value, currency, locale), [locale, currencyCode]);
|
|
213
466
|
}
|
|
467
|
+
/** `useMoney` for a range, such as `$20.00 – $25.00`, written the way the provider's `locale` writes ranges. */
|
|
468
|
+
function useMoneyRange() {
|
|
469
|
+
const { locale, config } = useCommerce();
|
|
470
|
+
const { currencyCode } = config;
|
|
471
|
+
return useCallback((min, max, currency = currencyCode) => moneyRange(min, max, currency, locale), [locale, currencyCode]);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The provider's text for `key`, with `{placeholders}` filled from `values`, the way the package's own parts read
|
|
475
|
+
* it. For custom parts that should match the package's language. English outside a provider.
|
|
476
|
+
*/
|
|
477
|
+
function useMessage() {
|
|
478
|
+
const messages = useContext(CommerceContext)?.messages ?? enUS;
|
|
479
|
+
return useCallback((key, values) => formatMessage(messages, key, values), [messages]);
|
|
480
|
+
}
|
|
214
481
|
const CONNECTING_FAILURE_GRACE_MS = 6e4;
|
|
215
482
|
/** Polls every five seconds for five minutes after the page loads, then every minute, and stops at thirty. */
|
|
216
483
|
const FAST_POLL_FOR_MS = 5 * 6e4;
|
|
217
484
|
const STOP_POLLING_AT_MS = 30 * 6e4;
|
|
218
485
|
/** A failed retry keeps showing connecting this long so the click visibly does something. */
|
|
219
486
|
const RETRY_FEEDBACK_MS = 1e3;
|
|
487
|
+
/**
|
|
488
|
+
* `messages` while its contents are unchanged, so an inline object, including inline `errorCodes` or plural forms,
|
|
489
|
+
* does not change every render. The kept value is state, so a render React discards cannot leave it behind.
|
|
490
|
+
*/
|
|
491
|
+
function useStableMessages(value) {
|
|
492
|
+
const [stable, setStable] = useState(value);
|
|
493
|
+
if (stable === value || sameMessages(stable, value)) return stable;
|
|
494
|
+
setStable(() => value);
|
|
495
|
+
return value;
|
|
496
|
+
}
|
|
497
|
+
/** Equal keys and values, comparing plain-object values, such as `errorCodes` and plural forms, one level deeper. */
|
|
498
|
+
function sameMessages(a, b, deeper = true) {
|
|
499
|
+
if (a === b) return true;
|
|
500
|
+
if (!a || !b) return false;
|
|
501
|
+
const left = a;
|
|
502
|
+
const right = b;
|
|
503
|
+
const keys = Object.keys(left);
|
|
504
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && (Object.is(left[key], right[key]) || deeper && isPlainObject(left[key]) && isPlainObject(right[key]) && sameMessages(left[key], right[key], false)));
|
|
505
|
+
}
|
|
506
|
+
function isPlainObject(value) {
|
|
507
|
+
if (typeof value !== "object" || value === null) return false;
|
|
508
|
+
const prototype = Object.getPrototypeOf(value);
|
|
509
|
+
return prototype === Object.prototype || prototype === null;
|
|
510
|
+
}
|
|
220
511
|
/** `locale` as a canonical tag, or `en-US` when it is not a valid one, so one bad prop cannot break every price. */
|
|
221
512
|
function resolveLocale(locale) {
|
|
222
513
|
if (!locale) return "en-US";
|
|
@@ -243,18 +534,28 @@ function callHost(callback) {
|
|
|
243
534
|
async function loadConfig(signal) {
|
|
244
535
|
const config = await request("/config", { signal });
|
|
245
536
|
if (config?.state === "unbound" || config?.state === "connecting") {
|
|
246
|
-
if ("cartScope" in config || "currencyCode" in config) throw new
|
|
537
|
+
if ("cartScope" in config || "currencyCode" in config) throw new StorefrontError("invalidConfiguration");
|
|
247
538
|
return { state: config.state };
|
|
248
539
|
}
|
|
249
|
-
if (!config || config.state !== void 0 && config.state !== "ready" || typeof config.cartScope !== "string" || !config.cartScope.trim() || typeof config.currencyCode !== "string" || !/^[A-Z]{3}$/.test(config.currencyCode)) throw new
|
|
540
|
+
if (!config || config.state !== void 0 && config.state !== "ready" || typeof config.cartScope !== "string" || !config.cartScope.trim() || typeof config.currencyCode !== "string" || !/^[A-Z]{3}$/.test(config.currencyCode)) throw new StorefrontError("invalidConfiguration");
|
|
250
541
|
return {
|
|
251
542
|
state: "ready",
|
|
252
543
|
cartScope: config.cartScope,
|
|
253
544
|
currencyCode: config.currencyCode
|
|
254
545
|
};
|
|
255
546
|
}
|
|
256
|
-
function CommerceProvider({ children, catalogPath = "/shop", productPath = "/products", checkoutSuccessPath, theme = {}, getSwatch, locale, onItemAdded, onEvent }) {
|
|
547
|
+
function CommerceProvider({ children, catalogPath = "/shop", productPath = "/products", checkoutSuccessPath, theme = {}, getSwatch, locale, messages, onItemAdded, onEvent }) {
|
|
257
548
|
const resolvedLocale = useMemo(() => resolveLocale(locale), [locale]);
|
|
549
|
+
const stableMessages = useStableMessages(messages);
|
|
550
|
+
const resolvedMessages = useMemo(() => ({
|
|
551
|
+
...enUS,
|
|
552
|
+
...stableMessages,
|
|
553
|
+
locale: stableMessages?.locale || enUS.locale,
|
|
554
|
+
errorCodes: {
|
|
555
|
+
...enUS.errorCodes,
|
|
556
|
+
...stableMessages?.errorCodes
|
|
557
|
+
}
|
|
558
|
+
}), [stableMessages]);
|
|
258
559
|
const [pollingSince, setPollingSince] = useState(Date.now);
|
|
259
560
|
const [pollingStopped, setPollingStopped] = useState(false);
|
|
260
561
|
const [retrying, setRetrying] = useState(false);
|
|
@@ -300,7 +601,7 @@ function CommerceProvider({ children, catalogPath = "/shop", productPath = "/pro
|
|
|
300
601
|
checkoutSuccessPath
|
|
301
602
|
},
|
|
302
603
|
connection,
|
|
303
|
-
connectionError: connection !== "error" ? null : stalled ?
|
|
604
|
+
connectionError: connection !== "error" ? null : stalled ? formatMessage(resolvedMessages, "connectionSlow") : describeError(config.error, resolvedMessages),
|
|
304
605
|
retryConnection: () => {
|
|
305
606
|
const started = Date.now();
|
|
306
607
|
setPollingSince(started);
|
|
@@ -317,12 +618,14 @@ function CommerceProvider({ children, catalogPath = "/shop", productPath = "/pro
|
|
|
317
618
|
theme,
|
|
318
619
|
getSwatch,
|
|
319
620
|
locale: resolvedLocale,
|
|
621
|
+
messages: resolvedMessages,
|
|
320
622
|
onItemAdded,
|
|
321
623
|
onEvent,
|
|
322
624
|
children
|
|
323
625
|
});
|
|
324
626
|
}
|
|
325
|
-
function BoundCommerceProvider({ config, children, connection, connectionError, retryConnection, theme, getSwatch, locale, onItemAdded, onEvent }) {
|
|
627
|
+
function BoundCommerceProvider({ config, children, connection, connectionError, retryConnection, theme, getSwatch, locale, messages, onItemAdded, onEvent }) {
|
|
628
|
+
const text = (key, values) => formatMessage(messages, key, values);
|
|
326
629
|
const storageKey = `godaddy:commerce-storefront:cart:${config.cartScope}`;
|
|
327
630
|
const cartIdStorage = useMemo(() => new CartIdStorage(storageKey), [storageKey]);
|
|
328
631
|
const ready = connection === "ready";
|
|
@@ -338,8 +641,10 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
338
641
|
const [open, setOpen] = useState(false);
|
|
339
642
|
const [pending, setPending] = useState(false);
|
|
340
643
|
const [hydrating, setHydrating] = useState(true);
|
|
341
|
-
const [
|
|
342
|
-
const [
|
|
644
|
+
const [failure, setError] = useState(null);
|
|
645
|
+
const [storageUnavailable, setStorageUnavailable] = useState(false);
|
|
646
|
+
const error = failure && describeError(failure.cause, messages);
|
|
647
|
+
const storageWarning = storageUnavailable ? text("storageUnavailable") : null;
|
|
343
648
|
const [announcement, setAnnouncement] = useState("");
|
|
344
649
|
const id = useRef(null);
|
|
345
650
|
const currentCart = useRef(null);
|
|
@@ -399,7 +704,7 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
399
704
|
try {
|
|
400
705
|
return cartIdStorage.read();
|
|
401
706
|
} catch {
|
|
402
|
-
|
|
707
|
+
setStorageUnavailable(true);
|
|
403
708
|
return id.current;
|
|
404
709
|
}
|
|
405
710
|
}
|
|
@@ -410,9 +715,9 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
410
715
|
setCart(next);
|
|
411
716
|
try {
|
|
412
717
|
cartIdStorage.write(id.current);
|
|
413
|
-
|
|
718
|
+
setStorageUnavailable(false);
|
|
414
719
|
} catch {
|
|
415
|
-
|
|
720
|
+
setStorageUnavailable(true);
|
|
416
721
|
}
|
|
417
722
|
}
|
|
418
723
|
async function hydrate() {
|
|
@@ -446,12 +751,14 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
446
751
|
return isActive();
|
|
447
752
|
} catch (cause) {
|
|
448
753
|
if (isActive()) {
|
|
449
|
-
const
|
|
450
|
-
setError(
|
|
754
|
+
const shown = describeError(cause, messages);
|
|
755
|
+
setError({ cause });
|
|
756
|
+
const code = cause instanceof ApiError ? cause.code : void 0;
|
|
451
757
|
emit({
|
|
452
758
|
type: "error",
|
|
453
759
|
operation: kind,
|
|
454
|
-
message:
|
|
760
|
+
message: shown,
|
|
761
|
+
...code ? { code } : {}
|
|
455
762
|
});
|
|
456
763
|
}
|
|
457
764
|
return false;
|
|
@@ -476,7 +783,7 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
476
783
|
setCart(null);
|
|
477
784
|
setOpen(false);
|
|
478
785
|
setError(null);
|
|
479
|
-
|
|
786
|
+
setStorageUnavailable(false);
|
|
480
787
|
setAnnouncement("");
|
|
481
788
|
setPending(false);
|
|
482
789
|
setHydrating(ready);
|
|
@@ -498,7 +805,7 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
498
805
|
function addItem(item) {
|
|
499
806
|
rememberOpener();
|
|
500
807
|
return run("add", async () => {
|
|
501
|
-
if (!item.skuId || !item.name || !Number.isInteger(item.quantity) || item.quantity < 1) throw new
|
|
808
|
+
if (!item.skuId || !item.name || !Number.isInteger(item.quantity) || item.quantity < 1) throw new StorefrontError("invalidAddItem");
|
|
502
809
|
await hydrate();
|
|
503
810
|
if (!isActive()) return;
|
|
504
811
|
const fetchCart = (input, init) => {
|
|
@@ -514,10 +821,10 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
514
821
|
name: item.name,
|
|
515
822
|
quantity: item.quantity
|
|
516
823
|
}, fetchCart);
|
|
517
|
-
if (!result.cart?.id || !result.cart.lineItems?.length) throw new
|
|
824
|
+
if (!result.cart?.id || !result.cart.lineItems?.length) throw new StorefrontError("addReturnedNoCart");
|
|
518
825
|
if (!isActive()) return;
|
|
519
826
|
commit(result.cart);
|
|
520
|
-
setAnnouncement(
|
|
827
|
+
setAnnouncement(text("itemAdded", { name: item.name }));
|
|
521
828
|
openDrawer();
|
|
522
829
|
emit({
|
|
523
830
|
type: "item-added",
|
|
@@ -532,16 +839,16 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
532
839
|
return run(method === "DELETE" ? "remove" : "change-quantity", async () => {
|
|
533
840
|
await hydrate();
|
|
534
841
|
if (!isActive()) return;
|
|
535
|
-
if (!id.current || !currentCart.current?.lineItems?.some((item) => item.id === itemId)) throw new
|
|
536
|
-
if (quantity !== void 0 && (!Number.isInteger(quantity) || quantity < 1)) throw new
|
|
842
|
+
if (!id.current || !currentCart.current?.lineItems?.some((item) => item.id === itemId)) throw new StorefrontError("itemNotInCart");
|
|
843
|
+
if (quantity !== void 0 && (!Number.isInteger(quantity) || quantity < 1)) throw new StorefrontError("invalidQuantity");
|
|
537
844
|
const result = await boundRequest(`/cart/${encodeURIComponent(id.current)}/items/${encodeURIComponent(itemId)}`, {
|
|
538
845
|
method,
|
|
539
846
|
...quantity !== void 0 ? { body: JSON.stringify({ quantity }) } : {}
|
|
540
847
|
});
|
|
541
|
-
if (!result.cart?.id) throw new
|
|
848
|
+
if (!result.cart?.id) throw new StorefrontError("updateReturnedNoCart");
|
|
542
849
|
if (!isActive()) return;
|
|
543
850
|
commit(result.cart);
|
|
544
|
-
setAnnouncement(method === "DELETE" ? "
|
|
851
|
+
setAnnouncement(text(method === "DELETE" ? "itemRemoved" : "quantityUpdated"));
|
|
545
852
|
emit(method === "DELETE" ? {
|
|
546
853
|
type: "item-removed",
|
|
547
854
|
itemId,
|
|
@@ -558,16 +865,16 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
558
865
|
return run("discount", async () => {
|
|
559
866
|
await hydrate();
|
|
560
867
|
if (!isActive()) return;
|
|
561
|
-
if (!id.current || !code.trim()) throw new
|
|
868
|
+
if (!id.current || !code.trim()) throw new StorefrontError("discountMissing");
|
|
562
869
|
const codes = [...new Set([...currentCart.current?.discounts?.flatMap((discount) => discount.code ? [discount.code] : []) ?? [], code.trim()])];
|
|
563
870
|
const result = await boundRequest(`/cart/${encodeURIComponent(id.current)}/discounts`, {
|
|
564
871
|
method: "POST",
|
|
565
872
|
body: JSON.stringify({ discountCodes: codes })
|
|
566
873
|
});
|
|
567
|
-
if (!result.cart?.id) throw new
|
|
874
|
+
if (!result.cart?.id) throw new StorefrontError("discountReturnedNoCart");
|
|
568
875
|
if (!isActive()) return;
|
|
569
876
|
commit(result.cart);
|
|
570
|
-
setAnnouncement("
|
|
877
|
+
setAnnouncement(text("discountsUpdated"));
|
|
571
878
|
emit({
|
|
572
879
|
type: "discount-applied",
|
|
573
880
|
code: code.trim()
|
|
@@ -576,10 +883,10 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
576
883
|
}
|
|
577
884
|
function checkout() {
|
|
578
885
|
return run("checkout", async () => {
|
|
579
|
-
if (!config.checkoutSuccessPath) throw new
|
|
886
|
+
if (!config.checkoutSuccessPath) throw new StorefrontError("checkoutDisabled");
|
|
580
887
|
await hydrate();
|
|
581
888
|
if (!isActive()) return;
|
|
582
|
-
if (!id.current || !currentCart.current?.lineItems?.length) throw new
|
|
889
|
+
if (!id.current || !currentCart.current?.lineItems?.length) throw new StorefrontError("checkoutEmpty");
|
|
583
890
|
const origin = window.location.origin;
|
|
584
891
|
const successUrl = new URL(config.checkoutSuccessPath, origin);
|
|
585
892
|
successUrl.searchParams.set("orderId", id.current);
|
|
@@ -592,9 +899,9 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
592
899
|
})
|
|
593
900
|
});
|
|
594
901
|
if (!isActive()) return;
|
|
595
|
-
if (typeof session$1.url !== "string" || !session$1.url.trim()) throw new
|
|
902
|
+
if (typeof session$1.url !== "string" || !session$1.url.trim()) throw new StorefrontError("checkoutNoUrl");
|
|
596
903
|
const url = new URL(session$1.url, origin);
|
|
597
|
-
if (url.protocol !== "https:") throw new
|
|
904
|
+
if (url.protocol !== "https:") throw new StorefrontError("checkoutInvalidUrl");
|
|
598
905
|
emit({
|
|
599
906
|
type: "checkout-started",
|
|
600
907
|
cartId: id.current
|
|
@@ -611,6 +918,7 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
611
918
|
theme,
|
|
612
919
|
getSwatch,
|
|
613
920
|
locale,
|
|
921
|
+
messages,
|
|
614
922
|
cart,
|
|
615
923
|
open,
|
|
616
924
|
setOpen: (next) => {
|
|
@@ -637,10 +945,11 @@ function BoundCommerceProvider({ config, children, connection, connectionError,
|
|
|
637
945
|
value: registerDrawer,
|
|
638
946
|
children: /* @__PURE__ */ jsx(SampleCartProvider, {
|
|
639
947
|
enabled: connection === "unbound",
|
|
640
|
-
|
|
948
|
+
messages,
|
|
949
|
+
announce: (key) => setAnnouncement(text(key)),
|
|
641
950
|
emit,
|
|
642
951
|
onAdd: (name) => {
|
|
643
|
-
setAnnouncement(
|
|
952
|
+
setAnnouncement(text("sampleItemAdded", { name }));
|
|
644
953
|
openDrawer();
|
|
645
954
|
emit({
|
|
646
955
|
type: "item-added",
|
|
@@ -678,11 +987,11 @@ function useCart() {
|
|
|
678
987
|
imageUrl: null,
|
|
679
988
|
options: [],
|
|
680
989
|
subtotal: item.price * item.quantity,
|
|
681
|
-
currency:
|
|
990
|
+
currency: sampleCurrency
|
|
682
991
|
})),
|
|
683
992
|
itemCount: sampleItems$1.reduce((total, item) => total + item.quantity, 0),
|
|
684
993
|
subtotal: sampleItems$1.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
685
|
-
currency:
|
|
994
|
+
currency: sampleCurrency,
|
|
686
995
|
isSample,
|
|
687
996
|
loading: false
|
|
688
997
|
};
|
|
@@ -907,15 +1216,16 @@ function StorefrontSurface({ children, className = "" }) {
|
|
|
907
1216
|
}
|
|
908
1217
|
function CommerceStatus() {
|
|
909
1218
|
const { connection, connectionError, retryConnection } = useCommerce();
|
|
1219
|
+
const text = useMessage();
|
|
910
1220
|
if (connection === "ready") return null;
|
|
911
1221
|
return /* @__PURE__ */ jsx(StorefrontSurface, { children: connection === "unbound" ? /* @__PURE__ */ jsx("p", {
|
|
912
1222
|
role: "status",
|
|
913
1223
|
className: "text-sm text-commerce-muted",
|
|
914
|
-
children: "
|
|
1224
|
+
children: text("connectPrompt")
|
|
915
1225
|
}) : connection === "loading" || connection === "connecting" ? /* @__PURE__ */ jsx("p", {
|
|
916
1226
|
role: "status",
|
|
917
1227
|
className: "text-sm text-commerce-muted",
|
|
918
|
-
children: "
|
|
1228
|
+
children: text("connecting")
|
|
919
1229
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
920
1230
|
role: "alert",
|
|
921
1231
|
className: errorNoticeClass,
|
|
@@ -923,7 +1233,7 @@ function CommerceStatus() {
|
|
|
923
1233
|
type: "button",
|
|
924
1234
|
className: errorActionClass,
|
|
925
1235
|
onClick: retryConnection,
|
|
926
|
-
children: "
|
|
1236
|
+
children: text("retryConnection")
|
|
927
1237
|
})]
|
|
928
1238
|
}) });
|
|
929
1239
|
}
|
|
@@ -937,6 +1247,7 @@ function CommerceStatus() {
|
|
|
937
1247
|
function CartButton({ children } = {}) {
|
|
938
1248
|
const { setOpen, open, connection } = useCommerce();
|
|
939
1249
|
const { itemCount } = useCart();
|
|
1250
|
+
const text = useMessage();
|
|
940
1251
|
const [bump, setBump] = useState(0);
|
|
941
1252
|
const previous = useRef(itemCount);
|
|
942
1253
|
useEffect(() => {
|
|
@@ -952,7 +1263,7 @@ function CartButton({ children } = {}) {
|
|
|
952
1263
|
disabled: connection !== "ready" && connection !== "unbound",
|
|
953
1264
|
"aria-haspopup": "dialog",
|
|
954
1265
|
"aria-expanded": open,
|
|
955
|
-
"aria-label": custom === null ?
|
|
1266
|
+
"aria-label": custom === null ? text("cartButtonLabel", { count: itemCount }) : void 0,
|
|
956
1267
|
"data-commerce-part": "cart-button",
|
|
957
1268
|
onClick: () => setOpen(true),
|
|
958
1269
|
children: custom ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(BagIcon, {}), itemCount > 0 && /* @__PURE__ */ jsx("span", {
|
|
@@ -982,8 +1293,11 @@ function BagIcon() {
|
|
|
982
1293
|
const isSoldOut = (available) => available !== null && available <= 0;
|
|
983
1294
|
/** Why `quantity` cannot be added, or `null` when it can. `available` is `null` when stock is not tracked. */
|
|
984
1295
|
function quantityProblem(quantity, available) {
|
|
985
|
-
if (!Number.isInteger(quantity) || quantity < 1) return
|
|
986
|
-
if (available !== null && available > 0 && quantity > available) return
|
|
1296
|
+
if (!Number.isInteger(quantity) || quantity < 1) return { key: "quantityNotWhole" };
|
|
1297
|
+
if (available !== null && available > 0 && quantity > available) return {
|
|
1298
|
+
key: "quantityOverStock",
|
|
1299
|
+
values: { count: available }
|
|
1300
|
+
};
|
|
987
1301
|
return null;
|
|
988
1302
|
}
|
|
989
1303
|
function AddToCartButton(props) {
|
|
@@ -998,16 +1312,21 @@ function AddToCartButton(props) {
|
|
|
998
1312
|
*/
|
|
999
1313
|
function AddToCart({ sku, name, quantity = 1, children, className, block = false, label, labelName }) {
|
|
1000
1314
|
const { addItem, pending, hydrating, error, open, connection } = useCommerce();
|
|
1315
|
+
const text = useMessage();
|
|
1001
1316
|
const [adding, setAdding] = useState(false);
|
|
1002
1317
|
const reasonId = useId();
|
|
1003
1318
|
const available = getAvailableInventoryQuantity(sku);
|
|
1004
1319
|
const soldOut = isSoldOut(available);
|
|
1005
1320
|
let reason = null;
|
|
1006
|
-
if (connection === "unbound") reason = "
|
|
1007
|
-
else if (connection === "error") reason = "
|
|
1008
|
-
else if (connection !== "ready") reason = "
|
|
1009
|
-
else if (!sku.id) reason = "
|
|
1010
|
-
else if (!soldOut) reason =
|
|
1321
|
+
if (connection === "unbound") reason = text("addReasonUnbound");
|
|
1322
|
+
else if (connection === "error") reason = text("addReasonError");
|
|
1323
|
+
else if (connection !== "ready") reason = text("connecting");
|
|
1324
|
+
else if (!sku.id) reason = text("addReasonNoSku");
|
|
1325
|
+
else if (!soldOut && hydrating) reason = text("loadingCart");
|
|
1326
|
+
else if (!soldOut) {
|
|
1327
|
+
const problem = quantityProblem(quantity, available);
|
|
1328
|
+
reason = problem && text(problem.key, problem.values);
|
|
1329
|
+
}
|
|
1011
1330
|
const disabled = adding || soldOut || reason !== null;
|
|
1012
1331
|
const ownLabel = !!labelName && !soldOut && !adding && renderLabel(children, {
|
|
1013
1332
|
busy: false,
|
|
@@ -1042,7 +1361,7 @@ function AddToCart({ sku, name, quantity = 1, children, className, block = false
|
|
|
1042
1361
|
children: renderLabel(children, {
|
|
1043
1362
|
busy: adding,
|
|
1044
1363
|
soldOut
|
|
1045
|
-
}, soldOut ? "
|
|
1364
|
+
}, soldOut ? text("outOfStock") : adding ? text("adding") : label ?? text("addToCart"))
|
|
1046
1365
|
}),
|
|
1047
1366
|
reason && /* @__PURE__ */ jsx("span", {
|
|
1048
1367
|
id: reasonId,
|
|
@@ -1158,11 +1477,12 @@ function CartDrawerRoot({ children, className }) {
|
|
|
1158
1477
|
function CartTitle({ className, children }) {
|
|
1159
1478
|
const { isSample, itemCount } = useCartInternal("CartTitle");
|
|
1160
1479
|
const Heading = useContext(CartDrawerContext) ? Dialog.Title : "h2";
|
|
1480
|
+
const text = useMessage();
|
|
1161
1481
|
return /* @__PURE__ */ jsx("div", {
|
|
1162
1482
|
...partProps("cart-title", className),
|
|
1163
1483
|
children: /* @__PURE__ */ jsx(Heading, {
|
|
1164
1484
|
className: "text-lg font-semibold tracking-tight",
|
|
1165
|
-
children: renderLabel(children, { isSample }, /* @__PURE__ */ jsxs(Fragment$1, { children: [isSample ? "
|
|
1485
|
+
children: renderLabel(children, { isSample }, /* @__PURE__ */ jsxs(Fragment$1, { children: [text(isSample ? "sampleCartTitle" : "cartTitle"), itemCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
1166
1486
|
className: "font-normal text-commerce-muted",
|
|
1167
1487
|
children: [
|
|
1168
1488
|
" (",
|
|
@@ -1179,7 +1499,8 @@ function CartDescription({ className, children }) {
|
|
|
1179
1499
|
const drawer = useContext(CartDrawerContext);
|
|
1180
1500
|
const register = drawer?.register;
|
|
1181
1501
|
useEffect(() => register?.("description"), [register]);
|
|
1182
|
-
const
|
|
1502
|
+
const message = useMessage();
|
|
1503
|
+
const text = renderLabel(children, { isSample }, isSample ? message("sampleCartDescription", { currency: sampleCurrency }) : message("cartDescription"));
|
|
1183
1504
|
return /* @__PURE__ */ jsx("div", {
|
|
1184
1505
|
...partProps("cart-description", className),
|
|
1185
1506
|
children: drawer ? /* @__PURE__ */ jsx(Dialog.Description, {
|
|
@@ -1194,12 +1515,13 @@ function CartDescription({ className, children }) {
|
|
|
1194
1515
|
/** The drawer's close button. Renders nothing outside the drawer. */
|
|
1195
1516
|
function CartClose({ className }) {
|
|
1196
1517
|
useCartInternal("CartClose");
|
|
1518
|
+
const text = useMessage();
|
|
1197
1519
|
if (!useContext(CartDrawerContext)) return null;
|
|
1198
1520
|
return /* @__PURE__ */ jsx("div", {
|
|
1199
1521
|
...partProps("cart-close", className),
|
|
1200
1522
|
children: /* @__PURE__ */ jsx(Dialog.Close, {
|
|
1201
1523
|
className: "inline-flex min-h-11 min-w-11 items-center justify-center rounded-full text-commerce-muted transition-colors hover:bg-commerce-tint hover:text-inherit focus-visible:outline focus-visible:outline-2 focus-visible:outline-current",
|
|
1202
|
-
"aria-label": "
|
|
1524
|
+
"aria-label": text("closeCart"),
|
|
1203
1525
|
children: /* @__PURE__ */ jsx("svg", {
|
|
1204
1526
|
"aria-hidden": "true",
|
|
1205
1527
|
viewBox: "0 0 20 20",
|
|
@@ -1218,6 +1540,7 @@ function CartClose({ className }) {
|
|
|
1218
1540
|
/** A storage warning, and a cart error with a refresh button. Renders nothing when there are neither. */
|
|
1219
1541
|
function CartNotices({ className }) {
|
|
1220
1542
|
const { storageWarning, error, action, locked, run, refresh } = useCartInternal("CartNotices");
|
|
1543
|
+
const text = useMessage();
|
|
1221
1544
|
if (!storageWarning && !error) return null;
|
|
1222
1545
|
return /* @__PURE__ */ jsx("div", {
|
|
1223
1546
|
...partProps("cart-notices", className),
|
|
@@ -1237,7 +1560,7 @@ function CartNotices({ className }) {
|
|
|
1237
1560
|
"aria-disabled": locked,
|
|
1238
1561
|
"aria-busy": action === "refresh",
|
|
1239
1562
|
onClick: () => run("refresh", refresh),
|
|
1240
|
-
children: "
|
|
1563
|
+
children: text("refreshCart")
|
|
1241
1564
|
})]
|
|
1242
1565
|
})]
|
|
1243
1566
|
})
|
|
@@ -1261,6 +1584,7 @@ function useCartItem() {
|
|
|
1261
1584
|
*/
|
|
1262
1585
|
function CartItems({ className, children }) {
|
|
1263
1586
|
const { items, hydrating } = useCartInternal("CartItems");
|
|
1587
|
+
const text = useMessage();
|
|
1264
1588
|
const template = hasChildren(children) ? children : packagedItem;
|
|
1265
1589
|
const registry = usePartRegistry();
|
|
1266
1590
|
const { has } = registry;
|
|
@@ -1273,7 +1597,7 @@ function CartItems({ className, children }) {
|
|
|
1273
1597
|
children: [hydrating && /* @__PURE__ */ jsx("p", {
|
|
1274
1598
|
role: "status",
|
|
1275
1599
|
className: "py-4 text-sm text-commerce-muted",
|
|
1276
|
-
children: "
|
|
1600
|
+
children: text("loadingCart")
|
|
1277
1601
|
}), /* @__PURE__ */ jsx("div", {
|
|
1278
1602
|
role: "list",
|
|
1279
1603
|
...partProps("cart-items", className),
|
|
@@ -1330,17 +1654,16 @@ function CartItemName({ className }) {
|
|
|
1330
1654
|
/** The selected options on one line, such as "Color: Blue · Size: M". Renders nothing without options. */
|
|
1331
1655
|
function CartItemOptions({ className }) {
|
|
1332
1656
|
const { options } = useCartItemInternal("CartItemOptions");
|
|
1657
|
+
const text = useMessage();
|
|
1333
1658
|
if (!options.length) return null;
|
|
1334
1659
|
return /* @__PURE__ */ jsx("div", {
|
|
1335
1660
|
...partProps("cart-item-options", className),
|
|
1336
1661
|
children: /* @__PURE__ */ jsx("p", {
|
|
1337
1662
|
className: "text-sm text-commerce-muted",
|
|
1338
|
-
children: options.map((option, index) => /* @__PURE__ */ jsxs("span", { children: [
|
|
1339
|
-
|
|
1340
|
-
option.
|
|
1341
|
-
|
|
1342
|
-
option.values.join(", ")
|
|
1343
|
-
] }, `${option.attribute}:${option.values.join(",")}`))
|
|
1663
|
+
children: options.map((option, index) => /* @__PURE__ */ jsxs("span", { children: [index > 0 && " · ", text("itemOption", {
|
|
1664
|
+
attribute: option.attribute,
|
|
1665
|
+
values: option.values.join(", ")
|
|
1666
|
+
})] }, `${option.attribute}:${option.values.join(",")}`))
|
|
1344
1667
|
})
|
|
1345
1668
|
});
|
|
1346
1669
|
}
|
|
@@ -1348,11 +1671,12 @@ function CartItemOptions({ className }) {
|
|
|
1348
1671
|
function CartItemPrice({ className }) {
|
|
1349
1672
|
const { subtotal, currency } = useCartItemInternal("CartItemPrice");
|
|
1350
1673
|
const money$1 = useMoney();
|
|
1674
|
+
const text = useMessage();
|
|
1351
1675
|
return /* @__PURE__ */ jsx("div", {
|
|
1352
1676
|
...partProps("cart-item-price", className),
|
|
1353
1677
|
children: /* @__PURE__ */ jsx("p", {
|
|
1354
1678
|
className: "text-sm leading-6 tabular-nums",
|
|
1355
|
-
children: subtotal === null ? "
|
|
1679
|
+
children: subtotal === null ? text("priceUnavailable") : money$1(subtotal, currency)
|
|
1356
1680
|
})
|
|
1357
1681
|
});
|
|
1358
1682
|
}
|
|
@@ -1375,6 +1699,7 @@ function StepperIcon$1({ plus }) {
|
|
|
1375
1699
|
function CartItemQuantity({ className }) {
|
|
1376
1700
|
const { id, name, quantity } = useCartItemInternal("CartItemQuantity");
|
|
1377
1701
|
const { action, locked, run, changeQuantity } = useCartInternal("CartItemQuantity");
|
|
1702
|
+
const text = useMessage();
|
|
1378
1703
|
return /* @__PURE__ */ jsx("div", {
|
|
1379
1704
|
...partProps("cart-item-quantity", className),
|
|
1380
1705
|
children: /* @__PURE__ */ jsxs("div", {
|
|
@@ -1383,7 +1708,7 @@ function CartItemQuantity({ className }) {
|
|
|
1383
1708
|
/* @__PURE__ */ jsx("button", {
|
|
1384
1709
|
type: "button",
|
|
1385
1710
|
className: stepperButtonClass$1,
|
|
1386
|
-
"aria-label":
|
|
1711
|
+
"aria-label": text("decreaseQuantity", { name }),
|
|
1387
1712
|
"data-commerce-control": "decrease",
|
|
1388
1713
|
disabled: action === `decrease:${id}` || !id || quantity <= 1,
|
|
1389
1714
|
"aria-disabled": locked || !id || quantity <= 1,
|
|
@@ -1395,13 +1720,13 @@ function CartItemQuantity({ className }) {
|
|
|
1395
1720
|
className: "min-w-8 text-center text-sm tabular-nums",
|
|
1396
1721
|
children: [/* @__PURE__ */ jsx("span", {
|
|
1397
1722
|
className: "sr-only",
|
|
1398
|
-
children: "
|
|
1723
|
+
children: text("quantityPrefix")
|
|
1399
1724
|
}), quantity]
|
|
1400
1725
|
}),
|
|
1401
1726
|
/* @__PURE__ */ jsx("button", {
|
|
1402
1727
|
type: "button",
|
|
1403
1728
|
className: stepperButtonClass$1,
|
|
1404
|
-
"aria-label":
|
|
1729
|
+
"aria-label": text("increaseQuantity", { name }),
|
|
1405
1730
|
"data-commerce-control": "increase",
|
|
1406
1731
|
disabled: action === `increase:${id}` || !id,
|
|
1407
1732
|
"aria-disabled": locked || !id,
|
|
@@ -1417,6 +1742,7 @@ function CartItemQuantity({ className }) {
|
|
|
1417
1742
|
function CartItemRemove({ className, children }) {
|
|
1418
1743
|
const { id, name } = useCartItemInternal("CartItemRemove");
|
|
1419
1744
|
const { action, locked, run, removeItem } = useCartInternal("CartItemRemove");
|
|
1745
|
+
const text = useMessage();
|
|
1420
1746
|
return /* @__PURE__ */ jsx("div", {
|
|
1421
1747
|
...partProps("cart-item-remove", className),
|
|
1422
1748
|
children: /* @__PURE__ */ jsxs("button", {
|
|
@@ -1426,7 +1752,7 @@ function CartItemRemove({ className, children }) {
|
|
|
1426
1752
|
"aria-disabled": locked || !id,
|
|
1427
1753
|
"aria-busy": action === `remove:${id}`,
|
|
1428
1754
|
onClick: () => run(`remove:${id}`, () => removeItem(id)),
|
|
1429
|
-
children: [renderLabel(children, { busy: action === `remove:${id}` }, "
|
|
1755
|
+
children: [renderLabel(children, { busy: action === `remove:${id}` }, text("remove")), /* @__PURE__ */ jsxs("span", {
|
|
1430
1756
|
className: "sr-only",
|
|
1431
1757
|
children: [" ", name]
|
|
1432
1758
|
})]
|
|
@@ -1452,6 +1778,7 @@ const packagedItem = /* @__PURE__ */ jsxs(CartItemRoot, {
|
|
|
1452
1778
|
/** Renders only when the cart is empty: a message and a link to the catalog, or its children. */
|
|
1453
1779
|
function CartEmpty({ className, children }) {
|
|
1454
1780
|
const { items, hydrating, isSample, catalogPath, close } = useCartInternal("CartEmpty");
|
|
1781
|
+
const text = useMessage();
|
|
1455
1782
|
if (hydrating || items.length) return null;
|
|
1456
1783
|
return /* @__PURE__ */ jsx("div", {
|
|
1457
1784
|
...partProps("cart-empty", className),
|
|
@@ -1474,17 +1801,17 @@ function CartEmpty({ className, children }) {
|
|
|
1474
1801
|
}),
|
|
1475
1802
|
/* @__PURE__ */ jsx("p", {
|
|
1476
1803
|
className: "text-base font-semibold",
|
|
1477
|
-
children: "
|
|
1804
|
+
children: text("cartEmpty")
|
|
1478
1805
|
}),
|
|
1479
1806
|
/* @__PURE__ */ jsx("p", {
|
|
1480
1807
|
className: "mt-1 text-sm text-commerce-muted",
|
|
1481
|
-
children: isSample ? "
|
|
1808
|
+
children: text(isSample ? "sampleCartEmptyHint" : "cartEmptyHint")
|
|
1482
1809
|
}),
|
|
1483
1810
|
/* @__PURE__ */ jsx(Link, {
|
|
1484
1811
|
className: `${secondaryButtonClass} mt-6`,
|
|
1485
1812
|
to: catalogPath,
|
|
1486
1813
|
onClick: close,
|
|
1487
|
-
children: "
|
|
1814
|
+
children: text("browseProducts")
|
|
1488
1815
|
})
|
|
1489
1816
|
] })
|
|
1490
1817
|
});
|
|
@@ -1502,12 +1829,13 @@ function CartSummary({ className, children }) {
|
|
|
1502
1829
|
function CartSubtotal({ className }) {
|
|
1503
1830
|
const { items, subtotal, currency, isSample } = useCartInternal("CartSubtotal");
|
|
1504
1831
|
const money$1 = useMoney();
|
|
1832
|
+
const text = useMessage();
|
|
1505
1833
|
if (!items.length || subtotal === null) return null;
|
|
1506
1834
|
return /* @__PURE__ */ jsx("div", {
|
|
1507
1835
|
...partProps("cart-subtotal", className),
|
|
1508
1836
|
children: /* @__PURE__ */ jsx("dl", { children: /* @__PURE__ */ jsxs("div", {
|
|
1509
1837
|
className: "flex justify-between gap-4 font-semibold",
|
|
1510
|
-
children: [/* @__PURE__ */ jsx("dt", { children: isSample ? "
|
|
1838
|
+
children: [/* @__PURE__ */ jsx("dt", { children: text(isSample ? "sampleSubtotal" : "subtotal") }), /* @__PURE__ */ jsx("dd", {
|
|
1511
1839
|
className: "tabular-nums",
|
|
1512
1840
|
children: money$1(subtotal, currency)
|
|
1513
1841
|
})]
|
|
@@ -1520,12 +1848,13 @@ function CartSubtotal({ className }) {
|
|
|
1520
1848
|
*/
|
|
1521
1849
|
function CartCheckoutNote({ className }) {
|
|
1522
1850
|
const { items, isSample } = useCartInternal("CartCheckoutNote");
|
|
1851
|
+
const text = useMessage();
|
|
1523
1852
|
if (isSample || !items.length) return null;
|
|
1524
1853
|
return /* @__PURE__ */ jsx("div", {
|
|
1525
1854
|
...partProps("cart-checkout-note", "commerce-cart-checkout-adjustments-note", className),
|
|
1526
1855
|
children: /* @__PURE__ */ jsxs("dl", {
|
|
1527
1856
|
className: "flex justify-between gap-4 text-sm text-commerce-muted",
|
|
1528
|
-
children: [/* @__PURE__ */ jsx("dt", { children: "
|
|
1857
|
+
children: [/* @__PURE__ */ jsx("dt", { children: text("checkoutAdjustments") }), /* @__PURE__ */ jsx("dd", { children: text("checkoutAdjustmentsValue") })]
|
|
1529
1858
|
})
|
|
1530
1859
|
});
|
|
1531
1860
|
}
|
|
@@ -1535,15 +1864,16 @@ function CartCheckoutNote({ className }) {
|
|
|
1535
1864
|
*/
|
|
1536
1865
|
function CartCheckout({ className, children }) {
|
|
1537
1866
|
const { items, isSample, checkoutEnabled, checkoutReady, hydrating, action, locked, run, checkout } = useCartInternal("CartCheckout");
|
|
1867
|
+
const text = useMessage();
|
|
1538
1868
|
if (!items.length) return null;
|
|
1539
1869
|
let content;
|
|
1540
1870
|
if (isSample) content = /* @__PURE__ */ jsx("p", {
|
|
1541
1871
|
className: "text-sm text-commerce-muted",
|
|
1542
|
-
children: "
|
|
1872
|
+
children: text("sampleCheckoutNote", { currency: sampleCurrency })
|
|
1543
1873
|
});
|
|
1544
1874
|
else if (!checkoutEnabled) content = /* @__PURE__ */ jsx("p", {
|
|
1545
1875
|
className: "text-sm text-commerce-muted",
|
|
1546
|
-
children: "
|
|
1876
|
+
children: text("checkoutUnavailable")
|
|
1547
1877
|
});
|
|
1548
1878
|
else content = /* @__PURE__ */ jsx("button", {
|
|
1549
1879
|
type: "button",
|
|
@@ -1552,7 +1882,7 @@ function CartCheckout({ className, children }) {
|
|
|
1552
1882
|
"aria-disabled": locked || !checkoutReady,
|
|
1553
1883
|
"aria-busy": action === "checkout",
|
|
1554
1884
|
onClick: () => run("checkout", checkout),
|
|
1555
|
-
children: renderLabel(children, { busy: action === "checkout" }, action === "checkout" ? "
|
|
1885
|
+
children: renderLabel(children, { busy: action === "checkout" }, text(action === "checkout" ? "openingCheckout" : "checkout"))
|
|
1556
1886
|
});
|
|
1557
1887
|
return /* @__PURE__ */ jsx("div", {
|
|
1558
1888
|
...partProps("cart-checkout", className),
|
|
@@ -1566,8 +1896,9 @@ function CartCheckout({ className, children }) {
|
|
|
1566
1896
|
function CartContinueShopping({ className, children }) {
|
|
1567
1897
|
const { items, isSample, catalogPath } = useCartInternal("CartContinueShopping");
|
|
1568
1898
|
const inDrawer = !!useContext(CartDrawerContext);
|
|
1899
|
+
const text = useMessage();
|
|
1569
1900
|
if (!items.length) return null;
|
|
1570
|
-
const label = renderLabel(children, { isSample }, "
|
|
1901
|
+
const label = renderLabel(children, { isSample }, text("continueShopping"));
|
|
1571
1902
|
const linkClass = "inline-flex min-h-11 w-full items-center justify-center rounded-lg px-5 py-2 text-sm font-medium transition-colors hover:bg-commerce-tint focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-current";
|
|
1572
1903
|
return /* @__PURE__ */ jsx("div", {
|
|
1573
1904
|
...partProps("cart-continue-shopping", className),
|
|
@@ -1626,6 +1957,7 @@ function CartDrawer() {
|
|
|
1626
1957
|
*/
|
|
1627
1958
|
function ProductImage({ url, name, className = "" }) {
|
|
1628
1959
|
const [failedUrl, setFailedUrl] = useState(null);
|
|
1960
|
+
const text = useMessage();
|
|
1629
1961
|
return url && url !== failedUrl ? /* @__PURE__ */ jsx("img", {
|
|
1630
1962
|
src: url,
|
|
1631
1963
|
alt: name,
|
|
@@ -1635,20 +1967,20 @@ function ProductImage({ url, name, className = "" }) {
|
|
|
1635
1967
|
}) : /* @__PURE__ */ jsx("div", {
|
|
1636
1968
|
className: `flex h-full min-h-44 items-center justify-center bg-commerce-tint p-6 text-center text-sm text-commerce-muted ${className}`,
|
|
1637
1969
|
role: "img",
|
|
1638
|
-
"aria-label":
|
|
1639
|
-
children: "
|
|
1970
|
+
"aria-label": text("imageUnavailable", { name }),
|
|
1971
|
+
children: text("noImage")
|
|
1640
1972
|
});
|
|
1641
1973
|
}
|
|
1642
1974
|
/** Set by `CatalogProducts` for each entry it renders, so the card template needs no product prop. */
|
|
1643
1975
|
const CatalogItemContext = createContext(null);
|
|
1644
1976
|
const titleLink = "title-link";
|
|
1645
|
-
function cardValue(item, productPath, links) {
|
|
1977
|
+
function cardValue(item, productPath, links, sampleNameText) {
|
|
1646
1978
|
if (item.source === "sample") return {
|
|
1647
1979
|
...links,
|
|
1648
1980
|
source: "sample",
|
|
1649
1981
|
sample: item.sample,
|
|
1650
1982
|
product: null,
|
|
1651
|
-
name:
|
|
1983
|
+
name: sampleNameText,
|
|
1652
1984
|
href: null,
|
|
1653
1985
|
isSample: true
|
|
1654
1986
|
};
|
|
@@ -1686,6 +2018,7 @@ function useProductCard() {
|
|
|
1686
2018
|
function ProductCardRoot({ children, product, className }) {
|
|
1687
2019
|
const catalogItem = useContext(CatalogItemContext);
|
|
1688
2020
|
const { config } = useCommerce();
|
|
2021
|
+
const text = useMessage();
|
|
1689
2022
|
const [imageLinked, setImageLinked] = useState(false);
|
|
1690
2023
|
const { register, has } = usePartRegistry(useCallback((registered) => setImageLinked(!registered(titleLink)), []));
|
|
1691
2024
|
useEffect(() => {
|
|
@@ -1696,16 +2029,18 @@ function ProductCardRoot({ children, product, className }) {
|
|
|
1696
2029
|
source: "live",
|
|
1697
2030
|
product
|
|
1698
2031
|
} : null);
|
|
2032
|
+
const sampleText = item?.source === "sample" ? sampleName(text, item.sample) : "";
|
|
1699
2033
|
return item && cardValue(item, config.productPath, {
|
|
1700
2034
|
imageLinked,
|
|
1701
2035
|
register
|
|
1702
|
-
});
|
|
2036
|
+
}, sampleText);
|
|
1703
2037
|
}, [
|
|
1704
2038
|
catalogItem,
|
|
1705
2039
|
product,
|
|
1706
2040
|
config.productPath,
|
|
1707
2041
|
imageLinked,
|
|
1708
|
-
register
|
|
2042
|
+
register,
|
|
2043
|
+
text
|
|
1709
2044
|
]);
|
|
1710
2045
|
if (catalogItem && product) throw new Error("Inside <CatalogProducts>, ProductCardRoot shows each catalog product. Remove its product prop.");
|
|
1711
2046
|
if (!value) throw new Error("ProductCardRoot needs a product prop outside <CatalogProducts>.");
|
|
@@ -1774,11 +2109,11 @@ function ProductCardDescription({ className }) {
|
|
|
1774
2109
|
})
|
|
1775
2110
|
});
|
|
1776
2111
|
}
|
|
1777
|
-
const formatRange = (range, money$1) => {
|
|
2112
|
+
const formatRange = (range, money$1, moneyRange$1) => {
|
|
1778
2113
|
const min = range?.min;
|
|
1779
2114
|
const max = range?.max;
|
|
1780
2115
|
if (typeof min !== "number") return null;
|
|
1781
|
-
return
|
|
2116
|
+
return typeof max === "number" && max !== min ? moneyRange$1(min, max) : money$1(min);
|
|
1782
2117
|
};
|
|
1783
2118
|
/**
|
|
1784
2119
|
* "Sale" when some SKU's compare-at price is above that SKU's price. Without SKU prices, as in the catalog list, only
|
|
@@ -1786,12 +2121,13 @@ const formatRange = (range, money$1) => {
|
|
|
1786
2121
|
*/
|
|
1787
2122
|
function ProductCardBadge({ className }) {
|
|
1788
2123
|
const { product } = useProductCardInternal("ProductCardBadge");
|
|
2124
|
+
const text = useMessage();
|
|
1789
2125
|
if (!isProductOnSale(product)) return null;
|
|
1790
2126
|
return /* @__PURE__ */ jsx("div", {
|
|
1791
2127
|
...partProps("product-card-badge", className),
|
|
1792
2128
|
children: /* @__PURE__ */ jsx("span", {
|
|
1793
2129
|
className: saleBadgeClass,
|
|
1794
|
-
children: "
|
|
2130
|
+
children: text("sale")
|
|
1795
2131
|
})
|
|
1796
2132
|
});
|
|
1797
2133
|
}
|
|
@@ -1799,7 +2135,9 @@ function ProductCardBadge({ className }) {
|
|
|
1799
2135
|
function ProductCardPrice({ className }) {
|
|
1800
2136
|
const card = useProductCardInternal("ProductCardPrice");
|
|
1801
2137
|
const money$1 = useMoney();
|
|
1802
|
-
const
|
|
2138
|
+
const moneyRange$1 = useMoneyRange();
|
|
2139
|
+
const message = useMessage();
|
|
2140
|
+
const text = card.source === "sample" ? money$1(card.sample.price, sampleCurrency) : formatRange(card.product.priceRange, money$1, moneyRange$1) ?? message("priceUnavailable");
|
|
1803
2141
|
const range = card.source === "live" ? card.product.priceRange : null;
|
|
1804
2142
|
const starting = card.source === "live" && range?.min === range?.max ? getStartingCompareAt(card.product) : null;
|
|
1805
2143
|
const compareAt = starting === null ? null : money$1(starting);
|
|
@@ -1812,7 +2150,7 @@ function ProductCardPrice({ className }) {
|
|
|
1812
2150
|
children: text
|
|
1813
2151
|
}), compareAt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1814
2152
|
className: "sr-only",
|
|
1815
|
-
children: "
|
|
2153
|
+
children: message("wasPrice")
|
|
1816
2154
|
}), /* @__PURE__ */ jsx("del", {
|
|
1817
2155
|
className: "text-commerce-muted",
|
|
1818
2156
|
children: compareAt
|
|
@@ -1827,6 +2165,7 @@ function ProductCardPrice({ className }) {
|
|
|
1827
2165
|
function ProductCardSwatches({ className }) {
|
|
1828
2166
|
const { product } = useProductCardInternal("ProductCardSwatches");
|
|
1829
2167
|
const { getSwatch } = useCommerce();
|
|
2168
|
+
const text = useMessage();
|
|
1830
2169
|
if (!getSwatch || !product) return null;
|
|
1831
2170
|
for (const attribute of getProductAttributes(product)) {
|
|
1832
2171
|
const swatches = attribute.values.map((value) => ({
|
|
@@ -1844,16 +2183,13 @@ function ProductCardSwatches({ className }) {
|
|
|
1844
2183
|
children: /* @__PURE__ */ jsxs("p", {
|
|
1845
2184
|
className: "flex items-center gap-1.5 text-xs text-commerce-muted",
|
|
1846
2185
|
children: [
|
|
1847
|
-
/* @__PURE__ */
|
|
2186
|
+
/* @__PURE__ */ jsx("span", {
|
|
1848
2187
|
className: "sr-only",
|
|
1849
|
-
children:
|
|
1850
|
-
swatches.length,
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
" ",
|
|
1855
|
-
swatches.map((swatch) => swatch.label).join(", ")
|
|
1856
|
-
]
|
|
2188
|
+
children: text("swatchSummary", {
|
|
2189
|
+
count: swatches.length,
|
|
2190
|
+
attribute: inSentence(attribute.label),
|
|
2191
|
+
values: swatches.map((swatch) => swatch.label).join(", ")
|
|
2192
|
+
})
|
|
1857
2193
|
}),
|
|
1858
2194
|
shown.map((swatch) => /* @__PURE__ */ jsx("span", {
|
|
1859
2195
|
"aria-hidden": "true",
|
|
@@ -1892,6 +2228,7 @@ function ProductCardAction({ className, children }) {
|
|
|
1892
2228
|
function LiveAction({ product, name, href, label }) {
|
|
1893
2229
|
const selectedSku = getSingleMatchedSku(product);
|
|
1894
2230
|
const hasVariants = getProductAttributes(product).length > 0;
|
|
2231
|
+
const text = useMessage();
|
|
1895
2232
|
if (!hasVariants && selectedSku) return /* @__PURE__ */ jsx(AddToCart, {
|
|
1896
2233
|
sku: selectedSku,
|
|
1897
2234
|
name,
|
|
@@ -1910,11 +2247,13 @@ function LiveAction({ product, name, href, label }) {
|
|
|
1910
2247
|
action,
|
|
1911
2248
|
busy: false,
|
|
1912
2249
|
soldOut: false
|
|
1913
|
-
}, hasVariants ? "
|
|
2250
|
+
}, text(hasVariants ? "chooseOptions" : "viewProduct"))
|
|
1914
2251
|
});
|
|
1915
2252
|
}
|
|
1916
2253
|
function SampleAction({ sample, label }) {
|
|
1917
2254
|
const { add } = useSampleCart();
|
|
2255
|
+
const text = useMessage();
|
|
2256
|
+
const name = sampleName(text, sample);
|
|
1918
2257
|
const custom = renderLabel(label, {
|
|
1919
2258
|
action: "add",
|
|
1920
2259
|
busy: false,
|
|
@@ -1923,20 +2262,16 @@ function SampleAction({ sample, label }) {
|
|
|
1923
2262
|
return custom === null ? /* @__PURE__ */ jsx("button", {
|
|
1924
2263
|
type: "button",
|
|
1925
2264
|
className: `${secondaryButtonClass} w-full`,
|
|
1926
|
-
"aria-label":
|
|
2265
|
+
"aria-label": text("addSampleToCart", { name }),
|
|
1927
2266
|
onClick: () => add(sample.key),
|
|
1928
|
-
children: "
|
|
2267
|
+
children: text("addToCart")
|
|
1929
2268
|
}) : /* @__PURE__ */ jsxs("button", {
|
|
1930
2269
|
type: "button",
|
|
1931
2270
|
className: `${secondaryButtonClass} w-full`,
|
|
1932
2271
|
onClick: () => add(sample.key),
|
|
1933
2272
|
children: [custom, /* @__PURE__ */ jsxs("span", {
|
|
1934
2273
|
className: "sr-only",
|
|
1935
|
-
children: [
|
|
1936
|
-
" (",
|
|
1937
|
-
sample.name,
|
|
1938
|
-
", sample)"
|
|
1939
|
-
]
|
|
2274
|
+
children: [" ", text("sampleItemName", { name })]
|
|
1940
2275
|
})]
|
|
1941
2276
|
});
|
|
1942
2277
|
}
|
|
@@ -2075,36 +2410,44 @@ function CatalogLoader({ children, className, pageSize, urlState }) {
|
|
|
2075
2410
|
});
|
|
2076
2411
|
}
|
|
2077
2412
|
/** The catalog title as the page's H1, with a description. */
|
|
2078
|
-
function CatalogHeader({ title
|
|
2413
|
+
function CatalogHeader({ title, description, className }) {
|
|
2079
2414
|
useCatalogInternal("CatalogHeader");
|
|
2415
|
+
const text = useMessage();
|
|
2080
2416
|
return /* @__PURE__ */ jsxs("header", {
|
|
2081
2417
|
...partProps("catalog-header", className),
|
|
2082
|
-
children: [/* @__PURE__ */ jsx("h1", {
|
|
2418
|
+
children: [title !== null && /* @__PURE__ */ jsx("h1", {
|
|
2083
2419
|
className: "max-w-3xl text-balance text-3xl font-semibold leading-tight tracking-tight",
|
|
2084
|
-
children: title
|
|
2085
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
2420
|
+
children: title === void 0 ? text("catalogTitle") : title
|
|
2421
|
+
}), description !== null && /* @__PURE__ */ jsx("p", {
|
|
2086
2422
|
className: "mt-1 max-w-xl text-base leading-7 text-commerce-muted",
|
|
2087
|
-
children: description
|
|
2423
|
+
children: description === void 0 ? text("catalogDescription") : description
|
|
2088
2424
|
})]
|
|
2089
2425
|
});
|
|
2090
2426
|
}
|
|
2091
2427
|
/** Announces loading, failure, or how many products the page shows. */
|
|
2092
2428
|
function CatalogSummary({ className }) {
|
|
2093
|
-
const
|
|
2429
|
+
const { key, values } = summary(useCatalogInternal("CatalogSummary"));
|
|
2430
|
+
const text = useMessage();
|
|
2094
2431
|
return /* @__PURE__ */ jsx("div", {
|
|
2095
2432
|
...partProps("catalog-summary", className),
|
|
2096
2433
|
children: /* @__PURE__ */ jsx("p", {
|
|
2097
2434
|
className: "text-sm text-commerce-muted",
|
|
2098
2435
|
role: "status",
|
|
2099
|
-
children:
|
|
2436
|
+
children: text(key, values)
|
|
2100
2437
|
})
|
|
2101
2438
|
});
|
|
2102
2439
|
}
|
|
2103
2440
|
function summary(catalog) {
|
|
2104
|
-
if (catalog.source === "sample") return
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2441
|
+
if (catalog.source === "sample") return {
|
|
2442
|
+
key: "sampleCatalogSummary",
|
|
2443
|
+
values: { currency: sampleCurrency }
|
|
2444
|
+
};
|
|
2445
|
+
if (catalog.products.isPending) return { key: "loadingProducts" };
|
|
2446
|
+
if (catalog.products.isError) return { key: "productsUnavailable" };
|
|
2447
|
+
return {
|
|
2448
|
+
key: "productCount",
|
|
2449
|
+
values: { count: catalog.nodes.length }
|
|
2450
|
+
};
|
|
2108
2451
|
}
|
|
2109
2452
|
/** Two columns on phones, then as many 240px columns as fit. Shared with `RelatedProducts`. */
|
|
2110
2453
|
const catalogGridClass = "grid grid-cols-2 gap-x-4 gap-y-10 sm:gap-x-6 md:grid-cols-[repeat(auto-fill,minmax(240px,1fr))] lg:gap-y-12";
|
|
@@ -2136,20 +2479,22 @@ function CatalogProducts({ className, children }) {
|
|
|
2136
2479
|
}
|
|
2137
2480
|
})) : sampleItems, [nodes]);
|
|
2138
2481
|
const products = catalog.source === "live" ? catalog.products : null;
|
|
2482
|
+
const { messages } = useCommerce();
|
|
2483
|
+
const text = useMessage();
|
|
2139
2484
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2140
2485
|
products?.isError && /* @__PURE__ */ jsxs("div", {
|
|
2141
2486
|
role: "alert",
|
|
2142
2487
|
className: errorNoticeClass,
|
|
2143
|
-
children: [/* @__PURE__ */ jsx("p", { children:
|
|
2488
|
+
children: [/* @__PURE__ */ jsx("p", { children: describeError(products.error, messages) }), /* @__PURE__ */ jsx("button", {
|
|
2144
2489
|
type: "button",
|
|
2145
2490
|
className: errorActionClass,
|
|
2146
2491
|
onClick: () => void products.refetch(),
|
|
2147
|
-
children: "
|
|
2492
|
+
children: text("retryProducts")
|
|
2148
2493
|
})]
|
|
2149
2494
|
}),
|
|
2150
2495
|
products?.isSuccess && !items.length && !customEmpty && /* @__PURE__ */ jsx("p", {
|
|
2151
2496
|
className: "py-16 text-center text-commerce-muted",
|
|
2152
|
-
children: "
|
|
2497
|
+
children: text("noProducts")
|
|
2153
2498
|
}),
|
|
2154
2499
|
/* @__PURE__ */ jsx("div", {
|
|
2155
2500
|
...partProps("catalog-products", className),
|
|
@@ -2183,10 +2528,11 @@ function CatalogEmpty({ className, children }) {
|
|
|
2183
2528
|
const catalog = useCatalogInternal("CatalogEmpty");
|
|
2184
2529
|
const register = useContext(CatalogEmptyContext)?.register;
|
|
2185
2530
|
useEffect(() => register?.("empty"), [register]);
|
|
2531
|
+
const text = useMessage();
|
|
2186
2532
|
if (catalog.source === "sample" || !catalog.products.isSuccess || catalog.nodes.length) return null;
|
|
2187
2533
|
return /* @__PURE__ */ jsx("div", {
|
|
2188
2534
|
...partProps("catalog-empty", className),
|
|
2189
|
-
children: hasChildren(children) ? children : /* @__PURE__ */ jsx("p", { children: "
|
|
2535
|
+
children: hasChildren(children) ? children : /* @__PURE__ */ jsx("p", { children: text("noProducts") })
|
|
2190
2536
|
});
|
|
2191
2537
|
}
|
|
2192
2538
|
/**
|
|
@@ -2194,13 +2540,14 @@ function CatalogEmpty({ className, children }) {
|
|
|
2194
2540
|
*/
|
|
2195
2541
|
function CatalogPagination({ className }) {
|
|
2196
2542
|
const catalog = useCatalogInternal("CatalogPagination");
|
|
2543
|
+
const text = useMessage();
|
|
2197
2544
|
if (catalog.source === "sample") return null;
|
|
2198
2545
|
const { after, goToPage } = catalog;
|
|
2199
2546
|
const pageInfo = catalog.products.data?.skuGroups?.pageInfo;
|
|
2200
2547
|
const hasNext = !!(pageInfo?.hasNextPage && pageInfo.endCursor);
|
|
2201
2548
|
if (!after && !hasNext) return null;
|
|
2202
2549
|
return /* @__PURE__ */ jsx("nav", {
|
|
2203
|
-
"aria-label": "
|
|
2550
|
+
"aria-label": text("productPages"),
|
|
2204
2551
|
...partProps("catalog-pagination", className),
|
|
2205
2552
|
children: /* @__PURE__ */ jsxs("div", {
|
|
2206
2553
|
className: "flex justify-center gap-3",
|
|
@@ -2208,12 +2555,12 @@ function CatalogPagination({ className }) {
|
|
|
2208
2555
|
type: "button",
|
|
2209
2556
|
className: secondaryButtonClass,
|
|
2210
2557
|
onClick: () => goToPage(),
|
|
2211
|
-
children: "
|
|
2558
|
+
children: text("firstPage")
|
|
2212
2559
|
}), hasNext && pageInfo?.endCursor && /* @__PURE__ */ jsx("button", {
|
|
2213
2560
|
type: "button",
|
|
2214
2561
|
className: secondaryButtonClass,
|
|
2215
2562
|
onClick: () => goToPage(pageInfo.endCursor ?? void 0),
|
|
2216
|
-
children: "
|
|
2563
|
+
children: text("nextPage")
|
|
2217
2564
|
})]
|
|
2218
2565
|
})
|
|
2219
2566
|
});
|
|
@@ -2325,7 +2672,8 @@ function ProductDetailsRoot({ productId: productIdProp, className,...props }) {
|
|
|
2325
2672
|
}) });
|
|
2326
2673
|
}
|
|
2327
2674
|
function ProductDetailsLoader({ productId, className, children, urlState = true, loading, notFound, error }) {
|
|
2328
|
-
const { config, getSwatch } = useCommerce();
|
|
2675
|
+
const { config, getSwatch, messages } = useCommerce();
|
|
2676
|
+
const text = useMessage();
|
|
2329
2677
|
const [params, setParams] = useRootParams(urlState);
|
|
2330
2678
|
const [quantity, setQuantity] = useState(1);
|
|
2331
2679
|
const [imageIndex, setImageIndex] = useState(0);
|
|
@@ -2403,7 +2751,7 @@ function ProductDetailsLoader({ productId, className, children, urlState = true,
|
|
|
2403
2751
|
children: hasChildren(loading) ? loading : /* @__PURE__ */ jsx("p", {
|
|
2404
2752
|
role: "status",
|
|
2405
2753
|
className: "text-sm text-commerce-muted",
|
|
2406
|
-
children: "
|
|
2754
|
+
children: text("loadingProduct")
|
|
2407
2755
|
})
|
|
2408
2756
|
});
|
|
2409
2757
|
if (product.isError && !productNotFound) {
|
|
@@ -2412,22 +2760,22 @@ function ProductDetailsLoader({ productId, className, children, urlState = true,
|
|
|
2412
2760
|
role: "alert",
|
|
2413
2761
|
...partProps("product-details-root", className),
|
|
2414
2762
|
children: renderLabel(error, {
|
|
2415
|
-
message:
|
|
2763
|
+
message: describeError(product.error, messages),
|
|
2416
2764
|
retry
|
|
2417
2765
|
}, /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2418
2766
|
/* @__PURE__ */ jsx("h1", {
|
|
2419
2767
|
className: "text-2xl font-semibold tracking-tight",
|
|
2420
|
-
children: "
|
|
2768
|
+
children: text("productUnavailable")
|
|
2421
2769
|
}),
|
|
2422
2770
|
/* @__PURE__ */ jsx("p", {
|
|
2423
2771
|
className: `my-4 ${errorNoticeClass}`,
|
|
2424
|
-
children:
|
|
2772
|
+
children: describeError(product.error, messages)
|
|
2425
2773
|
}),
|
|
2426
2774
|
/* @__PURE__ */ jsx("button", {
|
|
2427
2775
|
type: "button",
|
|
2428
2776
|
className: buttonClass,
|
|
2429
2777
|
onClick: retry,
|
|
2430
|
-
children: "
|
|
2778
|
+
children: text("retryProduct")
|
|
2431
2779
|
})
|
|
2432
2780
|
] }))
|
|
2433
2781
|
});
|
|
@@ -2436,11 +2784,11 @@ function ProductDetailsLoader({ productId, className, children, urlState = true,
|
|
|
2436
2784
|
...partProps("product-details-root", className),
|
|
2437
2785
|
children: hasChildren(notFound) ? notFound : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("h1", {
|
|
2438
2786
|
className: "text-2xl font-semibold tracking-tight",
|
|
2439
|
-
children: "
|
|
2787
|
+
children: text("productNotFound")
|
|
2440
2788
|
}), /* @__PURE__ */ jsx(Link, {
|
|
2441
2789
|
className: `${buttonClass} mt-6`,
|
|
2442
2790
|
to: config.catalogPath,
|
|
2443
|
-
children: "
|
|
2791
|
+
children: text("backToShop")
|
|
2444
2792
|
})] })
|
|
2445
2793
|
});
|
|
2446
2794
|
const value = {
|
|
@@ -2539,11 +2887,11 @@ function getSoldOutValues(group, attributes, selections) {
|
|
|
2539
2887
|
//#endregion
|
|
2540
2888
|
//#region src/product-purchase.tsx
|
|
2541
2889
|
const disabledReasons = {
|
|
2542
|
-
incomplete: "
|
|
2543
|
-
checking: "
|
|
2544
|
-
"variant-error": "
|
|
2545
|
-
unavailable: "
|
|
2546
|
-
"not-purchasable": "
|
|
2890
|
+
incomplete: "selectAllOptions",
|
|
2891
|
+
checking: "checkingVariant",
|
|
2892
|
+
"variant-error": "variantCheckFailed",
|
|
2893
|
+
unavailable: "combinationUnavailable",
|
|
2894
|
+
"not-purchasable": "notPurchasable"
|
|
2547
2895
|
};
|
|
2548
2896
|
/** Above this many values, buttons wrap into a wall, so the option is a select. */
|
|
2549
2897
|
const maxChoiceButtons = 8;
|
|
@@ -2608,14 +2956,11 @@ const swatchClass = "relative block h-9 w-9 overflow-hidden rounded-full border
|
|
|
2608
2956
|
function ChoiceGroup({ label, selected, choices, missing, onSelect }) {
|
|
2609
2957
|
const name = useId();
|
|
2610
2958
|
const errorId = useId();
|
|
2611
|
-
const
|
|
2959
|
+
const text = useMessage();
|
|
2960
|
+
const error = missing && /* @__PURE__ */ jsx("p", {
|
|
2612
2961
|
id: errorId,
|
|
2613
2962
|
className: "mt-2 inline-block rounded bg-red-50 px-2 py-1 text-sm text-red-800",
|
|
2614
|
-
children:
|
|
2615
|
-
"Select ",
|
|
2616
|
-
inSentence(label),
|
|
2617
|
-
"."
|
|
2618
|
-
]
|
|
2963
|
+
children: text("selectAttributeError", { attribute: inSentence(label) })
|
|
2619
2964
|
});
|
|
2620
2965
|
if (choices.length > maxChoiceButtons) return /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
2621
2966
|
/* @__PURE__ */ jsx("legend", {
|
|
@@ -2628,12 +2973,12 @@ function ChoiceGroup({ label, selected, choices, missing, onSelect }) {
|
|
|
2628
2973
|
"aria-describedby": missing ? errorId : void 0,
|
|
2629
2974
|
value: selected,
|
|
2630
2975
|
onChange: (event) => onSelect(event.target.value),
|
|
2631
|
-
children: [/* @__PURE__ */
|
|
2976
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
2632
2977
|
value: "",
|
|
2633
|
-
children:
|
|
2978
|
+
children: text("selectAttribute", { attribute: inSentence(label) })
|
|
2634
2979
|
}), choices.map((choice) => /* @__PURE__ */ jsx("option", {
|
|
2635
2980
|
value: choice.value,
|
|
2636
|
-
children: choice.soldOut ?
|
|
2981
|
+
children: choice.soldOut ? text("soldOutOption", { label: choice.label }) : choice.label
|
|
2637
2982
|
}, choice.value))]
|
|
2638
2983
|
}),
|
|
2639
2984
|
error
|
|
@@ -2654,7 +2999,7 @@ function ChoiceGroup({ label, selected, choices, missing, onSelect }) {
|
|
|
2654
2999
|
className: `flex flex-wrap ${swatches ? "gap-3" : "gap-2"}`,
|
|
2655
3000
|
children: choices.map((choice) => /* @__PURE__ */ jsxs("label", {
|
|
2656
3001
|
className: "group relative cursor-pointer",
|
|
2657
|
-
title: swatches ?
|
|
3002
|
+
title: swatches ? choice.soldOut ? text("soldOutChoice", { label: choice.label }) : choice.label : void 0,
|
|
2658
3003
|
children: [/* @__PURE__ */ jsx("input", {
|
|
2659
3004
|
type: "radio",
|
|
2660
3005
|
className: "peer absolute inset-0 z-10 m-0 cursor-pointer opacity-0",
|
|
@@ -2662,7 +3007,7 @@ function ChoiceGroup({ label, selected, choices, missing, onSelect }) {
|
|
|
2662
3007
|
value: choice.value,
|
|
2663
3008
|
checked: choice.value === selected,
|
|
2664
3009
|
"aria-invalid": missing || void 0,
|
|
2665
|
-
"aria-label": choice.soldOut ?
|
|
3010
|
+
"aria-label": choice.soldOut ? text("soldOutChoice", { label: choice.label }) : void 0,
|
|
2666
3011
|
"data-commerce-control": "choice",
|
|
2667
3012
|
onChange: () => onSelect(choice.value)
|
|
2668
3013
|
}), swatches ? /* @__PURE__ */ jsxs("span", {
|
|
@@ -2688,12 +3033,13 @@ function ChoiceGroup({ label, selected, choices, missing, onSelect }) {
|
|
|
2688
3033
|
function SkuOption() {
|
|
2689
3034
|
const { skuOptions, explicitSkuId, selectSku, showMissing } = useProductDetailsInternal("ProductDetailsOptions");
|
|
2690
3035
|
useSkuOptionRegistration();
|
|
3036
|
+
const text = useMessage();
|
|
2691
3037
|
const missing = showMissing && !explicitSkuId;
|
|
2692
3038
|
return /* @__PURE__ */ jsx("div", {
|
|
2693
3039
|
...partProps("product-details-sku-option"),
|
|
2694
3040
|
"data-commerce-missing": missing || void 0,
|
|
2695
3041
|
children: /* @__PURE__ */ jsx(ChoiceGroup, {
|
|
2696
|
-
label: "
|
|
3042
|
+
label: text("skuOptionLabel"),
|
|
2697
3043
|
selected: explicitSkuId,
|
|
2698
3044
|
missing,
|
|
2699
3045
|
onSelect: selectSku,
|
|
@@ -2737,6 +3083,8 @@ function ProductDetailsOption({ attribute, className }) {
|
|
|
2737
3083
|
*/
|
|
2738
3084
|
function ProductDetailsPurchaseStatus({ className }) {
|
|
2739
3085
|
const { status, variantError, retryVariant } = useProductDetailsInternal("ProductDetailsPurchaseStatus");
|
|
3086
|
+
const { messages } = useCommerce();
|
|
3087
|
+
const text = useMessage();
|
|
2740
3088
|
let content;
|
|
2741
3089
|
switch (status) {
|
|
2742
3090
|
case "ready":
|
|
@@ -2745,11 +3093,11 @@ function ProductDetailsPurchaseStatus({ className }) {
|
|
|
2745
3093
|
content = /* @__PURE__ */ jsxs("div", {
|
|
2746
3094
|
role: "alert",
|
|
2747
3095
|
className: errorNoticeClass,
|
|
2748
|
-
children: [/* @__PURE__ */ jsx("p", { children:
|
|
3096
|
+
children: [/* @__PURE__ */ jsx("p", { children: describeError(variantError, messages) }), /* @__PURE__ */ jsx("button", {
|
|
2749
3097
|
type: "button",
|
|
2750
3098
|
className: errorActionClass,
|
|
2751
3099
|
onClick: retryVariant,
|
|
2752
|
-
children: "
|
|
3100
|
+
children: text("retryAvailability")
|
|
2753
3101
|
})]
|
|
2754
3102
|
});
|
|
2755
3103
|
break;
|
|
@@ -2757,20 +3105,20 @@ function ProductDetailsPurchaseStatus({ className }) {
|
|
|
2757
3105
|
content = /* @__PURE__ */ jsx("p", {
|
|
2758
3106
|
role: "status",
|
|
2759
3107
|
className: errorNoticeClass,
|
|
2760
|
-
children: disabledReasons[status]
|
|
3108
|
+
children: text(disabledReasons[status])
|
|
2761
3109
|
});
|
|
2762
3110
|
break;
|
|
2763
3111
|
case "not-purchasable":
|
|
2764
3112
|
content = /* @__PURE__ */ jsx("p", {
|
|
2765
3113
|
className: "text-sm text-commerce-muted",
|
|
2766
|
-
children: disabledReasons[status]
|
|
3114
|
+
children: text(disabledReasons[status])
|
|
2767
3115
|
});
|
|
2768
3116
|
break;
|
|
2769
3117
|
case "checking":
|
|
2770
3118
|
content = /* @__PURE__ */ jsx("p", {
|
|
2771
3119
|
role: "status",
|
|
2772
3120
|
className: "text-sm text-commerce-muted",
|
|
2773
|
-
children: disabledReasons[status]
|
|
3121
|
+
children: text(disabledReasons[status])
|
|
2774
3122
|
});
|
|
2775
3123
|
break;
|
|
2776
3124
|
}
|
|
@@ -2782,13 +3130,14 @@ function ProductDetailsPurchaseStatus({ className }) {
|
|
|
2782
3130
|
/** Stock for the verified SKU. Renders nothing until a SKU is verified. */
|
|
2783
3131
|
function ProductDetailsAvailability({ className }) {
|
|
2784
3132
|
const { selectedSku, availableQuantity } = useProductDetailsInternal("ProductDetailsAvailability");
|
|
3133
|
+
const text = useMessage();
|
|
2785
3134
|
if (!selectedSku) return null;
|
|
2786
3135
|
return /* @__PURE__ */ jsx("div", {
|
|
2787
3136
|
...partProps("product-details-availability", className),
|
|
2788
3137
|
children: /* @__PURE__ */ jsx("p", {
|
|
2789
3138
|
className: "text-sm text-commerce-muted",
|
|
2790
3139
|
"aria-live": "polite",
|
|
2791
|
-
children: availableQuantity === null ? "
|
|
3140
|
+
children: availableQuantity === null ? text("availableToOrder") : isSoldOut(availableQuantity) ? text("variantOutOfStock") : text("availableCount", { count: availableQuantity })
|
|
2792
3141
|
})
|
|
2793
3142
|
});
|
|
2794
3143
|
}
|
|
@@ -2815,9 +3164,11 @@ function ProductDetailsQuantity({ className }) {
|
|
|
2815
3164
|
const { selectedSku, availableQuantity, quantity, setQuantity, name } = useProductDetailsInternal("ProductDetailsQuantity");
|
|
2816
3165
|
const id = useId();
|
|
2817
3166
|
const problemId = useId();
|
|
3167
|
+
const text = useMessage();
|
|
2818
3168
|
const soldOut = !!selectedSku && isSoldOut(availableQuantity);
|
|
2819
3169
|
const max = selectedSku && availableQuantity !== null && availableQuantity > 0 ? availableQuantity : null;
|
|
2820
|
-
const
|
|
3170
|
+
const issue = soldOut ? null : quantityProblem(quantity, max);
|
|
3171
|
+
const problem = issue && text(issue.key, issue.values);
|
|
2821
3172
|
const current = Number.isInteger(quantity) ? quantity : 1;
|
|
2822
3173
|
return /* @__PURE__ */ jsxs("div", {
|
|
2823
3174
|
...partProps("product-details-quantity", className),
|
|
@@ -2825,7 +3176,7 @@ function ProductDetailsQuantity({ className }) {
|
|
|
2825
3176
|
/* @__PURE__ */ jsx("label", {
|
|
2826
3177
|
htmlFor: id,
|
|
2827
3178
|
className: "sr-only",
|
|
2828
|
-
children: "
|
|
3179
|
+
children: text("quantity")
|
|
2829
3180
|
}),
|
|
2830
3181
|
/* @__PURE__ */ jsxs("div", {
|
|
2831
3182
|
className: "inline-flex items-center rounded-lg border border-commerce-edge",
|
|
@@ -2833,7 +3184,7 @@ function ProductDetailsQuantity({ className }) {
|
|
|
2833
3184
|
/* @__PURE__ */ jsx("button", {
|
|
2834
3185
|
type: "button",
|
|
2835
3186
|
className: stepperButtonClass,
|
|
2836
|
-
"aria-label":
|
|
3187
|
+
"aria-label": text("decreaseQuantity", { name }),
|
|
2837
3188
|
"data-commerce-control": "decrease",
|
|
2838
3189
|
disabled: soldOut || current <= 1,
|
|
2839
3190
|
onClick: () => setQuantity(current - 1),
|
|
@@ -2855,7 +3206,7 @@ function ProductDetailsQuantity({ className }) {
|
|
|
2855
3206
|
/* @__PURE__ */ jsx("button", {
|
|
2856
3207
|
type: "button",
|
|
2857
3208
|
className: stepperButtonClass,
|
|
2858
|
-
"aria-label":
|
|
3209
|
+
"aria-label": text("increaseQuantity", { name }),
|
|
2859
3210
|
"data-commerce-control": "increase",
|
|
2860
3211
|
disabled: soldOut || max !== null && current >= max,
|
|
2861
3212
|
onClick: () => setQuantity(current + 1),
|
|
@@ -2880,6 +3231,7 @@ function ProductDetailsAddToCart({ className, children }) {
|
|
|
2880
3231
|
const { selectedSku, name, quantity, status, price, attributes, selections, requestMissing } = useProductDetailsInternal("ProductDetailsAddToCart");
|
|
2881
3232
|
const { config } = useCommerce();
|
|
2882
3233
|
const money$1 = useMoney();
|
|
3234
|
+
const text = useMessage();
|
|
2883
3235
|
const reasonId = useId();
|
|
2884
3236
|
if (selectedSku) {
|
|
2885
3237
|
const unit = price?.value?.value;
|
|
@@ -2892,8 +3244,8 @@ function ProductDetailsAddToCart({ className, children }) {
|
|
|
2892
3244
|
quantity,
|
|
2893
3245
|
className: buttonClass,
|
|
2894
3246
|
block: true,
|
|
2895
|
-
label: total &&
|
|
2896
|
-
labelName: total ?
|
|
3247
|
+
label: total && text("addToCartWithPrice", { price: total }),
|
|
3248
|
+
labelName: total ? text("addToCartWithPriceName", { price: total }) : void 0,
|
|
2897
3249
|
children
|
|
2898
3250
|
})
|
|
2899
3251
|
});
|
|
@@ -2910,7 +3262,7 @@ function ProductDetailsAddToCart({ className, children }) {
|
|
|
2910
3262
|
children: renderLabel(children, {
|
|
2911
3263
|
busy: false,
|
|
2912
3264
|
soldOut: false
|
|
2913
|
-
}, next ?
|
|
3265
|
+
}, next ? text("selectAttribute", { attribute: inSentence(next) }) : text("selectOption"))
|
|
2914
3266
|
})
|
|
2915
3267
|
});
|
|
2916
3268
|
}
|
|
@@ -2925,11 +3277,11 @@ function ProductDetailsAddToCart({ className, children }) {
|
|
|
2925
3277
|
children: renderLabel(children, {
|
|
2926
3278
|
busy: false,
|
|
2927
3279
|
soldOut: false
|
|
2928
|
-
}, "
|
|
3280
|
+
}, text("addToCart"))
|
|
2929
3281
|
}), /* @__PURE__ */ jsx("span", {
|
|
2930
3282
|
id: reasonId,
|
|
2931
3283
|
hidden: true,
|
|
2932
|
-
children: status === "ready" ? "" : disabledReasons[status]
|
|
3284
|
+
children: status === "ready" ? "" : text(disabledReasons[status])
|
|
2933
3285
|
})]
|
|
2934
3286
|
});
|
|
2935
3287
|
}
|
|
@@ -2958,6 +3310,7 @@ function ProductDetails() {
|
|
|
2958
3310
|
function ProductDetailsBackLink({ className, children }) {
|
|
2959
3311
|
useProductDetailsInternal("ProductDetailsBackLink");
|
|
2960
3312
|
const { config } = useCommerce();
|
|
3313
|
+
const text = useMessage();
|
|
2961
3314
|
return /* @__PURE__ */ jsx("div", {
|
|
2962
3315
|
...partProps("product-details-back-link", className),
|
|
2963
3316
|
children: /* @__PURE__ */ jsx(Link, {
|
|
@@ -2975,13 +3328,14 @@ function ProductDetailsBackLink({ className, children }) {
|
|
|
2975
3328
|
strokeLinecap: "round",
|
|
2976
3329
|
strokeLinejoin: "round"
|
|
2977
3330
|
})
|
|
2978
|
-
}), "
|
|
3331
|
+
}), text("backToShop")] }))
|
|
2979
3332
|
})
|
|
2980
3333
|
});
|
|
2981
3334
|
}
|
|
2982
3335
|
/** Main image with thumbnails; the selected SKU's images replace the product's when present. */
|
|
2983
3336
|
function ProductDetailsGallery({ className }) {
|
|
2984
3337
|
const { images, imageIndex, setImageIndex, name } = useProductDetailsInternal("ProductDetailsGallery");
|
|
3338
|
+
const text = useMessage();
|
|
2985
3339
|
return /* @__PURE__ */ jsxs("div", {
|
|
2986
3340
|
...partProps("product-details-gallery", className),
|
|
2987
3341
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -2995,7 +3349,7 @@ function ProductDetailsGallery({ className }) {
|
|
|
2995
3349
|
children: images.map((url, index) => /* @__PURE__ */ jsx("button", {
|
|
2996
3350
|
type: "button",
|
|
2997
3351
|
className: `h-20 w-20 shrink-0 overflow-hidden bg-commerce-tint outline-offset-2 transition-opacity focus-visible:outline focus-visible:outline-2 focus-visible:outline-current ${imageIndex === index ? "outline outline-2 outline-current" : "opacity-60 hover:opacity-100"}`,
|
|
2998
|
-
"aria-label":
|
|
3352
|
+
"aria-label": text("viewImage", { number: index + 1 }),
|
|
2999
3353
|
"aria-pressed": imageIndex === index,
|
|
3000
3354
|
"data-commerce-control": "thumbnail",
|
|
3001
3355
|
onClick: () => setImageIndex(index),
|
|
@@ -3013,12 +3367,13 @@ function ProductDetailsGallery({ className }) {
|
|
|
3013
3367
|
*/
|
|
3014
3368
|
function ProductDetailsBadge({ className }) {
|
|
3015
3369
|
const { price, product } = useProductDetailsInternal("ProductDetailsBadge");
|
|
3370
|
+
const text = useMessage();
|
|
3016
3371
|
if (!(price ? isOnSale(price.value?.value, price.compareAtValue?.value) : isProductOnSale(product))) return null;
|
|
3017
3372
|
return /* @__PURE__ */ jsx("div", {
|
|
3018
3373
|
...partProps("product-details-badge", className),
|
|
3019
3374
|
children: /* @__PURE__ */ jsx("span", {
|
|
3020
3375
|
className: saleBadgeClass,
|
|
3021
|
-
children: "
|
|
3376
|
+
children: text("sale")
|
|
3022
3377
|
})
|
|
3023
3378
|
});
|
|
3024
3379
|
}
|
|
@@ -3041,6 +3396,7 @@ function ProductDetailsPrice({ className }) {
|
|
|
3041
3396
|
const { price, product } = useProductDetailsInternal("ProductDetailsPrice");
|
|
3042
3397
|
const { config } = useCommerce();
|
|
3043
3398
|
const money$1 = useMoney();
|
|
3399
|
+
const text = useMessage();
|
|
3044
3400
|
let compareAt = null;
|
|
3045
3401
|
if (price?.compareAtValue?.value != null && isOnSale(price.value?.value, price.compareAtValue.value)) compareAt = money$1(price.compareAtValue.value, price.compareAtValue.currencyCode ?? config.currencyCode);
|
|
3046
3402
|
else if (!price) {
|
|
@@ -3053,9 +3409,9 @@ function ProductDetailsPrice({ className }) {
|
|
|
3053
3409
|
"aria-live": "polite",
|
|
3054
3410
|
className: "flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xl font-medium tabular-nums",
|
|
3055
3411
|
"data-testid": "product-price",
|
|
3056
|
-
children: [price?.value?.value != null ? money$1(price.value.value, price.value.currencyCode ?? config.currencyCode) : product.priceRange?.min != null ?
|
|
3412
|
+
children: [price?.value?.value != null ? money$1(price.value.value, price.value.currencyCode ?? config.currencyCode) : product.priceRange?.min != null ? text("priceFrom", { price: money$1(product.priceRange.min) }) : text("priceUnavailable"), compareAt && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
3057
3413
|
className: "sr-only",
|
|
3058
|
-
children: "
|
|
3414
|
+
children: text("wasPrice")
|
|
3059
3415
|
}), /* @__PURE__ */ jsx("del", {
|
|
3060
3416
|
className: "text-base font-normal text-commerce-muted",
|
|
3061
3417
|
children: compareAt
|
|
@@ -3087,10 +3443,11 @@ const packagedCard = /* @__PURE__ */ jsx(ProductCardRoot, {
|
|
|
3087
3443
|
* the products are the catalog's first ones. Renders nothing until they load, when none are left, if the request
|
|
3088
3444
|
* fails, or while no store is connected.
|
|
3089
3445
|
*/
|
|
3090
|
-
function RelatedProducts({ productId: productIdProp, title
|
|
3446
|
+
function RelatedProducts({ productId: productIdProp, title, limit = 4, className, children }) {
|
|
3091
3447
|
const { productId: routeProductId = "" } = useParams();
|
|
3092
3448
|
const productId = productIdProp ?? routeProductId;
|
|
3093
3449
|
const { config, connection } = useCommerce();
|
|
3450
|
+
const text = useMessage();
|
|
3094
3451
|
const headingId = useId();
|
|
3095
3452
|
const first = limit + 1;
|
|
3096
3453
|
const products = useQuery({
|
|
@@ -3117,12 +3474,12 @@ function RelatedProducts({ productId: productIdProp, title = "You may also like"
|
|
|
3117
3474
|
if (connection !== "ready" || !related.length) return null;
|
|
3118
3475
|
const card = hasChildren(children) ? children : packagedCard;
|
|
3119
3476
|
return /* @__PURE__ */ jsx(StorefrontSurface, { children: /* @__PURE__ */ jsxs("section", {
|
|
3120
|
-
"aria-labelledby": headingId,
|
|
3477
|
+
"aria-labelledby": title === null ? void 0 : headingId,
|
|
3121
3478
|
...partProps("related-products", className),
|
|
3122
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
3479
|
+
children: [title !== null && /* @__PURE__ */ jsx("h2", {
|
|
3123
3480
|
id: headingId,
|
|
3124
3481
|
className: "mb-6 text-xl font-semibold tracking-tight",
|
|
3125
|
-
children: title
|
|
3482
|
+
children: title === void 0 ? text("relatedTitle") : title
|
|
3126
3483
|
}), /* @__PURE__ */ jsx("div", {
|
|
3127
3484
|
className: catalogGridClass,
|
|
3128
3485
|
children: related.map((product) => /* @__PURE__ */ jsx(CatalogItemContext.Provider, {
|
|
@@ -3137,4 +3494,4 @@ function RelatedProducts({ productId: productIdProp, title = "You may also like"
|
|
|
3137
3494
|
}
|
|
3138
3495
|
|
|
3139
3496
|
//#endregion
|
|
3140
|
-
export { AddToCartButton, ApiError, CartButton, CartCheckout, CartCheckoutNote, CartClose, CartContinueShopping, CartDescription, CartDrawer, CartDrawerRoot, CartEmpty, CartItemImage, CartItemName, CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, CartItems, CartNotices, CartRoot, CartSubtotal, CartSummary, CartTitle, Catalog, CatalogEmpty, CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, CommerceProvider, CommerceStatus, CommerceStorefront, ProductCard, ProductCardAction, ProductCardBadge, ProductCardDescription, ProductCardImage, ProductCardPrice, ProductCardRoot, ProductCardSwatches, ProductCardTitle, ProductDetails, ProductDetailsAddToCart, ProductDetailsAvailability, ProductDetailsBackLink, ProductDetailsBadge, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsOption, ProductDetailsOptions, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, ProductDetailsTitle, ProductImage, RelatedProducts, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getSingleMatchedSku, money, useCart, useCartItem, useCommerce, useMoney, useProductCard, useProductDetails, useProductDetailsOption };
|
|
3497
|
+
export { AddToCartButton, ApiError, CartButton, CartCheckout, CartCheckoutNote, CartClose, CartContinueShopping, CartDescription, CartDrawer, CartDrawerRoot, CartEmpty, CartItemImage, CartItemName, CartItemOptions, CartItemPrice, CartItemQuantity, CartItemRemove, CartItemRoot, CartItems, CartNotices, CartRoot, CartSubtotal, CartSummary, CartTitle, Catalog, CatalogEmpty, CatalogHeader, CatalogPagination, CatalogProducts, CatalogRoot, CatalogSummary, CommerceProvider, CommerceStatus, CommerceStorefront, ProductCard, ProductCardAction, ProductCardBadge, ProductCardDescription, ProductCardImage, ProductCardPrice, ProductCardRoot, ProductCardSwatches, ProductCardTitle, ProductDetails, ProductDetailsAddToCart, ProductDetailsAvailability, ProductDetailsBackLink, ProductDetailsBadge, ProductDetailsDescription, ProductDetailsGallery, ProductDetailsOption, ProductDetailsOptions, ProductDetailsPrice, ProductDetailsPurchase, ProductDetailsPurchaseStatus, ProductDetailsQuantity, ProductDetailsRoot, ProductDetailsTitle, ProductImage, RelatedProducts, enUS, formatMessage, getAvailableInventoryQuantity, getCartSummaryTotals, getImageUrls, getLabeledSkuOptions, getPrimaryImageUrl, getProductAttributes, getSingleMatchedSku, money, moneyRange, useCart, useCartItem, useCommerce, useMessage, useMoney, useMoneyRange, useProductCard, useProductDetails, useProductDetailsOption };
|