@numueg/theme-sdk 0.12.0 → 0.13.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 +53 -0
- package/dist/{chunk-7RZISLP2.cjs → chunk-H7RGDWSU.cjs} +3 -3
- package/dist/{chunk-7RZISLP2.cjs.map → chunk-H7RGDWSU.cjs.map} +1 -1
- package/dist/{chunk-ZECKSIDL.mjs → chunk-QPXMBK5U.mjs} +3 -3
- package/dist/{chunk-ZECKSIDL.mjs.map → chunk-QPXMBK5U.mjs.map} +1 -1
- package/dist/{entities-1XRRUTlO.d.mts → entities-DRKn04q0.d.mts} +32 -0
- package/dist/{entities-1XRRUTlO.d.ts → entities-DRKn04q0.d.ts} +32 -0
- package/dist/index.cjs +161 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +215 -5
- package/dist/index.d.ts +215 -5
- package/dist/index.mjs +146 -29
- package/dist/index.mjs.map +1 -1
- package/dist/{mount-DDFMacmj.d.mts → mount-BTaDtz8k.d.mts} +1 -1
- package/dist/{mount-qofjiBxw.d.ts → mount-CLQniVfc.d.ts} +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/v2-compat.d.mts +1 -1
- package/dist/v2-compat.d.ts +1 -1
- package/dist/validation.cjs +11 -11
- package/dist/validation.mjs +1 -1
- package/dist/verify.cjs +3 -3
- package/dist/verify.d.mts +2 -2
- package/dist/verify.d.ts +2 -2
- package/dist/verify.mjs +2 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { resolveThemeSettings } from './chunk-XF2FGIVS.mjs';
|
|
2
|
-
export { KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, REQUIRED_TEMPLATES, SDK_VERSION, THEME_CONTRACT_VERSION, mergeResults, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema } from './chunk-
|
|
2
|
+
export { KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, REQUIRED_TEMPLATES, SDK_VERSION, THEME_CONTRACT_VERSION, mergeResults, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema } from './chunk-QPXMBK5U.mjs';
|
|
3
3
|
import { ShopContext, ThemeSettingsContext, CurrentTemplateContext, PageContext, LocalizationContext, CurrencyContext, CartContext, CustomerContext, NavigationContext, CollectionContext, ProductContext, useShop, useLocalization, useCustomer, useThemeSettings } from './chunk-ZYZZG4JR.mjs';
|
|
4
4
|
export { CartContext, CollectionContext, CurrencyContext, CustomerContext, LocalizationContext, NavigationContext, PageContext, ProductContext, ShopContext, ThemeSettingsContext, useCollections, useCustomer, useDirection, useFieldTranslation, useLocale, useLocalization, useNumberFormat, usePage, useProducts, useShop, useThemeSettings, useTranslation } from './chunk-ZYZZG4JR.mjs';
|
|
5
5
|
import { createContext, forwardRef, useState, useImperativeHandle, useEffect, useCallback, useMemo, useRef, useContext, useSyncExternalStore, StrictMode, createElement, Component } from 'react';
|
|
@@ -1022,6 +1022,133 @@ function useRelatedProducts(productId, options = {}) {
|
|
|
1022
1022
|
});
|
|
1023
1023
|
return { items: data ?? EMPTY4, loading: isLoading, error: error ?? null };
|
|
1024
1024
|
}
|
|
1025
|
+
function useActivePromotions(page = "/", locale = "ar") {
|
|
1026
|
+
const key = `numu:promotions:${page}:${locale}`;
|
|
1027
|
+
const fetcher = useCallback(
|
|
1028
|
+
async (signal) => {
|
|
1029
|
+
const qs = new URLSearchParams({ page, locale });
|
|
1030
|
+
const res = await fetch(`/api/storefront/promotions?${qs.toString()}`, {
|
|
1031
|
+
credentials: "include",
|
|
1032
|
+
cache: "no-store",
|
|
1033
|
+
signal
|
|
1034
|
+
});
|
|
1035
|
+
if (!res.ok) return null;
|
|
1036
|
+
const json = await res.json();
|
|
1037
|
+
if (!json) return null;
|
|
1038
|
+
return (json.data ?? json) || null;
|
|
1039
|
+
},
|
|
1040
|
+
[page, locale]
|
|
1041
|
+
);
|
|
1042
|
+
const { data } = useCachedResource(
|
|
1043
|
+
key,
|
|
1044
|
+
fetcher,
|
|
1045
|
+
{ initialData: null }
|
|
1046
|
+
);
|
|
1047
|
+
return data ?? null;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/utils/money.ts
|
|
1051
|
+
function resolveLocale(locale) {
|
|
1052
|
+
return locale === "ar" ? "ar-EG" : "en-EG";
|
|
1053
|
+
}
|
|
1054
|
+
function formatMoney(cents, options = {}) {
|
|
1055
|
+
const { currency, locale, fractionDigits = 0 } = options;
|
|
1056
|
+
const safe = Number.isFinite(cents) ? cents : 0;
|
|
1057
|
+
return new Intl.NumberFormat(resolveLocale(locale), {
|
|
1058
|
+
style: "currency",
|
|
1059
|
+
currency: currency || "EGP",
|
|
1060
|
+
maximumFractionDigits: fractionDigits
|
|
1061
|
+
}).format(safe / 100);
|
|
1062
|
+
}
|
|
1063
|
+
function formatMoneyMajor(amount, options = {}) {
|
|
1064
|
+
return formatMoney(
|
|
1065
|
+
(Number.isFinite(amount) ? amount : 0) * 100,
|
|
1066
|
+
options
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
function centsToMajor(cents) {
|
|
1070
|
+
return (Number.isFinite(cents) ? cents : 0) / 100;
|
|
1071
|
+
}
|
|
1072
|
+
function majorToCents(amount) {
|
|
1073
|
+
return Math.round((Number.isFinite(amount) ? amount : 0) * 100);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// src/lib/promotions.ts
|
|
1077
|
+
function multibuyOffers(promotions) {
|
|
1078
|
+
if (!promotions) return [];
|
|
1079
|
+
const list = Array.isArray(promotions) ? promotions : promotions.auto_discounts ?? [];
|
|
1080
|
+
const offers = [];
|
|
1081
|
+
for (const promo of list) {
|
|
1082
|
+
const rule = promo?.discount_rule;
|
|
1083
|
+
if (!rule || rule.kind !== "multibuy") continue;
|
|
1084
|
+
const quantity = rule.multibuy_quantity;
|
|
1085
|
+
const groupPriceCents = rule.multibuy_price_cents;
|
|
1086
|
+
if (typeof quantity !== "number" || quantity < 2) continue;
|
|
1087
|
+
if (typeof groupPriceCents !== "number" || groupPriceCents <= 0) continue;
|
|
1088
|
+
const eligibleProductIds = promo.eligible_product_ids ?? [];
|
|
1089
|
+
const eligibleCategoryIds = promo.eligible_category_ids ?? [];
|
|
1090
|
+
offers.push({
|
|
1091
|
+
promotionId: promo.promotion_id,
|
|
1092
|
+
quantity,
|
|
1093
|
+
groupPriceCents,
|
|
1094
|
+
groupPriceMajor: centsToMajor(groupPriceCents),
|
|
1095
|
+
headline: promo.translated_content?.headline,
|
|
1096
|
+
eligibleProductIds,
|
|
1097
|
+
eligibleCategoryIds,
|
|
1098
|
+
// No scoping rows at all ⇒ every product qualifies. Note an older
|
|
1099
|
+
// backend that doesn't send these fields also lands here, which is the
|
|
1100
|
+
// right default: counting everything is what themes did before.
|
|
1101
|
+
isStoreWide: eligibleProductIds.length === 0 && eligibleCategoryIds.length === 0,
|
|
1102
|
+
raw: promo
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
return offers;
|
|
1106
|
+
}
|
|
1107
|
+
function offerIncludesProduct(offer, product) {
|
|
1108
|
+
if (!offer) return false;
|
|
1109
|
+
if (offer.isStoreWide) return true;
|
|
1110
|
+
const id = product?.product_id ?? product?.id;
|
|
1111
|
+
if (id && offer.eligibleProductIds.includes(id)) return true;
|
|
1112
|
+
const category = product?.category_id;
|
|
1113
|
+
return Boolean(category && offer.eligibleCategoryIds.includes(category));
|
|
1114
|
+
}
|
|
1115
|
+
function eligibleUnitsInCart(offer, cart) {
|
|
1116
|
+
if (!offer) return 0;
|
|
1117
|
+
return (cart?.items ?? []).reduce((sum, item) => {
|
|
1118
|
+
if (!item) return sum;
|
|
1119
|
+
if (!offerIncludesProduct(offer, item)) return sum;
|
|
1120
|
+
return sum + (item.quantity || 0);
|
|
1121
|
+
}, 0);
|
|
1122
|
+
}
|
|
1123
|
+
function offerProgress(offer, cart, eligibleUnits) {
|
|
1124
|
+
const empty = {
|
|
1125
|
+
unitsInCart: 0,
|
|
1126
|
+
unitsNeeded: 0,
|
|
1127
|
+
groupsUnlocked: 0,
|
|
1128
|
+
savingMajor: 0
|
|
1129
|
+
};
|
|
1130
|
+
if (!offer) return empty;
|
|
1131
|
+
const unitsInCart = typeof eligibleUnits === "number" ? Math.max(0, eligibleUnits) : eligibleUnitsInCart(offer, cart);
|
|
1132
|
+
const groupsUnlocked = Math.floor(unitsInCart / offer.quantity);
|
|
1133
|
+
const remainder = unitsInCart % offer.quantity;
|
|
1134
|
+
const unitsNeeded = remainder === 0 ? 0 : offer.quantity - remainder;
|
|
1135
|
+
const applied = (cart?.applied_promotions ?? []).find(
|
|
1136
|
+
(p) => p && p.id === offer.promotionId
|
|
1137
|
+
);
|
|
1138
|
+
return {
|
|
1139
|
+
unitsInCart,
|
|
1140
|
+
unitsNeeded,
|
|
1141
|
+
groupsUnlocked,
|
|
1142
|
+
savingMajor: applied ? applied.amount || 0 : 0
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function offerBeatsRegularPrice(offer, unitPriceMajor) {
|
|
1146
|
+
if (!offer || typeof unitPriceMajor !== "number" || unitPriceMajor <= 0) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
const unitCents = Math.round(unitPriceMajor * 100);
|
|
1150
|
+
return unitCents * offer.quantity > offer.groupPriceCents;
|
|
1151
|
+
}
|
|
1025
1152
|
function useProductSizeChart(productOverride) {
|
|
1026
1153
|
const ctxProduct = useProductOptional();
|
|
1027
1154
|
const product = productOverride ?? ctxProduct;
|
|
@@ -1495,11 +1622,27 @@ var EMPTY_CART = {
|
|
|
1495
1622
|
};
|
|
1496
1623
|
function normalizeCartFromServer(cart) {
|
|
1497
1624
|
const toMajor = (n) => typeof n === "number" ? n / 100 : 0;
|
|
1625
|
+
const { automatic_discount_cents: wireAutomaticCents, ...rest } = cart;
|
|
1498
1626
|
return {
|
|
1499
|
-
...
|
|
1627
|
+
...rest,
|
|
1500
1628
|
subtotal: toMajor(cart.subtotal),
|
|
1501
1629
|
total: toMajor(cart.total),
|
|
1502
1630
|
...cart.discount_amount != null ? { discount_amount: toMajor(cart.discount_amount) } : {},
|
|
1631
|
+
// Offers-v2 automatic promotions. These MUST be converted here too —
|
|
1632
|
+
// they arrive in cents next to already-major totals, so forwarding them
|
|
1633
|
+
// raw makes every theme render a saving 100x too large. This function is
|
|
1634
|
+
// the one and only cents→major boundary for the cart.
|
|
1635
|
+
//
|
|
1636
|
+
// The backend field is `automatic_discount_cents`; themes get
|
|
1637
|
+
// `automatic_discount` (major), because a `_cents` name holding pounds
|
|
1638
|
+
// invites exactly the double-divide this conversion exists to prevent.
|
|
1639
|
+
...wireAutomaticCents != null ? { automatic_discount: toMajor(wireAutomaticCents) } : {},
|
|
1640
|
+
...Array.isArray(cart.applied_promotions) ? {
|
|
1641
|
+
applied_promotions: cart.applied_promotions.map((p) => ({
|
|
1642
|
+
...p,
|
|
1643
|
+
amount: toMajor(p.amount)
|
|
1644
|
+
}))
|
|
1645
|
+
} : {},
|
|
1503
1646
|
items: Array.isArray(cart.items) ? cart.items.map((it) => {
|
|
1504
1647
|
const raw = it;
|
|
1505
1648
|
return {
|
|
@@ -3850,32 +3993,6 @@ function assetUrl(name) {
|
|
|
3850
3993
|
return `${cleanBase}${filename}`;
|
|
3851
3994
|
}
|
|
3852
3995
|
|
|
3853
|
-
// src/utils/money.ts
|
|
3854
|
-
function resolveLocale(locale) {
|
|
3855
|
-
return locale === "ar" ? "ar-EG" : "en-EG";
|
|
3856
|
-
}
|
|
3857
|
-
function formatMoney(cents, options = {}) {
|
|
3858
|
-
const { currency, locale, fractionDigits = 0 } = options;
|
|
3859
|
-
const safe = Number.isFinite(cents) ? cents : 0;
|
|
3860
|
-
return new Intl.NumberFormat(resolveLocale(locale), {
|
|
3861
|
-
style: "currency",
|
|
3862
|
-
currency: currency || "EGP",
|
|
3863
|
-
maximumFractionDigits: fractionDigits
|
|
3864
|
-
}).format(safe / 100);
|
|
3865
|
-
}
|
|
3866
|
-
function formatMoneyMajor(amount, options = {}) {
|
|
3867
|
-
return formatMoney(
|
|
3868
|
-
(Number.isFinite(amount) ? amount : 0) * 100,
|
|
3869
|
-
options
|
|
3870
|
-
);
|
|
3871
|
-
}
|
|
3872
|
-
function centsToMajor(cents) {
|
|
3873
|
-
return (Number.isFinite(cents) ? cents : 0) / 100;
|
|
3874
|
-
}
|
|
3875
|
-
function majorToCents(amount) {
|
|
3876
|
-
return Math.round((Number.isFinite(amount) ? amount : 0) * 100);
|
|
3877
|
-
}
|
|
3878
|
-
|
|
3879
3996
|
// src/utils/templates.ts
|
|
3880
3997
|
function resolveSections(group) {
|
|
3881
3998
|
if (!group) return [];
|
|
@@ -3964,6 +4081,6 @@ function buildLocaleBundle(modules) {
|
|
|
3964
4081
|
return bundle;
|
|
3965
4082
|
}
|
|
3966
4083
|
|
|
3967
|
-
export { AddToCartButton, Block, CollectionCard, CollectionProvider, CurrencySwitcher, EditableImage, EditableText, Form, HeroMedia, ICON_NAMES, Icon, IconMap, Image, LOGO_SHAPE_OPTIONS, LOGO_SIZE_OPTIONS, Link, LocaleSwitcher, Logo, MAX_BLOCK_DEPTH, Money, NAVIGATE_EVENT, NuMuProvider, ProductCard, ProductProvider, RichText, Section, SectionContext, applyGlobalStyleTokens, applyImageTransform, asImageTransform, assetUrl, availableValues, buildLocaleBundle, buildThemeElement, centsToMajor, clearSdkSingleton, collectBlocks, collectSections, collectionHref, computeGlobalStyleTokens, defaultVariant, defineBlock, defineSection, defineThemeEntry, dynamicSource, findVariantByOptions, flattenMessages, focalSrc, formatMoney, formatMoneyMajor, getReactSingleton, getSdkSingleton, isDefinedBlock, isDefinedSection, isDynamicSource, isSdkAvailable, logoImgStyle, logoStyleTokens, majorToCents, mountTheme, pickTranslations, productHref, publishVariantSelection, readVariantSelection, registerReactSingleton, registerSdkSingleton, requestNavigate, resolveDynamicValue, resolveFontStack, resolveSections, resolveSettingsMap, resolveSizeChart, resolveSourcePath, sanitizeHtml, selectChromeSections, selectTemplateSections, useAnalytics, useApp, useArticle, useArticles, useBlog, useBlogs, useCachedResource, useCart, useCheckout, useCollection, useCollectionOptional, useCurrency, useCurrentTemplate, useCustomerActions, useCustomerAddresses, useGiftCardBalance, useImage, useListingHeading, useMetafield, useMetafields, useMoney, useNavigation, useOrder, useOrders, useProduct, useProductOptional, useProductSizeChart, useRelatedProducts, useReorder, useResolvedSettings, useSearch, useSection, useSectionGroup, useSectionOptional, useShippingRates, useVariantSelection, useWishlist };
|
|
4084
|
+
export { AddToCartButton, Block, CollectionCard, CollectionProvider, CurrencySwitcher, EditableImage, EditableText, Form, HeroMedia, ICON_NAMES, Icon, IconMap, Image, LOGO_SHAPE_OPTIONS, LOGO_SIZE_OPTIONS, Link, LocaleSwitcher, Logo, MAX_BLOCK_DEPTH, Money, NAVIGATE_EVENT, NuMuProvider, ProductCard, ProductProvider, RichText, Section, SectionContext, applyGlobalStyleTokens, applyImageTransform, asImageTransform, assetUrl, availableValues, buildLocaleBundle, buildThemeElement, centsToMajor, clearSdkSingleton, collectBlocks, collectSections, collectionHref, computeGlobalStyleTokens, defaultVariant, defineBlock, defineSection, defineThemeEntry, dynamicSource, eligibleUnitsInCart, findVariantByOptions, flattenMessages, focalSrc, formatMoney, formatMoneyMajor, getReactSingleton, getSdkSingleton, isDefinedBlock, isDefinedSection, isDynamicSource, isSdkAvailable, logoImgStyle, logoStyleTokens, majorToCents, mountTheme, multibuyOffers, offerBeatsRegularPrice, offerIncludesProduct, offerProgress, pickTranslations, productHref, publishVariantSelection, readVariantSelection, registerReactSingleton, registerSdkSingleton, requestNavigate, resolveDynamicValue, resolveFontStack, resolveSections, resolveSettingsMap, resolveSizeChart, resolveSourcePath, sanitizeHtml, selectChromeSections, selectTemplateSections, useActivePromotions, useAnalytics, useApp, useArticle, useArticles, useBlog, useBlogs, useCachedResource, useCart, useCheckout, useCollection, useCollectionOptional, useCurrency, useCurrentTemplate, useCustomerActions, useCustomerAddresses, useGiftCardBalance, useImage, useListingHeading, useMetafield, useMetafields, useMoney, useNavigation, useOrder, useOrders, useProduct, useProductOptional, useProductSizeChart, useRelatedProducts, useReorder, useResolvedSettings, useSearch, useSection, useSectionGroup, useSectionOptional, useShippingRates, useVariantSelection, useWishlist };
|
|
3968
4085
|
//# sourceMappingURL=index.mjs.map
|
|
3969
4086
|
//# sourceMappingURL=index.mjs.map
|