@shoppexio/storefront-react 0.1.5 → 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 +35 -0
- package/README.md +7 -1
- package/dist/account.js +26 -67
- 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 +205 -10
- package/dist/index.js +157 -4
- package/dist/index.js.map +1 -1
- package/eject/account/favorites.tsx +3 -0
- 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);
|
|
@@ -2743,6 +2837,53 @@ async function loadShopPage(slug) {
|
|
|
2743
2837
|
}
|
|
2744
2838
|
}
|
|
2745
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
|
+
|
|
2746
2887
|
// src/storefront-context.tsx
|
|
2747
2888
|
import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useState as useState4 } from "react";
|
|
2748
2889
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -2789,10 +2930,13 @@ export {
|
|
|
2789
2930
|
DEFAULT_ACCOUNT_TAB,
|
|
2790
2931
|
DEFAULT_LOW_STOCK_THRESHOLD,
|
|
2791
2932
|
DELIVERY_TIME_UNITS,
|
|
2933
|
+
FAVORITES_CHANGED_EVENT,
|
|
2934
|
+
FavoritesProvider,
|
|
2792
2935
|
IMPLEMENTED_ACCOUNT_TABS,
|
|
2793
2936
|
MAX_PAY_WHAT_YOU_WANT_PRICE,
|
|
2794
2937
|
PRODUCT_BADGE_ICON_NAMES,
|
|
2795
2938
|
QUOTE_UNAVAILABLE_CHECKOUT_ERROR,
|
|
2939
|
+
SERVICE_STATES,
|
|
2796
2940
|
SERVING_CONTEXT_HINT,
|
|
2797
2941
|
SHOP_REVIEW_PAGE_SIZE,
|
|
2798
2942
|
STOREFRONT_SORT_KEYS,
|
|
@@ -2809,15 +2953,18 @@ export {
|
|
|
2809
2953
|
buildCustomFieldPayload,
|
|
2810
2954
|
buildInitialCheckoutFieldValues,
|
|
2811
2955
|
buildProductDetail,
|
|
2956
|
+
buildStatusPageModel,
|
|
2812
2957
|
buildStorefrontCatalog,
|
|
2813
2958
|
calculateDisplayLineTotal,
|
|
2814
2959
|
calculateLinePricing,
|
|
2960
|
+
canRequestStockAlert,
|
|
2815
2961
|
canonicalAccountPath,
|
|
2816
2962
|
checkoutFieldKey,
|
|
2817
2963
|
checkoutFieldWriteRepriced,
|
|
2818
2964
|
checkoutNeedsDetails,
|
|
2819
2965
|
clampQuantity,
|
|
2820
2966
|
clearRecoveredQuoteCheckoutError,
|
|
2967
|
+
createFavoritesStore,
|
|
2821
2968
|
filterCatalogCards,
|
|
2822
2969
|
findStorefrontProduct,
|
|
2823
2970
|
firstOptionalString,
|
|
@@ -2851,6 +2998,7 @@ export {
|
|
|
2851
2998
|
loadShopPage,
|
|
2852
2999
|
loadShopReviewsPage,
|
|
2853
3000
|
missingRequiredCheckoutValues,
|
|
3001
|
+
notifyFavoritesChanged,
|
|
2854
3002
|
optionalBoolean,
|
|
2855
3003
|
optionalString,
|
|
2856
3004
|
optionalStringWithDefault,
|
|
@@ -2860,9 +3008,11 @@ export {
|
|
|
2860
3008
|
parseEnum,
|
|
2861
3009
|
parseFiniteNumber,
|
|
2862
3010
|
parseProductBadges,
|
|
3011
|
+
parseServiceStatus,
|
|
2863
3012
|
parseStorefrontSortKey,
|
|
2864
3013
|
readCheckoutNeedsDetails,
|
|
2865
3014
|
readStorefrontData,
|
|
3015
|
+
requestStockAlert,
|
|
2866
3016
|
requireArray,
|
|
2867
3017
|
requireBoolean,
|
|
2868
3018
|
requireNumber,
|
|
@@ -2886,9 +3036,11 @@ export {
|
|
|
2886
3036
|
resolveSupportChannels,
|
|
2887
3037
|
resolveUnsellableLineIds,
|
|
2888
3038
|
resolveVariantStock,
|
|
3039
|
+
resolveYouReceive,
|
|
2889
3040
|
reviewsForProduct,
|
|
2890
3041
|
roundCurrency,
|
|
2891
3042
|
sanitizeMerchantHtml,
|
|
3043
|
+
sdkFavoritesTransport,
|
|
2892
3044
|
sortCatalogCards,
|
|
2893
3045
|
startCartMutation,
|
|
2894
3046
|
stripMerchantHtmlImages,
|
|
@@ -2896,6 +3048,7 @@ export {
|
|
|
2896
3048
|
toShopReviews,
|
|
2897
3049
|
toStarCount,
|
|
2898
3050
|
useCart,
|
|
3051
|
+
useFavorites,
|
|
2899
3052
|
useNavigationLinks,
|
|
2900
3053
|
useOnlineUserCount,
|
|
2901
3054
|
usePagedShopReviews,
|