@shoppexio/storefront-react 0.1.4 → 0.2.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/CHANGELOG.md +47 -0
- package/README.md +8 -2
- package/dist/account.js +119 -125
- package/dist/account.js.map +1 -1
- package/dist/{chunk-D6XKDVAV.js → chunk-ZEFZPUAW.js} +152 -2
- package/dist/chunk-ZEFZPUAW.js.map +1 -0
- package/dist/index.d.ts +298 -20
- package/dist/index.js +245 -22
- package/dist/index.js.map +1 -1
- package/eject/account/account-page.tsx +8 -2
- package/eject/account/account-warranties.ts +9 -0
- package/eject/account/favorites.tsx +3 -0
- package/eject/account/messages/ar.json +2 -1
- package/eject/account/messages/de.json +2 -1
- package/eject/account/messages/en.json +2 -1
- package/eject/account/messages/es.json +2 -1
- package/eject/account/messages/fi.json +2 -1
- package/eject/account/messages/fr.json +2 -1
- package/eject/account/messages/hi.json +2 -1
- package/eject/account/messages/it.json +2 -1
- package/eject/account/messages/lt.json +2 -1
- package/eject/account/messages/nl.json +2 -1
- package/eject/account/messages/pl.json +2 -1
- package/eject/account/messages/pt.json +2 -1
- package/eject/account/messages/ru.json +2 -1
- package/eject/account/messages/tr.json +2 -1
- package/eject/account/messages/uk.json +2 -1
- package/eject/account/messages/zh.json +2 -1
- package/eject/account/orders.tsx +6 -1
- package/package.json +1 -1
- package/dist/chunk-D6XKDVAV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,19 +5,25 @@ import {
|
|
|
5
5
|
ACCOUNT_STOREFRONT_ROUTES,
|
|
6
6
|
ACCOUNT_TABS,
|
|
7
7
|
DEFAULT_ACCOUNT_TAB,
|
|
8
|
+
FAVORITES_CHANGED_EVENT,
|
|
9
|
+
FavoritesProvider,
|
|
8
10
|
IMPLEMENTED_ACCOUNT_TABS,
|
|
9
11
|
SERVING_CONTEXT_HINT,
|
|
10
12
|
accountOrderHref,
|
|
11
13
|
accountTabHref,
|
|
12
14
|
accountTabLabelKey,
|
|
13
15
|
canonicalAccountPath,
|
|
16
|
+
createFavoritesStore,
|
|
14
17
|
getBuyerCurrency,
|
|
15
18
|
getShoppex,
|
|
16
19
|
isAccountTab,
|
|
17
20
|
isImplementedAccountTab,
|
|
21
|
+
notifyFavoritesChanged,
|
|
18
22
|
parseAccountPath,
|
|
19
|
-
resolveAccountView
|
|
20
|
-
|
|
23
|
+
resolveAccountView,
|
|
24
|
+
sdkFavoritesTransport,
|
|
25
|
+
useFavorites
|
|
26
|
+
} from "./chunk-ZEFZPUAW.js";
|
|
21
27
|
|
|
22
28
|
// src/account-route-messages.ts
|
|
23
29
|
var ACCOUNT_ROUTE_MESSAGE_KEYS = [
|
|
@@ -283,6 +289,43 @@ function badgesForProductPage(badges) {
|
|
|
283
289
|
return badges.filter((badge) => badge.showOnProductPage);
|
|
284
290
|
}
|
|
285
291
|
|
|
292
|
+
// src/service-status.ts
|
|
293
|
+
var SERVICE_STATES = ["operational", "updating", "detected", "offline"];
|
|
294
|
+
var STATE_ORDER = { detected: 0, offline: 1, updating: 2, operational: 3 };
|
|
295
|
+
function parseServiceStatus(value, context) {
|
|
296
|
+
if (value === null || value === void 0) return null;
|
|
297
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
298
|
+
throw new Error(`Invalid Shoppex ${context}.service_status: expected an object or null.`);
|
|
299
|
+
}
|
|
300
|
+
const record = value;
|
|
301
|
+
const state = record.state;
|
|
302
|
+
if (typeof state !== "string" || !SERVICE_STATES.includes(state)) {
|
|
303
|
+
throw new Error(`Invalid Shoppex ${context}.service_status.state: expected one of ${SERVICE_STATES.join(", ")}.`);
|
|
304
|
+
}
|
|
305
|
+
const updatedAt = record.updated_at;
|
|
306
|
+
if (typeof updatedAt !== "string" || Number.isNaN(Date.parse(updatedAt))) {
|
|
307
|
+
throw new Error(`Invalid Shoppex ${context}.service_status.updated_at: expected an ISO timestamp.`);
|
|
308
|
+
}
|
|
309
|
+
const note = typeof record.note === "string" && record.note.trim() ? record.note.trim() : null;
|
|
310
|
+
return { state, note, updatedAt };
|
|
311
|
+
}
|
|
312
|
+
function buildStatusPageModel(products) {
|
|
313
|
+
const entries = [];
|
|
314
|
+
const seen = /* @__PURE__ */ new Set();
|
|
315
|
+
for (const product of products) {
|
|
316
|
+
if (product.serviceStatus === null || seen.has(product.id)) continue;
|
|
317
|
+
seen.add(product.id);
|
|
318
|
+
entries.push({ product, status: product.serviceStatus });
|
|
319
|
+
}
|
|
320
|
+
if (entries.length === 0) return null;
|
|
321
|
+
entries.sort((a, b) => STATE_ORDER[a.status.state] - STATE_ORDER[b.status.state]);
|
|
322
|
+
const counts = { operational: 0, updating: 0, detected: 0, offline: 0 };
|
|
323
|
+
for (const entry of entries) counts[entry.status.state] += 1;
|
|
324
|
+
const presentStates = [...SERVICE_STATES].sort((a, b) => STATE_ORDER[a] - STATE_ORDER[b]).filter((state) => counts[state] > 0);
|
|
325
|
+
const lastUpdatedAt = entries.map((entry) => entry.status.updatedAt).reduce((latest, value) => Date.parse(value) > Date.parse(latest) ? value : latest);
|
|
326
|
+
return { entries, counts, presentStates, lastUpdatedAt };
|
|
327
|
+
}
|
|
328
|
+
|
|
286
329
|
// src/video-embed.ts
|
|
287
330
|
var ALLOWED_VIDEO_EMBED_HOSTS = /* @__PURE__ */ new Set([
|
|
288
331
|
"www.youtube-nocookie.com",
|
|
@@ -372,11 +415,30 @@ function resolveProductVideoEmbedSrc(value) {
|
|
|
372
415
|
var PRODUCT_TYPES = ["PRODUCT", "SUBSCRIPTION", "INFO_CARD", "SERIALS", "PHYSICAL"];
|
|
373
416
|
function resolveDeliveryPromise(product) {
|
|
374
417
|
if (product.productType === "PHYSICAL") return { kind: "shipped" };
|
|
418
|
+
if (!product.deliveryTimeStated) return { kind: "unspecified" };
|
|
375
419
|
if (product.deliveryTimeCount !== null) {
|
|
376
420
|
return { kind: "timed", count: product.deliveryTimeCount, unit: product.deliveryTimeUnit };
|
|
377
421
|
}
|
|
378
422
|
return { kind: "instant" };
|
|
379
423
|
}
|
|
424
|
+
var DELIVERABLES_BY_TYPE = {
|
|
425
|
+
SERIALS: "serials",
|
|
426
|
+
FILE: "file",
|
|
427
|
+
SERVICE: "service",
|
|
428
|
+
DYNAMIC: "dynamic",
|
|
429
|
+
INFO_CARD: "info_card",
|
|
430
|
+
SUBSCRIPTION: "subscription",
|
|
431
|
+
PHYSICAL: "physical"
|
|
432
|
+
};
|
|
433
|
+
function parseDeliverable(value) {
|
|
434
|
+
const normalized = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
435
|
+
return DELIVERABLES_BY_TYPE[normalized] ?? null;
|
|
436
|
+
}
|
|
437
|
+
function resolveYouReceive(product) {
|
|
438
|
+
if (product.deliverable === "serials") return { kind: "serials", unitLabel: product.unitLabelSingular };
|
|
439
|
+
if (product.deliverable === "file") return { kind: "download" };
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
380
442
|
var SUBSCRIPTION_INTERVAL_UNITS = ["DAY", "WEEK", "MONTH", "YEAR"];
|
|
381
443
|
var SUBSCRIPTION_INTERVAL_NOUNS = {
|
|
382
444
|
DAY: "day",
|
|
@@ -405,6 +467,25 @@ function parseSubscription(product, productType) {
|
|
|
405
467
|
trialDays: normalizePositiveDays(product.trial_period) ?? 0
|
|
406
468
|
};
|
|
407
469
|
}
|
|
470
|
+
var PAYMENT_METHOD_KINDS = ["card", "wallet", "crypto"];
|
|
471
|
+
function parsePaymentMethods(value) {
|
|
472
|
+
if (!Array.isArray(value)) {
|
|
473
|
+
throw new Error("Invalid Shoppex store: payment_methods must be an array.");
|
|
474
|
+
}
|
|
475
|
+
return value.map((entry, index) => {
|
|
476
|
+
const context = `store.payment_methods[${index}]`;
|
|
477
|
+
const record = requireUnknownRecord(entry, context);
|
|
478
|
+
const kind = record.kind;
|
|
479
|
+
if (typeof kind !== "string" || !PAYMENT_METHOD_KINDS.includes(kind)) {
|
|
480
|
+
throw new Error(`Invalid Shoppex ${context}: kind must be card, wallet or crypto.`);
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
id: requireString(record, "id", context),
|
|
484
|
+
label: requireString(record, "label", context),
|
|
485
|
+
kind
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
}
|
|
408
489
|
function hasInjectedStorefrontData() {
|
|
409
490
|
return window.__SHOPPEX_INITIAL__ !== void 0;
|
|
410
491
|
}
|
|
@@ -583,7 +664,7 @@ function getStorefrontSettings() {
|
|
|
583
664
|
]),
|
|
584
665
|
defaultCurrency,
|
|
585
666
|
activeCurrency,
|
|
586
|
-
|
|
667
|
+
paymentMethods: parsePaymentMethods(store.payment_methods),
|
|
587
668
|
cartEnabled: optionalBoolean(store.cart_enabled, true, "store.cart_enabled"),
|
|
588
669
|
hideOutOfStock: optionalBoolean(store.hide_out_of_stock, false, "store.hide_out_of_stock"),
|
|
589
670
|
hideStockCounter: optionalBoolean(store.hide_stock_counter, false, "store.hide_stock_counter"),
|
|
@@ -608,6 +689,7 @@ function getStorefrontSettings() {
|
|
|
608
689
|
// visitor only found out from the backend's refusal. A payload without them
|
|
609
690
|
// is broken, so it fails here instead.
|
|
610
691
|
contactFormEnabled: requireBoolean(store.contact_form_enabled, "store.contact_form_enabled"),
|
|
692
|
+
stockAlertsEnabled: requireBoolean(store.stock_alerts_enabled, "store.stock_alerts_enabled"),
|
|
611
693
|
requireInvoiceForContact: requireBoolean(
|
|
612
694
|
store.require_invoice_for_contact,
|
|
613
695
|
"store.require_invoice_for_contact"
|
|
@@ -724,13 +806,19 @@ function parseProduct(value, index) {
|
|
|
724
806
|
rating: rating?.average ?? null,
|
|
725
807
|
ratingCount: rating?.count ?? 0,
|
|
726
808
|
ratingTally: rating?.tally ?? null,
|
|
809
|
+
shortDescription: optionalString(product.short_description),
|
|
810
|
+
deliverable: parseDeliverable(product.type),
|
|
811
|
+
unitLabelSingular: optionalString(product.label_singular),
|
|
812
|
+
unitLabelPlural: optionalString(product.label_plural),
|
|
813
|
+
serviceStatus: parseServiceStatus(product.service_status, context),
|
|
727
814
|
highlights: parseHighlights(product.product_highlights),
|
|
728
815
|
metadata: parseMetadata(product.metadata),
|
|
729
816
|
descriptionTabs: parseContentTabs(product.description_tabs),
|
|
730
817
|
faqs: parseFaqs(product.faqs),
|
|
731
818
|
license: parseProductLicense(product),
|
|
732
819
|
licensingEnabled: product.licensing_enabled === true || product.licensing_enabled === 1,
|
|
733
|
-
|
|
820
|
+
deliveryTimeStated: Object.prototype.hasOwnProperty.call(product, "delivery_time") && product.delivery_time !== void 0,
|
|
821
|
+
deliveryTimeCount: normalizeDeliveryCount(product.delivery_time),
|
|
734
822
|
deliveryTimeUnit: normalizeDeliveryTimeUnit(product.delivery_time_unit),
|
|
735
823
|
subscription: parseSubscription(product, productType)
|
|
736
824
|
};
|
|
@@ -796,6 +884,7 @@ function parsePriceVariants(value, productContext) {
|
|
|
796
884
|
return {
|
|
797
885
|
id: requireString(variant, "id", context),
|
|
798
886
|
label: requireStringFromKeys(variant, ["label", "title"], context),
|
|
887
|
+
description: optionalString(variant.description),
|
|
799
888
|
license: parseVariantLicense(variant),
|
|
800
889
|
price: unitPrice,
|
|
801
890
|
baseUnitPrice: unitPrice,
|
|
@@ -1105,6 +1194,11 @@ function normalizeDiscountPercent(value) {
|
|
|
1105
1194
|
if (!Number.isFinite(percent) || percent <= 0) return 0;
|
|
1106
1195
|
return Math.min(percent, 100);
|
|
1107
1196
|
}
|
|
1197
|
+
function normalizeDeliveryCount(value) {
|
|
1198
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
1199
|
+
const parsed = Number(value);
|
|
1200
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.ceil(parsed) : null;
|
|
1201
|
+
}
|
|
1108
1202
|
function normalizePositiveDays(value) {
|
|
1109
1203
|
if (value === null || value === void 0 || value === "") return null;
|
|
1110
1204
|
const parsed = Number(value);
|
|
@@ -2542,7 +2636,7 @@ function useNavigationLinks(slot, themeLinks, authored) {
|
|
|
2542
2636
|
}
|
|
2543
2637
|
|
|
2544
2638
|
// src/reviews.ts
|
|
2545
|
-
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
2639
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
2546
2640
|
var SHOP_REVIEW_PAGE_SIZE = 12;
|
|
2547
2641
|
function toTimestamp(value) {
|
|
2548
2642
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
@@ -2582,7 +2676,8 @@ function toShopReviews(rows, labels) {
|
|
|
2582
2676
|
dateLabel: formatDateLabel(timestamp),
|
|
2583
2677
|
timestamp,
|
|
2584
2678
|
productTitle: row.product_title?.trim() || null,
|
|
2585
|
-
verified: row.verified === true
|
|
2679
|
+
verified: row.verified === true,
|
|
2680
|
+
automated: row.is_automated === true
|
|
2586
2681
|
};
|
|
2587
2682
|
}).sort((a, b) => b.timestamp - a.timestamp);
|
|
2588
2683
|
}
|
|
@@ -2591,20 +2686,83 @@ function reviewsForProduct(reviews, productTitle) {
|
|
|
2591
2686
|
if (!title) return [];
|
|
2592
2687
|
return reviews.filter((review) => review.productTitle === title);
|
|
2593
2688
|
}
|
|
2594
|
-
async function
|
|
2689
|
+
async function loadShopReviewsPage(options) {
|
|
2690
|
+
const product = options.product?.trim() || null;
|
|
2691
|
+
const result = await getShoppex().getShopReviewsPage(options.cursor ?? null, product ? { product } : void 0);
|
|
2692
|
+
if (!result.success || !result.data) {
|
|
2693
|
+
throw new Error(result.message?.trim() || "Reviews could not be loaded.");
|
|
2694
|
+
}
|
|
2695
|
+
if (product !== null && result.data.product !== product) {
|
|
2696
|
+
throw new Error("This store does not filter reviews by product yet.");
|
|
2697
|
+
}
|
|
2698
|
+
const { pagination } = result.data;
|
|
2699
|
+
return {
|
|
2700
|
+
reviews: toShopReviews(result.data.feedback, options.labels),
|
|
2701
|
+
nextCursor: pagination.has_more ? pagination.next_cursor : null,
|
|
2702
|
+
summary: result.data.summary ?? null
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
async function loadLatestWrittenReviews(limit, labels, options = {}) {
|
|
2595
2706
|
const written = [];
|
|
2596
2707
|
const seen = /* @__PURE__ */ new Set();
|
|
2597
2708
|
let cursor = null;
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2709
|
+
try {
|
|
2710
|
+
while (written.length < limit) {
|
|
2711
|
+
const page = await loadShopReviewsPage({ cursor, product: options.product, labels: { anonymousAuthor: labels.anonymousAuthor, emptyComment: "" } });
|
|
2712
|
+
written.push(...page.reviews.filter((review) => !review.automated && review.comment !== ""));
|
|
2713
|
+
if (!page.nextCursor || seen.has(page.nextCursor)) break;
|
|
2714
|
+
seen.add(page.nextCursor);
|
|
2715
|
+
cursor = page.nextCursor;
|
|
2716
|
+
}
|
|
2717
|
+
} catch {
|
|
2718
|
+
return null;
|
|
2606
2719
|
}
|
|
2607
|
-
return
|
|
2720
|
+
return written.slice(0, limit).map((review) => ({ ...review, comment: review.comment || labels.emptyComment }));
|
|
2721
|
+
}
|
|
2722
|
+
function usePagedShopReviews(options) {
|
|
2723
|
+
const product = options.product?.trim() || null;
|
|
2724
|
+
const { anonymousAuthor, emptyComment } = options.labels;
|
|
2725
|
+
const [state, setState] = useState3({ status: "loading" });
|
|
2726
|
+
const [cursor, setCursor] = useState3(null);
|
|
2727
|
+
const generation = useRef2(0);
|
|
2728
|
+
useEffect3(() => {
|
|
2729
|
+
const current = generation.current += 1;
|
|
2730
|
+
setState({ status: "loading" });
|
|
2731
|
+
setCursor(null);
|
|
2732
|
+
loadShopReviewsPage({ product, labels: { anonymousAuthor, emptyComment } }).then((page) => {
|
|
2733
|
+
if (generation.current !== current) return;
|
|
2734
|
+
setCursor(page.nextCursor);
|
|
2735
|
+
setState({
|
|
2736
|
+
status: "ready",
|
|
2737
|
+
reviews: page.reviews,
|
|
2738
|
+
summary: page.summary,
|
|
2739
|
+
hasMore: page.nextCursor !== null,
|
|
2740
|
+
loadingMore: false,
|
|
2741
|
+
moreFailed: false
|
|
2742
|
+
});
|
|
2743
|
+
}).catch(() => {
|
|
2744
|
+
if (generation.current === current) setState({ status: "error" });
|
|
2745
|
+
});
|
|
2746
|
+
}, [product, anonymousAuthor, emptyComment]);
|
|
2747
|
+
const loadMore = useCallback2(() => {
|
|
2748
|
+
if (state.status !== "ready" || state.loadingMore || cursor === null) return;
|
|
2749
|
+
const current = generation.current;
|
|
2750
|
+
setState({ ...state, loadingMore: true, moreFailed: false });
|
|
2751
|
+
loadShopReviewsPage({ cursor, product, labels: { anonymousAuthor, emptyComment } }).then((page) => {
|
|
2752
|
+
if (generation.current !== current) return;
|
|
2753
|
+
setCursor(page.nextCursor);
|
|
2754
|
+
setState((previous) => previous.status !== "ready" ? previous : {
|
|
2755
|
+
...previous,
|
|
2756
|
+
reviews: [...previous.reviews, ...page.reviews],
|
|
2757
|
+
hasMore: page.nextCursor !== null,
|
|
2758
|
+
loadingMore: false
|
|
2759
|
+
});
|
|
2760
|
+
}).catch(() => {
|
|
2761
|
+
if (generation.current !== current) return;
|
|
2762
|
+
setState((previous) => previous.status !== "ready" ? previous : { ...previous, loadingMore: false, moreFailed: true });
|
|
2763
|
+
});
|
|
2764
|
+
}, [state, cursor, product, anonymousAuthor, emptyComment]);
|
|
2765
|
+
return { ...state, loadMore };
|
|
2608
2766
|
}
|
|
2609
2767
|
function averageRating(reviews) {
|
|
2610
2768
|
if (reviews.length === 0) return null;
|
|
@@ -2663,18 +2821,69 @@ async function buildShopPageModel(page) {
|
|
|
2663
2821
|
};
|
|
2664
2822
|
}
|
|
2665
2823
|
async function loadShopPage(slug) {
|
|
2824
|
+
let result;
|
|
2825
|
+
try {
|
|
2826
|
+
result = await getShoppex().getPage(slug);
|
|
2827
|
+
} catch {
|
|
2828
|
+
return { status: "error" };
|
|
2829
|
+
}
|
|
2830
|
+
if (!result.success) return result.status === 404 ? { status: "missing" } : { status: "error" };
|
|
2831
|
+
const page = result.data;
|
|
2832
|
+
if (!page || typeof page.name !== "string" || !page.name.trim()) return { status: "missing" };
|
|
2666
2833
|
try {
|
|
2667
|
-
const result = await getShoppex().getPage(slug);
|
|
2668
|
-
const page = result.success ? result.data : void 0;
|
|
2669
|
-
if (!page || typeof page.name !== "string" || !page.name.trim()) {
|
|
2670
|
-
return { status: "missing" };
|
|
2671
|
-
}
|
|
2672
2834
|
return { status: "ready", page: await buildShopPageModel(page) };
|
|
2673
2835
|
} catch {
|
|
2674
|
-
return { status: "
|
|
2836
|
+
return { status: "error" };
|
|
2675
2837
|
}
|
|
2676
2838
|
}
|
|
2677
2839
|
|
|
2840
|
+
// src/stock-alerts.ts
|
|
2841
|
+
function canRequestStockAlert(settings, product, soldOut) {
|
|
2842
|
+
return settings.stockAlertsEnabled && soldOut && !product.onHold && product.productType !== "INFO_CARD";
|
|
2843
|
+
}
|
|
2844
|
+
function bootstrapGlobal(name) {
|
|
2845
|
+
const value = window[name];
|
|
2846
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2847
|
+
throw new Error(
|
|
2848
|
+
`Shoppex bootstrap global "${name}" is missing. This storefront must be served by the Shoppex storefront (or preview) worker.`
|
|
2849
|
+
);
|
|
2850
|
+
}
|
|
2851
|
+
return value.trim().replace(/\/+$/, "");
|
|
2852
|
+
}
|
|
2853
|
+
function pageLocale() {
|
|
2854
|
+
const lang = typeof document === "undefined" ? "" : document.documentElement.lang.trim();
|
|
2855
|
+
return lang || null;
|
|
2856
|
+
}
|
|
2857
|
+
var GENERIC_FAILURE = "We could not save your request. Please try again.";
|
|
2858
|
+
async function requestStockAlert(request) {
|
|
2859
|
+
const email = request.email.trim();
|
|
2860
|
+
if (!email) return { ok: false, status: null, message: "Enter your email address." };
|
|
2861
|
+
let response;
|
|
2862
|
+
try {
|
|
2863
|
+
const shopSlug = bootstrapGlobal("shopSlug");
|
|
2864
|
+
const apiBaseUrl = bootstrapGlobal("apiBaseUrl");
|
|
2865
|
+
response = await fetch(
|
|
2866
|
+
`${apiBaseUrl}/v1/storefront/shops/name/${encodeURIComponent(shopSlug)}/products/${encodeURIComponent(request.productId)}/stock-alerts`,
|
|
2867
|
+
{
|
|
2868
|
+
method: "POST",
|
|
2869
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2870
|
+
body: JSON.stringify({
|
|
2871
|
+
email,
|
|
2872
|
+
variant_id: request.variantId,
|
|
2873
|
+
locale: pageLocale(),
|
|
2874
|
+
...request.honeypot ? { website: request.honeypot } : {}
|
|
2875
|
+
})
|
|
2876
|
+
}
|
|
2877
|
+
);
|
|
2878
|
+
} catch {
|
|
2879
|
+
return { ok: false, status: null, message: GENERIC_FAILURE };
|
|
2880
|
+
}
|
|
2881
|
+
if (response.status === 202) return { ok: true };
|
|
2882
|
+
const payload = await response.json().catch(() => null);
|
|
2883
|
+
const serverMessage = typeof payload?.error === "string" && payload.error.trim() ? payload.error.trim() : typeof payload?.message === "string" && payload.message.trim() ? payload.message.trim() : null;
|
|
2884
|
+
return { ok: false, status: response.status, message: serverMessage ?? GENERIC_FAILURE };
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2678
2887
|
// src/storefront-context.tsx
|
|
2679
2888
|
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useState as useState4 } from "react";
|
|
2680
2889
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -2721,10 +2930,13 @@ export {
|
|
|
2721
2930
|
DEFAULT_ACCOUNT_TAB,
|
|
2722
2931
|
DEFAULT_LOW_STOCK_THRESHOLD,
|
|
2723
2932
|
DELIVERY_TIME_UNITS,
|
|
2933
|
+
FAVORITES_CHANGED_EVENT,
|
|
2934
|
+
FavoritesProvider,
|
|
2724
2935
|
IMPLEMENTED_ACCOUNT_TABS,
|
|
2725
2936
|
MAX_PAY_WHAT_YOU_WANT_PRICE,
|
|
2726
2937
|
PRODUCT_BADGE_ICON_NAMES,
|
|
2727
2938
|
QUOTE_UNAVAILABLE_CHECKOUT_ERROR,
|
|
2939
|
+
SERVICE_STATES,
|
|
2728
2940
|
SERVING_CONTEXT_HINT,
|
|
2729
2941
|
SHOP_REVIEW_PAGE_SIZE,
|
|
2730
2942
|
STOREFRONT_SORT_KEYS,
|
|
@@ -2741,15 +2953,18 @@ export {
|
|
|
2741
2953
|
buildCustomFieldPayload,
|
|
2742
2954
|
buildInitialCheckoutFieldValues,
|
|
2743
2955
|
buildProductDetail,
|
|
2956
|
+
buildStatusPageModel,
|
|
2744
2957
|
buildStorefrontCatalog,
|
|
2745
2958
|
calculateDisplayLineTotal,
|
|
2746
2959
|
calculateLinePricing,
|
|
2960
|
+
canRequestStockAlert,
|
|
2747
2961
|
canonicalAccountPath,
|
|
2748
2962
|
checkoutFieldKey,
|
|
2749
2963
|
checkoutFieldWriteRepriced,
|
|
2750
2964
|
checkoutNeedsDetails,
|
|
2751
2965
|
clampQuantity,
|
|
2752
2966
|
clearRecoveredQuoteCheckoutError,
|
|
2967
|
+
createFavoritesStore,
|
|
2753
2968
|
filterCatalogCards,
|
|
2754
2969
|
findStorefrontProduct,
|
|
2755
2970
|
firstOptionalString,
|
|
@@ -2781,7 +2996,9 @@ export {
|
|
|
2781
2996
|
isSelectionSoldOut,
|
|
2782
2997
|
loadLatestWrittenReviews,
|
|
2783
2998
|
loadShopPage,
|
|
2999
|
+
loadShopReviewsPage,
|
|
2784
3000
|
missingRequiredCheckoutValues,
|
|
3001
|
+
notifyFavoritesChanged,
|
|
2785
3002
|
optionalBoolean,
|
|
2786
3003
|
optionalString,
|
|
2787
3004
|
optionalStringWithDefault,
|
|
@@ -2791,9 +3008,11 @@ export {
|
|
|
2791
3008
|
parseEnum,
|
|
2792
3009
|
parseFiniteNumber,
|
|
2793
3010
|
parseProductBadges,
|
|
3011
|
+
parseServiceStatus,
|
|
2794
3012
|
parseStorefrontSortKey,
|
|
2795
3013
|
readCheckoutNeedsDetails,
|
|
2796
3014
|
readStorefrontData,
|
|
3015
|
+
requestStockAlert,
|
|
2797
3016
|
requireArray,
|
|
2798
3017
|
requireBoolean,
|
|
2799
3018
|
requireNumber,
|
|
@@ -2817,9 +3036,11 @@ export {
|
|
|
2817
3036
|
resolveSupportChannels,
|
|
2818
3037
|
resolveUnsellableLineIds,
|
|
2819
3038
|
resolveVariantStock,
|
|
3039
|
+
resolveYouReceive,
|
|
2820
3040
|
reviewsForProduct,
|
|
2821
3041
|
roundCurrency,
|
|
2822
3042
|
sanitizeMerchantHtml,
|
|
3043
|
+
sdkFavoritesTransport,
|
|
2823
3044
|
sortCatalogCards,
|
|
2824
3045
|
startCartMutation,
|
|
2825
3046
|
stripMerchantHtmlImages,
|
|
@@ -2827,8 +3048,10 @@ export {
|
|
|
2827
3048
|
toShopReviews,
|
|
2828
3049
|
toStarCount,
|
|
2829
3050
|
useCart,
|
|
3051
|
+
useFavorites,
|
|
2830
3052
|
useNavigationLinks,
|
|
2831
3053
|
useOnlineUserCount,
|
|
3054
|
+
usePagedShopReviews,
|
|
2832
3055
|
useShopReviews,
|
|
2833
3056
|
useStorefront,
|
|
2834
3057
|
validateCheckoutFieldValue,
|