@nok-integration/storefront-react 0.19.38 → 0.19.40
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/dist/index.d.ts +77 -5
- package/dist/index.mjs +113 -29
- package/dist/standalone/storefront.mjs +113 -29
- package/dist/standalone/styles.css +15 -14
- package/dist/styles.css +15 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2089,6 +2089,10 @@ declare type CategoryPage = z.infer<typeof CategoryPage>;
|
|
|
2089
2089
|
*
|
|
2090
2090
|
* Fetching happens on mount, client-side, through the provider's client — so it inherits
|
|
2091
2091
|
* the host's session and market without being told about either.
|
|
2092
|
+
*
|
|
2093
|
+
* The page is held under the CODE it belongs to and read back only while that code is
|
|
2094
|
+
* still the one being asked for, so a change of `code` never leaves the previous category
|
|
2095
|
+
* on screen while the next one loads.
|
|
2092
2096
|
*/
|
|
2093
2097
|
export declare function CategoryView({ code, initialData, onNotFound }: CategoryViewProps): JSX.Element;
|
|
2094
2098
|
|
|
@@ -2100,6 +2104,10 @@ export declare interface CategoryViewProps {
|
|
|
2100
2104
|
* synchronously on the first frame — no skeleton, no waterfall — and revalidated in the
|
|
2101
2105
|
* background, because a server-rendered page can be arbitrarily old by the time it is
|
|
2102
2106
|
* interactive and price and availability are the two things worst served by being stale.
|
|
2107
|
+
*
|
|
2108
|
+
* Matched against `code` on every render, not only at mount, so a host that swaps both
|
|
2109
|
+
* together has the new page painted immediately. A page for a different code is ignored
|
|
2110
|
+
* rather than shown.
|
|
2103
2111
|
*/
|
|
2104
2112
|
initialData?: CategoryPage;
|
|
2105
2113
|
/**
|
|
@@ -4803,6 +4811,19 @@ declare const Product: z.ZodObject<{
|
|
|
4803
4811
|
|
|
4804
4812
|
declare type Product = z.infer<typeof Product>;
|
|
4805
4813
|
|
|
4814
|
+
declare class ProductCache {
|
|
4815
|
+
private readonly now;
|
|
4816
|
+
private readonly entries;
|
|
4817
|
+
/** Injectable so the age limit can be tested without leaning on timers. */
|
|
4818
|
+
constructor(now?: () => number);
|
|
4819
|
+
private static keyFor;
|
|
4820
|
+
get(sku: string, market?: Market): Product | undefined;
|
|
4821
|
+
set(product: Product, market?: Market): void;
|
|
4822
|
+
setMany(products: readonly Product[], market?: Market): void;
|
|
4823
|
+
/** Test seam. Nothing in the SDK calls this. */
|
|
4824
|
+
clear(): void;
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4806
4827
|
/**
|
|
4807
4828
|
* 4.3 PDP interactive surface, aligned to the DAZN mobile PDP design. Sections render
|
|
4808
4829
|
* from server-authoritative contract data: badges, title + price, promo nudge, description
|
|
@@ -4950,11 +4971,10 @@ declare type ProductVariant = z.infer<typeof ProductVariant>;
|
|
|
4950
4971
|
/**
|
|
4951
4972
|
* A whole product-detail screen, data fetching included — a **level 1 view**.
|
|
4952
4973
|
*
|
|
4953
|
-
*
|
|
4954
|
-
*
|
|
4955
|
-
*
|
|
4956
|
-
*
|
|
4957
|
-
* have bought from.
|
|
4974
|
+
* All of the fetching lives in {@link useProduct}, which is exported: a host composing its
|
|
4975
|
+
* own product screen from level-2 pieces gets the same behaviour rather than writing it
|
|
4976
|
+
* again. Switching between two items paints the new one immediately wherever the rail
|
|
4977
|
+
* already carried it, and never leaves the outgoing item up while the next one loads.
|
|
4958
4978
|
*
|
|
4959
4979
|
* `sku_not_found` is separated from the generic error path because it is not a fault: the
|
|
4960
4980
|
* product is gone, and "this is no longer available" is the honest thing to say. An error
|
|
@@ -5538,6 +5558,8 @@ export declare function ShippingInfo(): JSX.Element;
|
|
|
5538
5558
|
declare interface ShopContextValue {
|
|
5539
5559
|
api: DaznClient;
|
|
5540
5560
|
market?: Market;
|
|
5561
|
+
/** Products this provider has already been given — see {@link ProductCache}. */
|
|
5562
|
+
products: ProductCache;
|
|
5541
5563
|
theme: 'light' | 'dark' | 'auto';
|
|
5542
5564
|
notifications: 'internal' | 'host';
|
|
5543
5565
|
locale?: string;
|
|
@@ -6395,6 +6417,56 @@ export declare function useMoney(defaultLocale?: string): {
|
|
|
6395
6417
|
*/
|
|
6396
6418
|
export declare function useNavigate(): ((to: ShopRoute) => void) | null;
|
|
6397
6419
|
|
|
6420
|
+
/**
|
|
6421
|
+
* The data behind a product screen: the product, then inventory and recommendations.
|
|
6422
|
+
*
|
|
6423
|
+
* This is what {@link ProductView} is built from, exported so a host composing its own
|
|
6424
|
+
* product screen from the level-2 pieces does not have to write the fetching again. Two
|
|
6425
|
+
* properties are worth knowing about, because a hand-rolled version has to reproduce both.
|
|
6426
|
+
*
|
|
6427
|
+
* **Everything is held under the SKU it belongs to**, and read back only while that SKU is
|
|
6428
|
+
* still the one being asked for. A change of `sku` invalidates the whole set in the same
|
|
6429
|
+
* render that changes it, so the outgoing product is never left on screen while the next
|
|
6430
|
+
* one loads, and there is no frame in which one item's title sits above another item's
|
|
6431
|
+
* price.
|
|
6432
|
+
*
|
|
6433
|
+
* **A product already in memory is painted at once.** The recommendation rail carries whole
|
|
6434
|
+
* products, so opening one of them is usually drawn on the first frame and corrected a
|
|
6435
|
+
* moment later, rather than spending a round trip behind a skeleton. Inventory and
|
|
6436
|
+
* recommendations always come from the request — they are the two things worst served by
|
|
6437
|
+
* being remembered, and neither is needed to draw the top of the screen.
|
|
6438
|
+
*
|
|
6439
|
+
* Inventory and recommendations are best-effort: the screen renders perfectly well without
|
|
6440
|
+
* either, so a failure in one does not fault it.
|
|
6441
|
+
*/ export declare function useProduct(sku: string, options?: UseProductOptions): UseProductResult;
|
|
6442
|
+
|
|
6443
|
+
export declare interface UseProductOptions {
|
|
6444
|
+
/**
|
|
6445
|
+
* An already-fetched product, from a host that server-rendered this screen. Painted on
|
|
6446
|
+
* the first frame and revalidated in the background.
|
|
6447
|
+
*
|
|
6448
|
+
* Read on every render, not only at mount, so a host that swaps `sku` and `initialData`
|
|
6449
|
+
* together has the new product painted immediately. It is matched against `sku` before
|
|
6450
|
+
* use: a product for a different SKU is ignored rather than shown.
|
|
6451
|
+
*/
|
|
6452
|
+
initialData?: {
|
|
6453
|
+
product: Product;
|
|
6454
|
+
inventory?: Inventory | null;
|
|
6455
|
+
};
|
|
6456
|
+
/** Fetch the "you may also like" rail. Default `true`. */
|
|
6457
|
+
showRecommendations?: boolean;
|
|
6458
|
+
}
|
|
6459
|
+
|
|
6460
|
+
export declare interface UseProductResult {
|
|
6461
|
+
/** Null until there is something to draw for this SKU. */
|
|
6462
|
+
product: Product | null;
|
|
6463
|
+
inventory: Inventory | null;
|
|
6464
|
+
recommendations: Product[];
|
|
6465
|
+
/** Nothing to draw yet and nothing has gone wrong — it is still on its way. */
|
|
6466
|
+
loading: boolean;
|
|
6467
|
+
error: ReturnType<typeof codeOf> | undefined;
|
|
6468
|
+
}
|
|
6469
|
+
|
|
6398
6470
|
/** The typed, schema-validating commerce client. Requires a provider. */
|
|
6399
6471
|
export declare function useShopApi(): DaznClient;
|
|
6400
6472
|
|
package/dist/index.mjs
CHANGED
|
@@ -5112,6 +5112,49 @@ function resolveAssetUrl(src, base) {
|
|
|
5112
5112
|
if (!src.startsWith("/") || src.startsWith("//")) return src;
|
|
5113
5113
|
return `${base.replace(/\/+$/, "")}${src}`;
|
|
5114
5114
|
}
|
|
5115
|
+
const MAX_AGE_MS = 5 * 6e4;
|
|
5116
|
+
const CAPACITY = 60;
|
|
5117
|
+
class ProductCache {
|
|
5118
|
+
constructor(now = () => Date.now()) {
|
|
5119
|
+
this.now = now;
|
|
5120
|
+
}
|
|
5121
|
+
now;
|
|
5122
|
+
entries = /* @__PURE__ */ new Map();
|
|
5123
|
+
static keyFor(sku, market) {
|
|
5124
|
+
return `${market?.country ?? ""}\0${market?.lang ?? ""}\0${sku}`;
|
|
5125
|
+
}
|
|
5126
|
+
get(sku, market) {
|
|
5127
|
+
const key = ProductCache.keyFor(sku, market);
|
|
5128
|
+
const entry = this.entries.get(key);
|
|
5129
|
+
if (!entry) return void 0;
|
|
5130
|
+
if (this.now() - entry.at > MAX_AGE_MS) {
|
|
5131
|
+
this.entries.delete(key);
|
|
5132
|
+
return void 0;
|
|
5133
|
+
}
|
|
5134
|
+
this.entries.delete(key);
|
|
5135
|
+
this.entries.set(key, entry);
|
|
5136
|
+
return entry.product;
|
|
5137
|
+
}
|
|
5138
|
+
set(product, market) {
|
|
5139
|
+
const key = ProductCache.keyFor(product.sku, market);
|
|
5140
|
+
this.entries.delete(key);
|
|
5141
|
+
this.entries.set(key, {
|
|
5142
|
+
product,
|
|
5143
|
+
at: this.now()
|
|
5144
|
+
});
|
|
5145
|
+
while (this.entries.size > CAPACITY) {
|
|
5146
|
+
const oldest = this.entries.keys().next();
|
|
5147
|
+
if (oldest.done) break;
|
|
5148
|
+
this.entries.delete(oldest.value);
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
setMany(products, market) {
|
|
5152
|
+
for (const product of products) this.set(product, market);
|
|
5153
|
+
}
|
|
5154
|
+
clear() {
|
|
5155
|
+
this.entries.clear();
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5115
5158
|
function createShopFetch(opts) {
|
|
5116
5159
|
const baseFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
5117
5160
|
if (!baseFetch) throw new Error("No fetch implementation available");
|
|
@@ -5226,6 +5269,9 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5226
5269
|
const getTokenRef = useRef(getToken);
|
|
5227
5270
|
getTokenRef.current = getToken;
|
|
5228
5271
|
const stableGetToken = useCallback(() => getTokenRef.current(), []);
|
|
5272
|
+
const productsRef = useRef(null);
|
|
5273
|
+
productsRef.current ??= new ProductCache();
|
|
5274
|
+
const products = productsRef.current;
|
|
5229
5275
|
const emit = useCallback((event) => {
|
|
5230
5276
|
for (const listener of listeners.current) {
|
|
5231
5277
|
try {
|
|
@@ -5266,6 +5312,7 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5266
5312
|
const value2 = useMemo(() => ({
|
|
5267
5313
|
api,
|
|
5268
5314
|
market,
|
|
5315
|
+
products,
|
|
5269
5316
|
theme,
|
|
5270
5317
|
notifications,
|
|
5271
5318
|
locale,
|
|
@@ -5273,7 +5320,7 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5273
5320
|
emit,
|
|
5274
5321
|
subscribe,
|
|
5275
5322
|
navigate
|
|
5276
|
-
}), [api, market, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
|
|
5323
|
+
}), [api, market, products, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
|
|
5277
5324
|
return jsx(ShopContext.Provider, {
|
|
5278
5325
|
value: value2,
|
|
5279
5326
|
children: jsx(ScreenTitleProvider, {
|
|
@@ -5476,8 +5523,8 @@ function Skeleton({ width, height, radius, className, circle }) {
|
|
|
5476
5523
|
style
|
|
5477
5524
|
});
|
|
5478
5525
|
}
|
|
5479
|
-
const surface$1 = "
|
|
5480
|
-
const fill = "
|
|
5526
|
+
const surface$1 = "_surface_zq8bq_10";
|
|
5527
|
+
const fill = "_fill_zq8bq_35";
|
|
5481
5528
|
const styles$O = {
|
|
5482
5529
|
surface: surface$1,
|
|
5483
5530
|
fill
|
|
@@ -10710,32 +10757,51 @@ function CategoryContent({ page: page2 }) {
|
|
|
10710
10757
|
})]
|
|
10711
10758
|
});
|
|
10712
10759
|
}
|
|
10760
|
+
function productsIn(page2) {
|
|
10761
|
+
const found = [];
|
|
10762
|
+
for (const block of page2.blocks) {
|
|
10763
|
+
if (block.kind === "rail" || block.kind === "grid") found.push(...block.products);
|
|
10764
|
+
else if (block.kind === "featured" || block.kind === "product_gallery") found.push(block.product);
|
|
10765
|
+
}
|
|
10766
|
+
return found;
|
|
10767
|
+
}
|
|
10713
10768
|
function CategoryView({ code, initialData, onNotFound }) {
|
|
10714
10769
|
const t = useT();
|
|
10715
|
-
const { api, market, emit } = useShop();
|
|
10716
|
-
const [
|
|
10717
|
-
const [
|
|
10718
|
-
const
|
|
10770
|
+
const { api, market, emit, products } = useShop();
|
|
10771
|
+
const [loaded, setLoaded] = useState(null);
|
|
10772
|
+
const [failure, setFailure] = useState(null);
|
|
10773
|
+
const settled = loaded?.code === code;
|
|
10774
|
+
const seeded = initialData && initialData.code === code ? initialData : void 0;
|
|
10775
|
+
const page2 = (settled ? loaded?.page : void 0) ?? seeded ?? null;
|
|
10776
|
+
const error2 = failure?.code === code ? failure.error : void 0;
|
|
10777
|
+
const loading = !page2 && !error2 && !settled;
|
|
10719
10778
|
useEffect(() => {
|
|
10720
10779
|
let live = true;
|
|
10721
|
-
setError(void 0);
|
|
10722
10780
|
api.catalog.getCategoryPage(code, market).then((res) => {
|
|
10781
|
+
const fetched = res;
|
|
10782
|
+
products.setMany(productsIn(fetched), market);
|
|
10723
10783
|
if (!live) return;
|
|
10724
|
-
|
|
10784
|
+
setLoaded({
|
|
10785
|
+
code,
|
|
10786
|
+
page: fetched
|
|
10787
|
+
});
|
|
10725
10788
|
}).catch((err) => {
|
|
10726
10789
|
if (!live) return;
|
|
10727
10790
|
const c = codeOf(err);
|
|
10728
|
-
|
|
10791
|
+
setFailure({
|
|
10792
|
+
code,
|
|
10793
|
+
error: c
|
|
10794
|
+
});
|
|
10729
10795
|
emit({
|
|
10730
10796
|
type: "error",
|
|
10731
10797
|
code: c,
|
|
10732
10798
|
scope: "category"
|
|
10733
10799
|
});
|
|
10734
|
-
})
|
|
10800
|
+
});
|
|
10735
10801
|
return () => {
|
|
10736
10802
|
live = false;
|
|
10737
10803
|
};
|
|
10738
|
-
}, [api, code, market, emit]);
|
|
10804
|
+
}, [api, code, market, emit, products]);
|
|
10739
10805
|
if (error2 === "category_not_found" && onNotFound) return jsx(Fragment, {
|
|
10740
10806
|
children: onNotFound()
|
|
10741
10807
|
});
|
|
@@ -10756,23 +10822,22 @@ function CategoryView({ code, initialData, onNotFound }) {
|
|
|
10756
10822
|
})
|
|
10757
10823
|
});
|
|
10758
10824
|
}
|
|
10759
|
-
function
|
|
10760
|
-
const
|
|
10761
|
-
const { api, market, emit } = useShop();
|
|
10762
|
-
const [loaded, setLoaded] = useState(
|
|
10763
|
-
sku,
|
|
10764
|
-
product: initialData.product,
|
|
10765
|
-
inventory: initialData.inventory ?? null,
|
|
10766
|
-
recommendations: []
|
|
10767
|
-
} : null);
|
|
10825
|
+
function useProduct(sku, options2 = {}) {
|
|
10826
|
+
const { initialData, showRecommendations = true } = options2;
|
|
10827
|
+
const { api, market, emit, products } = useShop();
|
|
10828
|
+
const [loaded, setLoaded] = useState(null);
|
|
10768
10829
|
const [failure, setFailure] = useState(null);
|
|
10769
10830
|
const current2 = loaded?.sku === sku ? loaded : null;
|
|
10770
10831
|
const error2 = failure?.sku === sku ? failure.code : void 0;
|
|
10832
|
+
const seeded = initialData && initialData.product.sku === sku ? initialData.product : products.get(sku, market);
|
|
10833
|
+
const product = current2?.product ?? seeded ?? null;
|
|
10834
|
+
const inventory2 = current2 ? current2.inventory : initialData && initialData.product.sku === sku ? initialData.inventory ?? null : null;
|
|
10771
10835
|
useEffect(() => {
|
|
10772
10836
|
let live = true;
|
|
10773
10837
|
(async () => {
|
|
10774
10838
|
try {
|
|
10775
10839
|
const p = await api.catalog.getProduct(sku, market);
|
|
10840
|
+
products.set(p, market);
|
|
10776
10841
|
if (!live) return;
|
|
10777
10842
|
setLoaded({
|
|
10778
10843
|
sku,
|
|
@@ -10784,12 +10849,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10784
10849
|
const [inv, recs] = await Promise.allSettled([hasVariants ? Promise.resolve(null) : api.inventory.get(sku), showRecommendations ? api.catalog.getRecommendations(sku, market) : Promise.resolve({
|
|
10785
10850
|
data: []
|
|
10786
10851
|
})]);
|
|
10852
|
+
const recommendations = recs.status === "fulfilled" ? recs.value.data : [];
|
|
10853
|
+
products.setMany(recommendations, market);
|
|
10787
10854
|
if (!live) return;
|
|
10788
10855
|
setLoaded({
|
|
10789
10856
|
sku,
|
|
10790
10857
|
product: p,
|
|
10791
10858
|
inventory: inv.status === "fulfilled" ? inv.value : null,
|
|
10792
|
-
recommendations
|
|
10859
|
+
recommendations
|
|
10793
10860
|
});
|
|
10794
10861
|
} catch (err) {
|
|
10795
10862
|
if (!live) return;
|
|
@@ -10808,7 +10875,23 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10808
10875
|
return () => {
|
|
10809
10876
|
live = false;
|
|
10810
10877
|
};
|
|
10811
|
-
}, [api, sku, market, emit, showRecommendations]);
|
|
10878
|
+
}, [api, sku, market, emit, showRecommendations, products]);
|
|
10879
|
+
return {
|
|
10880
|
+
product,
|
|
10881
|
+
inventory: inventory2,
|
|
10882
|
+
recommendations: current2?.recommendations ?? [],
|
|
10883
|
+
loading: !product && !error2,
|
|
10884
|
+
error: error2
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
|
|
10888
|
+
const t = useT();
|
|
10889
|
+
const { product, inventory: inventory2, recommendations, loading, error: error2 } = useProduct(sku, {
|
|
10890
|
+
...initialData ? {
|
|
10891
|
+
initialData
|
|
10892
|
+
} : {},
|
|
10893
|
+
showRecommendations
|
|
10894
|
+
});
|
|
10812
10895
|
const unavailable = jsx(ShopSurface, {
|
|
10813
10896
|
tone: "light",
|
|
10814
10897
|
fill: true,
|
|
@@ -10823,14 +10906,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10823
10906
|
});
|
|
10824
10907
|
return unavailable;
|
|
10825
10908
|
}
|
|
10826
|
-
if (
|
|
10909
|
+
if (product) {
|
|
10827
10910
|
return jsx(ProductDetail, {
|
|
10828
|
-
product
|
|
10829
|
-
inventory:
|
|
10830
|
-
recommendations
|
|
10831
|
-
});
|
|
10911
|
+
product,
|
|
10912
|
+
inventory: inventory2,
|
|
10913
|
+
recommendations
|
|
10914
|
+
}, product.sku);
|
|
10832
10915
|
}
|
|
10833
|
-
if (
|
|
10916
|
+
if (loading) return jsx(ScreenSkeleton, {
|
|
10834
10917
|
tone: "light"
|
|
10835
10918
|
});
|
|
10836
10919
|
return jsx(ShopSurface, {
|
|
@@ -11634,6 +11717,7 @@ export {
|
|
|
11634
11717
|
useCartSheet,
|
|
11635
11718
|
useMoney,
|
|
11636
11719
|
useNavigate,
|
|
11720
|
+
useProduct,
|
|
11637
11721
|
useShopApi,
|
|
11638
11722
|
useShopEvent,
|
|
11639
11723
|
useShopOptional,
|
|
@@ -5112,6 +5112,49 @@ function resolveAssetUrl(src, base) {
|
|
|
5112
5112
|
if (!src.startsWith("/") || src.startsWith("//")) return src;
|
|
5113
5113
|
return `${base.replace(/\/+$/, "")}${src}`;
|
|
5114
5114
|
}
|
|
5115
|
+
const MAX_AGE_MS = 5 * 6e4;
|
|
5116
|
+
const CAPACITY = 60;
|
|
5117
|
+
class ProductCache {
|
|
5118
|
+
constructor(now = () => Date.now()) {
|
|
5119
|
+
this.now = now;
|
|
5120
|
+
}
|
|
5121
|
+
now;
|
|
5122
|
+
entries = /* @__PURE__ */ new Map();
|
|
5123
|
+
static keyFor(sku, market) {
|
|
5124
|
+
return `${market?.country ?? ""}\0${market?.lang ?? ""}\0${sku}`;
|
|
5125
|
+
}
|
|
5126
|
+
get(sku, market) {
|
|
5127
|
+
const key = ProductCache.keyFor(sku, market);
|
|
5128
|
+
const entry = this.entries.get(key);
|
|
5129
|
+
if (!entry) return void 0;
|
|
5130
|
+
if (this.now() - entry.at > MAX_AGE_MS) {
|
|
5131
|
+
this.entries.delete(key);
|
|
5132
|
+
return void 0;
|
|
5133
|
+
}
|
|
5134
|
+
this.entries.delete(key);
|
|
5135
|
+
this.entries.set(key, entry);
|
|
5136
|
+
return entry.product;
|
|
5137
|
+
}
|
|
5138
|
+
set(product, market) {
|
|
5139
|
+
const key = ProductCache.keyFor(product.sku, market);
|
|
5140
|
+
this.entries.delete(key);
|
|
5141
|
+
this.entries.set(key, {
|
|
5142
|
+
product,
|
|
5143
|
+
at: this.now()
|
|
5144
|
+
});
|
|
5145
|
+
while (this.entries.size > CAPACITY) {
|
|
5146
|
+
const oldest = this.entries.keys().next();
|
|
5147
|
+
if (oldest.done) break;
|
|
5148
|
+
this.entries.delete(oldest.value);
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
setMany(products, market) {
|
|
5152
|
+
for (const product of products) this.set(product, market);
|
|
5153
|
+
}
|
|
5154
|
+
clear() {
|
|
5155
|
+
this.entries.clear();
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5115
5158
|
function createShopFetch(opts) {
|
|
5116
5159
|
const baseFetch = opts.fetchImpl ?? globalThis.fetch;
|
|
5117
5160
|
if (!baseFetch) throw new Error("No fetch implementation available");
|
|
@@ -5226,6 +5269,9 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5226
5269
|
const getTokenRef = useRef(getToken);
|
|
5227
5270
|
getTokenRef.current = getToken;
|
|
5228
5271
|
const stableGetToken = useCallback(() => getTokenRef.current(), []);
|
|
5272
|
+
const productsRef = useRef(null);
|
|
5273
|
+
productsRef.current ??= new ProductCache();
|
|
5274
|
+
const products = productsRef.current;
|
|
5229
5275
|
const emit = useCallback((event) => {
|
|
5230
5276
|
for (const listener of listeners.current) {
|
|
5231
5277
|
try {
|
|
@@ -5266,6 +5312,7 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5266
5312
|
const value2 = useMemo(() => ({
|
|
5267
5313
|
api,
|
|
5268
5314
|
market,
|
|
5315
|
+
products,
|
|
5269
5316
|
theme,
|
|
5270
5317
|
notifications,
|
|
5271
5318
|
locale,
|
|
@@ -5273,7 +5320,7 @@ function DaznShopProvider({ children, ...config }) {
|
|
|
5273
5320
|
emit,
|
|
5274
5321
|
subscribe,
|
|
5275
5322
|
navigate
|
|
5276
|
-
}), [api, market, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
|
|
5323
|
+
}), [api, market, products, theme, notifications, locale, assetBaseUrl, emit, subscribe, navigate]);
|
|
5277
5324
|
return jsx(ShopContext.Provider, {
|
|
5278
5325
|
value: value2,
|
|
5279
5326
|
children: jsx(ScreenTitleProvider, {
|
|
@@ -5476,8 +5523,8 @@ function Skeleton({ width, height, radius, className, circle }) {
|
|
|
5476
5523
|
style
|
|
5477
5524
|
});
|
|
5478
5525
|
}
|
|
5479
|
-
const surface$1 = "
|
|
5480
|
-
const fill = "
|
|
5526
|
+
const surface$1 = "_surface_zq8bq_10";
|
|
5527
|
+
const fill = "_fill_zq8bq_35";
|
|
5481
5528
|
const styles$O = {
|
|
5482
5529
|
surface: surface$1,
|
|
5483
5530
|
fill
|
|
@@ -10710,32 +10757,51 @@ function CategoryContent({ page: page2 }) {
|
|
|
10710
10757
|
})]
|
|
10711
10758
|
});
|
|
10712
10759
|
}
|
|
10760
|
+
function productsIn(page2) {
|
|
10761
|
+
const found = [];
|
|
10762
|
+
for (const block of page2.blocks) {
|
|
10763
|
+
if (block.kind === "rail" || block.kind === "grid") found.push(...block.products);
|
|
10764
|
+
else if (block.kind === "featured" || block.kind === "product_gallery") found.push(block.product);
|
|
10765
|
+
}
|
|
10766
|
+
return found;
|
|
10767
|
+
}
|
|
10713
10768
|
function CategoryView({ code, initialData, onNotFound }) {
|
|
10714
10769
|
const t = useT();
|
|
10715
|
-
const { api, market, emit } = useShop();
|
|
10716
|
-
const [
|
|
10717
|
-
const [
|
|
10718
|
-
const
|
|
10770
|
+
const { api, market, emit, products } = useShop();
|
|
10771
|
+
const [loaded, setLoaded] = useState(null);
|
|
10772
|
+
const [failure, setFailure] = useState(null);
|
|
10773
|
+
const settled = loaded?.code === code;
|
|
10774
|
+
const seeded = initialData && initialData.code === code ? initialData : void 0;
|
|
10775
|
+
const page2 = (settled ? loaded?.page : void 0) ?? seeded ?? null;
|
|
10776
|
+
const error2 = failure?.code === code ? failure.error : void 0;
|
|
10777
|
+
const loading = !page2 && !error2 && !settled;
|
|
10719
10778
|
useEffect(() => {
|
|
10720
10779
|
let live = true;
|
|
10721
|
-
setError(void 0);
|
|
10722
10780
|
api.catalog.getCategoryPage(code, market).then((res) => {
|
|
10781
|
+
const fetched = res;
|
|
10782
|
+
products.setMany(productsIn(fetched), market);
|
|
10723
10783
|
if (!live) return;
|
|
10724
|
-
|
|
10784
|
+
setLoaded({
|
|
10785
|
+
code,
|
|
10786
|
+
page: fetched
|
|
10787
|
+
});
|
|
10725
10788
|
}).catch((err) => {
|
|
10726
10789
|
if (!live) return;
|
|
10727
10790
|
const c = codeOf(err);
|
|
10728
|
-
|
|
10791
|
+
setFailure({
|
|
10792
|
+
code,
|
|
10793
|
+
error: c
|
|
10794
|
+
});
|
|
10729
10795
|
emit({
|
|
10730
10796
|
type: "error",
|
|
10731
10797
|
code: c,
|
|
10732
10798
|
scope: "category"
|
|
10733
10799
|
});
|
|
10734
|
-
})
|
|
10800
|
+
});
|
|
10735
10801
|
return () => {
|
|
10736
10802
|
live = false;
|
|
10737
10803
|
};
|
|
10738
|
-
}, [api, code, market, emit]);
|
|
10804
|
+
}, [api, code, market, emit, products]);
|
|
10739
10805
|
if (error2 === "category_not_found" && onNotFound) return jsx(Fragment, {
|
|
10740
10806
|
children: onNotFound()
|
|
10741
10807
|
});
|
|
@@ -10756,23 +10822,22 @@ function CategoryView({ code, initialData, onNotFound }) {
|
|
|
10756
10822
|
})
|
|
10757
10823
|
});
|
|
10758
10824
|
}
|
|
10759
|
-
function
|
|
10760
|
-
const
|
|
10761
|
-
const { api, market, emit } = useShop();
|
|
10762
|
-
const [loaded, setLoaded] = useState(
|
|
10763
|
-
sku,
|
|
10764
|
-
product: initialData.product,
|
|
10765
|
-
inventory: initialData.inventory ?? null,
|
|
10766
|
-
recommendations: []
|
|
10767
|
-
} : null);
|
|
10825
|
+
function useProduct(sku, options2 = {}) {
|
|
10826
|
+
const { initialData, showRecommendations = true } = options2;
|
|
10827
|
+
const { api, market, emit, products } = useShop();
|
|
10828
|
+
const [loaded, setLoaded] = useState(null);
|
|
10768
10829
|
const [failure, setFailure] = useState(null);
|
|
10769
10830
|
const current2 = loaded?.sku === sku ? loaded : null;
|
|
10770
10831
|
const error2 = failure?.sku === sku ? failure.code : void 0;
|
|
10832
|
+
const seeded = initialData && initialData.product.sku === sku ? initialData.product : products.get(sku, market);
|
|
10833
|
+
const product = current2?.product ?? seeded ?? null;
|
|
10834
|
+
const inventory2 = current2 ? current2.inventory : initialData && initialData.product.sku === sku ? initialData.inventory ?? null : null;
|
|
10771
10835
|
useEffect(() => {
|
|
10772
10836
|
let live = true;
|
|
10773
10837
|
(async () => {
|
|
10774
10838
|
try {
|
|
10775
10839
|
const p = await api.catalog.getProduct(sku, market);
|
|
10840
|
+
products.set(p, market);
|
|
10776
10841
|
if (!live) return;
|
|
10777
10842
|
setLoaded({
|
|
10778
10843
|
sku,
|
|
@@ -10784,12 +10849,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10784
10849
|
const [inv, recs] = await Promise.allSettled([hasVariants ? Promise.resolve(null) : api.inventory.get(sku), showRecommendations ? api.catalog.getRecommendations(sku, market) : Promise.resolve({
|
|
10785
10850
|
data: []
|
|
10786
10851
|
})]);
|
|
10852
|
+
const recommendations = recs.status === "fulfilled" ? recs.value.data : [];
|
|
10853
|
+
products.setMany(recommendations, market);
|
|
10787
10854
|
if (!live) return;
|
|
10788
10855
|
setLoaded({
|
|
10789
10856
|
sku,
|
|
10790
10857
|
product: p,
|
|
10791
10858
|
inventory: inv.status === "fulfilled" ? inv.value : null,
|
|
10792
|
-
recommendations
|
|
10859
|
+
recommendations
|
|
10793
10860
|
});
|
|
10794
10861
|
} catch (err) {
|
|
10795
10862
|
if (!live) return;
|
|
@@ -10808,7 +10875,23 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10808
10875
|
return () => {
|
|
10809
10876
|
live = false;
|
|
10810
10877
|
};
|
|
10811
|
-
}, [api, sku, market, emit, showRecommendations]);
|
|
10878
|
+
}, [api, sku, market, emit, showRecommendations, products]);
|
|
10879
|
+
return {
|
|
10880
|
+
product,
|
|
10881
|
+
inventory: inventory2,
|
|
10882
|
+
recommendations: current2?.recommendations ?? [],
|
|
10883
|
+
loading: !product && !error2,
|
|
10884
|
+
error: error2
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
function ProductView({ sku, initialData, showRecommendations = true, onNotFound }) {
|
|
10888
|
+
const t = useT();
|
|
10889
|
+
const { product, inventory: inventory2, recommendations, loading, error: error2 } = useProduct(sku, {
|
|
10890
|
+
...initialData ? {
|
|
10891
|
+
initialData
|
|
10892
|
+
} : {},
|
|
10893
|
+
showRecommendations
|
|
10894
|
+
});
|
|
10812
10895
|
const unavailable = jsx(ShopSurface, {
|
|
10813
10896
|
tone: "light",
|
|
10814
10897
|
fill: true,
|
|
@@ -10823,14 +10906,14 @@ function ProductView({ sku, initialData, showRecommendations = true, onNotFound
|
|
|
10823
10906
|
});
|
|
10824
10907
|
return unavailable;
|
|
10825
10908
|
}
|
|
10826
|
-
if (
|
|
10909
|
+
if (product) {
|
|
10827
10910
|
return jsx(ProductDetail, {
|
|
10828
|
-
product
|
|
10829
|
-
inventory:
|
|
10830
|
-
recommendations
|
|
10831
|
-
});
|
|
10911
|
+
product,
|
|
10912
|
+
inventory: inventory2,
|
|
10913
|
+
recommendations
|
|
10914
|
+
}, product.sku);
|
|
10832
10915
|
}
|
|
10833
|
-
if (
|
|
10916
|
+
if (loading) return jsx(ScreenSkeleton, {
|
|
10834
10917
|
tone: "light"
|
|
10835
10918
|
});
|
|
10836
10919
|
return jsx(ShopSurface, {
|
|
@@ -11634,6 +11717,7 @@ export {
|
|
|
11634
11717
|
useCartSheet,
|
|
11635
11718
|
useMoney,
|
|
11636
11719
|
useNavigate,
|
|
11720
|
+
useProduct,
|
|
11637
11721
|
useShopApi,
|
|
11638
11722
|
useShopEvent,
|
|
11639
11723
|
useShopOptional,
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
background-position: 0 50%;
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
.
|
|
219
|
+
._surface_zq8bq_10 {
|
|
220
220
|
font-family: var(--font-family-base);
|
|
221
221
|
font-weight: var(--font-weight-regular);
|
|
222
222
|
font-size: var(--type-paragraph-size);
|
|
@@ -226,43 +226,44 @@
|
|
|
226
226
|
-webkit-font-smoothing: antialiased;
|
|
227
227
|
text-rendering: optimizeLegibility;
|
|
228
228
|
}
|
|
229
|
-
.
|
|
230
|
-
.
|
|
231
|
-
.
|
|
229
|
+
._surface_zq8bq_10 *,
|
|
230
|
+
._surface_zq8bq_10 *::before,
|
|
231
|
+
._surface_zq8bq_10 *::after {
|
|
232
232
|
box-sizing: border-box;
|
|
233
233
|
}
|
|
234
|
-
.
|
|
234
|
+
._fill_zq8bq_35 {
|
|
235
235
|
min-height: 100vh;
|
|
236
236
|
min-height: calc(100dvh - var(--surface-top, 0px));
|
|
237
|
+
box-shadow: 0 calc(100lvh - 100dvh) 0 var(--color-surface-default);
|
|
237
238
|
}
|
|
238
|
-
:where(.
|
|
239
|
+
:where(._surface_zq8bq_10) :where(h1, h2, h3) {
|
|
239
240
|
margin: 0;
|
|
240
241
|
font-weight: var(--font-weight-bold);
|
|
241
242
|
}
|
|
242
|
-
:where(.
|
|
243
|
+
:where(._surface_zq8bq_10) :where(h1) {
|
|
243
244
|
font-size: var(--type-heading-lg-size);
|
|
244
245
|
line-height: var(--type-heading-lg-line);
|
|
245
246
|
}
|
|
246
|
-
:where(.
|
|
247
|
+
:where(._surface_zq8bq_10) :where(h2) {
|
|
247
248
|
font-size: var(--type-heading-md-size);
|
|
248
249
|
line-height: var(--type-heading-md-line);
|
|
249
250
|
}
|
|
250
|
-
.
|
|
251
|
+
._surface_zq8bq_10 a:not([class]) {
|
|
251
252
|
color: inherit;
|
|
252
253
|
}
|
|
253
|
-
.
|
|
254
|
+
._surface_zq8bq_10 img {
|
|
254
255
|
max-width: 100%;
|
|
255
256
|
height: auto;
|
|
256
257
|
}
|
|
257
|
-
.
|
|
258
|
+
._surface_zq8bq_10 :focus-visible {
|
|
258
259
|
outline: 2px solid var(--color-surface-max);
|
|
259
260
|
outline-offset: 2px;
|
|
260
261
|
border-radius: var(--radius-4);
|
|
261
262
|
}
|
|
262
263
|
@media (prefers-reduced-motion: reduce) {
|
|
263
|
-
.
|
|
264
|
-
.
|
|
265
|
-
.
|
|
264
|
+
._surface_zq8bq_10 *,
|
|
265
|
+
._surface_zq8bq_10 *::before,
|
|
266
|
+
._surface_zq8bq_10 *::after {
|
|
266
267
|
animation-duration: 0.001ms !important;
|
|
267
268
|
animation-iteration-count: 1 !important;
|
|
268
269
|
transition-duration: 0.001ms !important;
|
package/dist/styles.css
CHANGED
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
background-position: 0 50%;
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
.
|
|
219
|
+
._surface_zq8bq_10 {
|
|
220
220
|
font-family: var(--font-family-base);
|
|
221
221
|
font-weight: var(--font-weight-regular);
|
|
222
222
|
font-size: var(--type-paragraph-size);
|
|
@@ -226,43 +226,44 @@
|
|
|
226
226
|
-webkit-font-smoothing: antialiased;
|
|
227
227
|
text-rendering: optimizeLegibility;
|
|
228
228
|
}
|
|
229
|
-
.
|
|
230
|
-
.
|
|
231
|
-
.
|
|
229
|
+
._surface_zq8bq_10 *,
|
|
230
|
+
._surface_zq8bq_10 *::before,
|
|
231
|
+
._surface_zq8bq_10 *::after {
|
|
232
232
|
box-sizing: border-box;
|
|
233
233
|
}
|
|
234
|
-
.
|
|
234
|
+
._fill_zq8bq_35 {
|
|
235
235
|
min-height: 100vh;
|
|
236
236
|
min-height: calc(100dvh - var(--surface-top, 0px));
|
|
237
|
+
box-shadow: 0 calc(100lvh - 100dvh) 0 var(--color-surface-default);
|
|
237
238
|
}
|
|
238
|
-
:where(.
|
|
239
|
+
:where(._surface_zq8bq_10) :where(h1, h2, h3) {
|
|
239
240
|
margin: 0;
|
|
240
241
|
font-weight: var(--font-weight-bold);
|
|
241
242
|
}
|
|
242
|
-
:where(.
|
|
243
|
+
:where(._surface_zq8bq_10) :where(h1) {
|
|
243
244
|
font-size: var(--type-heading-lg-size);
|
|
244
245
|
line-height: var(--type-heading-lg-line);
|
|
245
246
|
}
|
|
246
|
-
:where(.
|
|
247
|
+
:where(._surface_zq8bq_10) :where(h2) {
|
|
247
248
|
font-size: var(--type-heading-md-size);
|
|
248
249
|
line-height: var(--type-heading-md-line);
|
|
249
250
|
}
|
|
250
|
-
.
|
|
251
|
+
._surface_zq8bq_10 a:not([class]) {
|
|
251
252
|
color: inherit;
|
|
252
253
|
}
|
|
253
|
-
.
|
|
254
|
+
._surface_zq8bq_10 img {
|
|
254
255
|
max-width: 100%;
|
|
255
256
|
height: auto;
|
|
256
257
|
}
|
|
257
|
-
.
|
|
258
|
+
._surface_zq8bq_10 :focus-visible {
|
|
258
259
|
outline: 2px solid var(--color-surface-max);
|
|
259
260
|
outline-offset: 2px;
|
|
260
261
|
border-radius: var(--radius-4);
|
|
261
262
|
}
|
|
262
263
|
@media (prefers-reduced-motion: reduce) {
|
|
263
|
-
.
|
|
264
|
-
.
|
|
265
|
-
.
|
|
264
|
+
._surface_zq8bq_10 *,
|
|
265
|
+
._surface_zq8bq_10 *::before,
|
|
266
|
+
._surface_zq8bq_10 *::after {
|
|
266
267
|
animation-duration: 0.001ms !important;
|
|
267
268
|
animation-iteration-count: 1 !important;
|
|
268
269
|
transition-duration: 0.001ms !important;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nok-integration/storefront-react",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.40",
|
|
4
4
|
"description": "Mountable React storefront components: catalogue, product detail and cart, mounted directly into a host DOM tree. No iframe.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"publishConfig": {
|