@nok-integration/storefront-react 0.6.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.
@@ -0,0 +1,3530 @@
1
+ import { AnchorHTMLAttributes } from 'react';
2
+ import { ButtonHTMLAttributes } from 'react';
3
+ import { ComponentPropsWithoutRef } from 'react';
4
+ import { CSSProperties } from 'react';
5
+ import { ForwardRefExoticComponent } from 'react';
6
+ import { JSX } from 'react';
7
+ import { ReactNode } from 'react';
8
+ import { RefAttributes } from 'react';
9
+ import { z } from 'zod';
10
+
11
+ /** `POST /v1/cart/items` — add an item. */
12
+ declare const AddCartItemRequest: z.ZodObject<{
13
+ sku: z.ZodString;
14
+ qty: z.ZodNumber;
15
+ }, "strict", z.ZodTypeAny, {
16
+ sku: string;
17
+ qty: number;
18
+ }, {
19
+ sku: string;
20
+ qty: number;
21
+ }>;
22
+
23
+ declare type AddCartItemRequest = z.infer<typeof AddCartItemRequest>;
24
+
25
+ /** The `error` member — a machine `code`, a human `message`, and optional `details`. */
26
+ declare const ApiErrorBody: z.ZodObject<{
27
+ code: z.ZodEnum<["invalid_request", "unauthorized", "out_of_stock", "cart_locked", "sku_not_found", "category_not_found", "amount_mismatch", "duplicate_order", "order_not_found", "order_not_cancellable", "forbidden", "invalid_transition", "rate_limited", "server_error"]>;
28
+ message: z.ZodString;
29
+ details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ code: "invalid_request" | "unauthorized" | "out_of_stock" | "cart_locked" | "sku_not_found" | "category_not_found" | "amount_mismatch" | "duplicate_order" | "order_not_found" | "order_not_cancellable" | "forbidden" | "invalid_transition" | "rate_limited" | "server_error";
32
+ message: string;
33
+ details?: Record<string, unknown> | undefined;
34
+ }, {
35
+ code: "invalid_request" | "unauthorized" | "out_of_stock" | "cart_locked" | "sku_not_found" | "category_not_found" | "amount_mismatch" | "duplicate_order" | "order_not_found" | "order_not_cancellable" | "forbidden" | "invalid_transition" | "rate_limited" | "server_error";
36
+ message: string;
37
+ details?: Record<string, unknown> | undefined;
38
+ }>;
39
+
40
+ declare type ApiErrorBody = z.infer<typeof ApiErrorBody>;
41
+
42
+ /**
43
+ * Narrow an unknown thrown value to the typed API error, if it is one.
44
+ *
45
+ * **Structural, not `instanceof`.** The identity check looks obviously correct and is
46
+ * wrong across a bundle boundary: the standalone distribution embeds its own copy of the
47
+ * client, so an error thrown inside the SDK is not an `instanceof` the `DaznApiError`
48
+ * a host imported separately — and vice versa. Every branch silently falls through to
49
+ * "unknown error", which is the worst possible failure for error handling because it
50
+ * still *works*, just always wrongly.
51
+ *
52
+ * Found by consuming the built package instead of the source. Against source there is
53
+ * one class and `instanceof` passes.
54
+ */
55
+ export declare function asApiError(err: unknown): DaznApiError | undefined;
56
+
57
+ /** Small status/label pill. */
58
+ export declare function Badge({ tone, children }: BadgeProps): JSX.Element;
59
+
60
+ export declare interface BadgeProps {
61
+ tone?: BadgeTone;
62
+ children: ReactNode;
63
+ }
64
+
65
+ export declare type BadgeTone = 'neutral' | 'success' | 'critical' | 'promo';
66
+
67
+ export declare function BottomSheet({ open, onClose, title, children, footer }: BottomSheetProps): JSX.Element | null;
68
+
69
+ export declare interface BottomSheetProps {
70
+ open: boolean;
71
+ onClose: () => void;
72
+ title: string;
73
+ children: ReactNode;
74
+ /** Optional sticky footer (e.g. a StickyCtaBar). */
75
+ footer?: ReactNode;
76
+ }
77
+
78
+ /**
79
+ * Primary/secondary/ghost button. ≥44px tap target, visible focus, disabled and
80
+ * loading states. Loading sets `aria-busy` and blocks clicks without collapsing size.
81
+ */
82
+ export declare const Button: ForwardRefExoticComponent<ButtonProps & RefAttributes<HTMLButtonElement>>;
83
+
84
+ export declare interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
85
+ variant?: ButtonVariant;
86
+ /** Shows a spinner and marks the control busy; also disables interaction. */
87
+ loading?: boolean;
88
+ fullWidth?: boolean;
89
+ }
90
+
91
+ export declare type ButtonVariant = 'primary' | 'secondary' | 'ghost';
92
+
93
+ /** A line in the cart. `line_total` = `unit_price` × `qty`, in the cart currency. */
94
+ declare const CartItem: z.ZodObject<{
95
+ sku: z.ZodString;
96
+ qty: z.ZodNumber;
97
+ unit_price: z.ZodObject<{
98
+ amount: z.ZodNumber;
99
+ currency: z.ZodString;
100
+ }, "strict", z.ZodTypeAny, {
101
+ amount: number;
102
+ currency: string;
103
+ }, {
104
+ amount: number;
105
+ currency: string;
106
+ }>;
107
+ line_total: z.ZodObject<{
108
+ amount: z.ZodNumber;
109
+ currency: z.ZodString;
110
+ }, "strict", z.ZodTypeAny, {
111
+ amount: number;
112
+ currency: string;
113
+ }, {
114
+ amount: number;
115
+ currency: string;
116
+ }>;
117
+ /**
118
+ * Product title for this line, e.g. "Signature Hoodie".
119
+ *
120
+ * **Display only**, and the reason it exists: without it a cart can only render the
121
+ * `sku`, and a SKU is an internal identifier, not something to show a fan. A consumer
122
+ * must not parse it, match on it, or treat it as stable — it is whatever the catalogue
123
+ * says today, and it is localised with the rest of the catalogue.
124
+ *
125
+ * Optional because it is projected from the catalogue read model, which is a cache: a
126
+ * SKU that has been de-listed since it entered the cart still has a line, and a
127
+ * fabricated title would be worse than none. Absent = the consumer falls back to
128
+ * whatever it can show, and the line still prices and checks out correctly.
129
+ */
130
+ title: z.ZodOptional<z.ZodString>;
131
+ /**
132
+ * Line thumbnail — the variant image where the variant has one, otherwise the
133
+ * product's first media entry. Absent = render a neutral placeholder; never
134
+ * substitute another product's image.
135
+ */
136
+ image: z.ZodOptional<z.ZodString>;
137
+ /**
138
+ * The variant's defining attributes, e.g. `{ size: 'M', colour: 'Black' }`. Mirrors
139
+ * {@link ProductVariant.attributes}, so a consumer reads one shape everywhere.
140
+ *
141
+ * Sent as the raw map rather than a pre-composed "M · Black" string on purpose. The
142
+ * separator, the order and the casing are presentation, and the engine has no business
143
+ * choosing them for a host that may render size and colour as separate chips. Two
144
+ * lines differing only by size are otherwise indistinguishable in the cart, which is
145
+ * the bug this field exists to fix.
146
+ */
147
+ attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
148
+ /**
149
+ * Optional stock ceiling for this line, so the quantity stepper can cap the increment
150
+ * and tell the fan *why* instead of silently refusing. Mirrors
151
+ * {@link ProductVariant.available}.
152
+ *
153
+ * **Display only, and a hint.** The projection is a cache and the authoritative check
154
+ * runs at checkout (DAZN's, under ADR-0008) — a consumer must not treat this as a
155
+ * guarantee that N are purchasable. Absent = impose no cap client-side; the engine
156
+ * still rejects an over-quantity update with `out_of_stock`, which carries the real
157
+ * `available` figure in its error details.
158
+ */
159
+ available: z.ZodOptional<z.ZodNumber>;
160
+ }, "strip", z.ZodTypeAny, {
161
+ sku: string;
162
+ qty: number;
163
+ unit_price: {
164
+ amount: number;
165
+ currency: string;
166
+ };
167
+ line_total: {
168
+ amount: number;
169
+ currency: string;
170
+ };
171
+ title?: string | undefined;
172
+ image?: string | undefined;
173
+ attributes?: Record<string, string> | undefined;
174
+ available?: number | undefined;
175
+ }, {
176
+ sku: string;
177
+ qty: number;
178
+ unit_price: {
179
+ amount: number;
180
+ currency: string;
181
+ };
182
+ line_total: {
183
+ amount: number;
184
+ currency: string;
185
+ };
186
+ title?: string | undefined;
187
+ image?: string | undefined;
188
+ attributes?: Record<string, string> | undefined;
189
+ available?: number | undefined;
190
+ }>;
191
+
192
+ declare type CartItem = z.infer<typeof CartItem>;
193
+
194
+ /**
195
+ * A single cart line: media, title, variant, unit price, qty stepper, and line total.
196
+ *
197
+ * Title, image and variant come from the line itself (contracts 2.15.0). Before that the
198
+ * wire carried only a SKU, so this rendered an internal code against an empty grey box —
199
+ * and two sizes of one product were indistinguishable.
200
+ *
201
+ * All three stay optional, because the catalogue projection is a cache: a SKU de-listed
202
+ * after it entered the cart still has a line. It degrades rather than breaks — the SKU
203
+ * stands in for a missing title, and a missing image leaves the placeholder — so the fan
204
+ * can always still read the price and remove the line.
205
+ */
206
+ export declare function CartLineItem({ item, title, image, maxQty, onQtyChange, onRemove, pending, }: CartLineItemProps): JSX.Element;
207
+
208
+ export declare interface CartLineItemProps {
209
+ item: CartItem;
210
+ /**
211
+ * Override the line's own title/image (contracts 2.15.0). Escape hatches for a host
212
+ * with richer catalogue data than the cart carries — not the normal path, and not
213
+ * required for the line to render properly.
214
+ */
215
+ title?: string;
216
+ image?: string;
217
+ maxQty?: number;
218
+ onQtyChange: (qty: number) => void;
219
+ onRemove: () => void;
220
+ pending?: boolean;
221
+ }
222
+
223
+ declare interface CartSheetApi {
224
+ open: () => void;
225
+ close: () => void;
226
+ isOpen: boolean;
227
+ }
228
+
229
+ /**
230
+ * Presents the cart as a bottom sheet over the current screen, so adding to
231
+ * the cart never costs the fan their place in the catalogue.
232
+ *
233
+ * The `/cart` route still renders the same {@link CartView} — this is a second
234
+ * presentation of one component, not a second cart. Both read the session cart from the
235
+ * engine, so they cannot disagree.
236
+ */
237
+ declare function CartSheetProvider({ children }: {
238
+ children: ReactNode;
239
+ }): JSX.Element;
240
+ export { CartSheetProvider as CartSheet }
241
+ export { CartSheetProvider }
242
+
243
+ /**
244
+ * Opens the cart as a bottom sheet.
245
+ *
246
+ * Falls back to a link to `/cart` when no {@link CartSheetProvider} is above it, so the
247
+ * cart is always reachable — including with JS unavailable, where the anchor still
248
+ * navigates. The route renders the same CartView, so the fallback is the same cart, not
249
+ * a degraded one.
250
+ */
251
+ export declare function CartTrigger({ className, children, ...rest }: CartTriggerProps): JSX.Element;
252
+
253
+ export declare interface CartTriggerProps {
254
+ className?: string;
255
+ children: ReactNode;
256
+ 'aria-label'?: string;
257
+ }
258
+
259
+ /**
260
+ * The CTA needs no prop to work in both presentations: StickyCtaBar is
261
+ * `position: sticky; bottom: 0`, so on the page it pins to the viewport and inside the
262
+ * sheet it pins to the bottom of the sheet's scrolling body. Same component, same
263
+ * safe-area handling, no branch.
264
+ */
265
+ export declare function CartView(props?: CartViewProps): JSX.Element;
266
+
267
+ /**
268
+ * 4.4 Cart. Loads the session cart, supports optimistic + idempotent qty/remove edits
269
+ * (reverting on error), and hands off to checkout.
270
+ *
271
+ * There is no `withSession` wrapper here any more. Re-authentication moved down into the
272
+ * SDK's transport, which asks the host's `getToken()` once on a `401` and retries — so
273
+ * every component gets that behaviour without remembering to opt in, and a component
274
+ * that forgets can no longer quietly lose it.
275
+ */
276
+ export declare interface CartViewProps {
277
+ /**
278
+ * Dismiss the containing sheet. Supplied when the cart is presented as a
279
+ * BottomSheet; omitted on the `/cart` route, where there is nothing to dismiss.
280
+ */
281
+ onContinueShopping?: () => void;
282
+ /**
283
+ * Label for the primary CTA. Default `Proceed to checkout`.
284
+ *
285
+ * DAZN's checkout is the next step and DAZN name it; a host whose own wording is
286
+ * "Continue" or "Go to checkout" should not have to accept ours. The locked-cart label
287
+ * is deliberately NOT configurable — it states a condition rather than an action, and a
288
+ * host renaming it could imply the button still works.
289
+ */
290
+ checkoutLabel?: string;
291
+ }
292
+
293
+ /**
294
+ * Query params for the single-product, recommendations and category-page reads — market
295
+ * context only. A separate alias (rather than reusing {@link MarketQuery} directly) so
296
+ * these endpoints can gain their own params later without touching the shared object.
297
+ */
298
+ declare const CatalogReadQuery: z.ZodObject<{
299
+ country: z.ZodOptional<z.ZodString>;
300
+ lang: z.ZodOptional<z.ZodString>;
301
+ }, "strip", z.ZodTypeAny, {
302
+ country?: string | undefined;
303
+ lang?: string | undefined;
304
+ }, {
305
+ country?: string | undefined;
306
+ lang?: string | undefined;
307
+ }>;
308
+
309
+ declare type CatalogReadQuery = z.infer<typeof CatalogReadQuery>;
310
+
311
+ /**
312
+ * 4.2 Category (PCP) — the composed screen, given a page that is already fetched.
313
+ *
314
+ * This exists for the same reason `ProductDetail` does: the presentation belongs to the
315
+ * package, so that the fetching wrapper around it — `CategoryView` here, an RSC route in
316
+ * our own app — is only ever fetching. It was previously written out twice, once in each,
317
+ * and the two drifted: the SDK copy rendered a bare `div`/`section`/`ul` tree with none of
318
+ * the page layout, so a host mounting `CategoryView` got correctly styled blocks stacked
319
+ * with no column spacing, no page background, and a full-bleed one-column "grid". The
320
+ * blocks looked right individually, which is what made it read as a missing stylesheet
321
+ * rather than a missing wrapper.
322
+ *
323
+ * Not a client component: it renders no interactivity of its own, so it stays usable
324
+ * directly from a server component. The pieces that do need the client carry their own
325
+ * `'use client'`.
326
+ */
327
+ export declare function CategoryContent({ page }: CategoryContentProps): JSX.Element;
328
+
329
+ export declare interface CategoryContentProps {
330
+ /** An already-fetched category page. */
331
+ page: CategoryPage;
332
+ }
333
+
334
+ /**
335
+ * `GET /v1/catalog/categories/{code}/page` — the CMS-authored content for a category
336
+ * landing page (PCP). A page is a hero plus an **ordered list of typed blocks**, so
337
+ * merchandisers can compose editorial + product sections freely. Product blocks embed
338
+ * resolved {@link Product}s so the storefront renders without a second round-trip.
339
+ */
340
+ declare const CategoryHero: z.ZodObject<{
341
+ title: z.ZodString;
342
+ subtitle: z.ZodOptional<z.ZodString>;
343
+ image: z.ZodOptional<z.ZodString>;
344
+ }, "strip", z.ZodTypeAny, {
345
+ title: string;
346
+ image?: string | undefined;
347
+ subtitle?: string | undefined;
348
+ }, {
349
+ title: string;
350
+ image?: string | undefined;
351
+ subtitle?: string | undefined;
352
+ }>;
353
+
354
+ declare type CategoryHero = z.infer<typeof CategoryHero>;
355
+
356
+ declare const CategoryPage: z.ZodObject<{
357
+ code: z.ZodString;
358
+ hero: z.ZodObject<{
359
+ title: z.ZodString;
360
+ subtitle: z.ZodOptional<z.ZodString>;
361
+ image: z.ZodOptional<z.ZodString>;
362
+ }, "strip", z.ZodTypeAny, {
363
+ title: string;
364
+ image?: string | undefined;
365
+ subtitle?: string | undefined;
366
+ }, {
367
+ title: string;
368
+ image?: string | undefined;
369
+ subtitle?: string | undefined;
370
+ }>;
371
+ blocks: z.ZodArray<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
372
+ kind: z.ZodLiteral<"rail">;
373
+ title: z.ZodString;
374
+ products: z.ZodArray<z.ZodObject<{
375
+ sku: z.ZodString;
376
+ title: z.ZodString;
377
+ category: z.ZodString;
378
+ price: z.ZodObject<{
379
+ amount: z.ZodNumber;
380
+ currency: z.ZodString;
381
+ }, "strict", z.ZodTypeAny, {
382
+ amount: number;
383
+ currency: string;
384
+ }, {
385
+ amount: number;
386
+ currency: string;
387
+ }>;
388
+ compare_at: z.ZodOptional<z.ZodObject<{
389
+ amount: z.ZodNumber;
390
+ currency: z.ZodString;
391
+ }, "strict", z.ZodTypeAny, {
392
+ amount: number;
393
+ currency: string;
394
+ }, {
395
+ amount: number;
396
+ currency: string;
397
+ }>>;
398
+ media: z.ZodArray<z.ZodString, "many">;
399
+ variants: z.ZodArray<z.ZodObject<{
400
+ sku: z.ZodString;
401
+ attributes: z.ZodRecord<z.ZodString, z.ZodString>;
402
+ in_stock: z.ZodBoolean;
403
+ image: z.ZodOptional<z.ZodString>;
404
+ available: z.ZodOptional<z.ZodNumber>;
405
+ }, "strip", z.ZodTypeAny, {
406
+ sku: string;
407
+ attributes: Record<string, string>;
408
+ in_stock: boolean;
409
+ image?: string | undefined;
410
+ available?: number | undefined;
411
+ }, {
412
+ sku: string;
413
+ attributes: Record<string, string>;
414
+ in_stock: boolean;
415
+ image?: string | undefined;
416
+ available?: number | undefined;
417
+ }>, "many">;
418
+ description: z.ZodOptional<z.ZodString>;
419
+ badges: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
420
+ promo: z.ZodOptional<z.ZodObject<{
421
+ message: z.ZodString;
422
+ ends_at: z.ZodOptional<z.ZodString>;
423
+ }, "strip", z.ZodTypeAny, {
424
+ message: string;
425
+ ends_at?: string | undefined;
426
+ }, {
427
+ message: string;
428
+ ends_at?: string | undefined;
429
+ }>>;
430
+ size_guide: z.ZodOptional<z.ZodObject<{
431
+ title: z.ZodString;
432
+ columns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
433
+ rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>;
434
+ notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
435
+ }, "strip", z.ZodTypeAny, {
436
+ title: string;
437
+ columns?: string[] | undefined;
438
+ rows?: string[][] | undefined;
439
+ notes?: string[] | undefined;
440
+ }, {
441
+ title: string;
442
+ columns?: string[] | undefined;
443
+ rows?: string[][] | undefined;
444
+ notes?: string[] | undefined;
445
+ }>>;
446
+ }, "strip", z.ZodTypeAny, {
447
+ sku: string;
448
+ title: string;
449
+ category: string;
450
+ price: {
451
+ amount: number;
452
+ currency: string;
453
+ };
454
+ media: string[];
455
+ variants: {
456
+ sku: string;
457
+ attributes: Record<string, string>;
458
+ in_stock: boolean;
459
+ image?: string | undefined;
460
+ available?: number | undefined;
461
+ }[];
462
+ compare_at?: {
463
+ amount: number;
464
+ currency: string;
465
+ } | undefined;
466
+ description?: string | undefined;
467
+ badges?: string[] | undefined;
468
+ promo?: {
469
+ message: string;
470
+ ends_at?: string | undefined;
471
+ } | undefined;
472
+ size_guide?: {
473
+ title: string;
474
+ columns?: string[] | undefined;
475
+ rows?: string[][] | undefined;
476
+ notes?: string[] | undefined;
477
+ } | undefined;
478
+ }, {
479
+ sku: string;
480
+ title: string;
481
+ category: string;
482
+ price: {
483
+ amount: number;
484
+ currency: string;
485
+ };
486
+ media: string[];
487
+ variants: {
488
+ sku: string;
489
+ attributes: Record<string, string>;
490
+ in_stock: boolean;
491
+ image?: string | undefined;
492
+ available?: number | undefined;
493
+ }[];
494
+ compare_at?: {
495
+ amount: number;
496
+ currency: string;
497
+ } | undefined;
498
+ description?: string | undefined;
499
+ badges?: string[] | undefined;
500
+ promo?: {
501
+ message: string;
502
+ ends_at?: string | undefined;
503
+ } | undefined;
504
+ size_guide?: {
505
+ title: string;
506
+ columns?: string[] | undefined;
507
+ rows?: string[][] | undefined;
508
+ notes?: string[] | undefined;
509
+ } | undefined;
510
+ }>, "many">;
511
+ }, "strip", z.ZodTypeAny, {
512
+ title: string;
513
+ kind: "rail";
514
+ products: {
515
+ sku: string;
516
+ title: string;
517
+ category: string;
518
+ price: {
519
+ amount: number;
520
+ currency: string;
521
+ };
522
+ media: string[];
523
+ variants: {
524
+ sku: string;
525
+ attributes: Record<string, string>;
526
+ in_stock: boolean;
527
+ image?: string | undefined;
528
+ available?: number | undefined;
529
+ }[];
530
+ compare_at?: {
531
+ amount: number;
532
+ currency: string;
533
+ } | undefined;
534
+ description?: string | undefined;
535
+ badges?: string[] | undefined;
536
+ promo?: {
537
+ message: string;
538
+ ends_at?: string | undefined;
539
+ } | undefined;
540
+ size_guide?: {
541
+ title: string;
542
+ columns?: string[] | undefined;
543
+ rows?: string[][] | undefined;
544
+ notes?: string[] | undefined;
545
+ } | undefined;
546
+ }[];
547
+ }, {
548
+ title: string;
549
+ kind: "rail";
550
+ products: {
551
+ sku: string;
552
+ title: string;
553
+ category: string;
554
+ price: {
555
+ amount: number;
556
+ currency: string;
557
+ };
558
+ media: string[];
559
+ variants: {
560
+ sku: string;
561
+ attributes: Record<string, string>;
562
+ in_stock: boolean;
563
+ image?: string | undefined;
564
+ available?: number | undefined;
565
+ }[];
566
+ compare_at?: {
567
+ amount: number;
568
+ currency: string;
569
+ } | undefined;
570
+ description?: string | undefined;
571
+ badges?: string[] | undefined;
572
+ promo?: {
573
+ message: string;
574
+ ends_at?: string | undefined;
575
+ } | undefined;
576
+ size_guide?: {
577
+ title: string;
578
+ columns?: string[] | undefined;
579
+ rows?: string[][] | undefined;
580
+ notes?: string[] | undefined;
581
+ } | undefined;
582
+ }[];
583
+ }>, z.ZodObject<{
584
+ kind: z.ZodLiteral<"lifestyle_text">;
585
+ text: z.ZodString;
586
+ align: z.ZodDefault<z.ZodEnum<["center", "left"]>>;
587
+ }, "strip", z.ZodTypeAny, {
588
+ kind: "lifestyle_text";
589
+ text: string;
590
+ align: "center" | "left";
591
+ }, {
592
+ kind: "lifestyle_text";
593
+ text: string;
594
+ align?: "center" | "left" | undefined;
595
+ }>, z.ZodObject<{
596
+ kind: z.ZodLiteral<"featured">;
597
+ product: z.ZodObject<{
598
+ sku: z.ZodString;
599
+ title: z.ZodString;
600
+ category: z.ZodString;
601
+ price: z.ZodObject<{
602
+ amount: z.ZodNumber;
603
+ currency: z.ZodString;
604
+ }, "strict", z.ZodTypeAny, {
605
+ amount: number;
606
+ currency: string;
607
+ }, {
608
+ amount: number;
609
+ currency: string;
610
+ }>;
611
+ compare_at: z.ZodOptional<z.ZodObject<{
612
+ amount: z.ZodNumber;
613
+ currency: z.ZodString;
614
+ }, "strict", z.ZodTypeAny, {
615
+ amount: number;
616
+ currency: string;
617
+ }, {
618
+ amount: number;
619
+ currency: string;
620
+ }>>;
621
+ media: z.ZodArray<z.ZodString, "many">;
622
+ variants: z.ZodArray<z.ZodObject<{
623
+ sku: z.ZodString;
624
+ attributes: z.ZodRecord<z.ZodString, z.ZodString>;
625
+ in_stock: z.ZodBoolean;
626
+ image: z.ZodOptional<z.ZodString>;
627
+ available: z.ZodOptional<z.ZodNumber>;
628
+ }, "strip", z.ZodTypeAny, {
629
+ sku: string;
630
+ attributes: Record<string, string>;
631
+ in_stock: boolean;
632
+ image?: string | undefined;
633
+ available?: number | undefined;
634
+ }, {
635
+ sku: string;
636
+ attributes: Record<string, string>;
637
+ in_stock: boolean;
638
+ image?: string | undefined;
639
+ available?: number | undefined;
640
+ }>, "many">;
641
+ description: z.ZodOptional<z.ZodString>;
642
+ badges: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
643
+ promo: z.ZodOptional<z.ZodObject<{
644
+ message: z.ZodString;
645
+ ends_at: z.ZodOptional<z.ZodString>;
646
+ }, "strip", z.ZodTypeAny, {
647
+ message: string;
648
+ ends_at?: string | undefined;
649
+ }, {
650
+ message: string;
651
+ ends_at?: string | undefined;
652
+ }>>;
653
+ size_guide: z.ZodOptional<z.ZodObject<{
654
+ title: z.ZodString;
655
+ columns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
656
+ rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>;
657
+ notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
658
+ }, "strip", z.ZodTypeAny, {
659
+ title: string;
660
+ columns?: string[] | undefined;
661
+ rows?: string[][] | undefined;
662
+ notes?: string[] | undefined;
663
+ }, {
664
+ title: string;
665
+ columns?: string[] | undefined;
666
+ rows?: string[][] | undefined;
667
+ notes?: string[] | undefined;
668
+ }>>;
669
+ }, "strip", z.ZodTypeAny, {
670
+ sku: string;
671
+ title: string;
672
+ category: string;
673
+ price: {
674
+ amount: number;
675
+ currency: string;
676
+ };
677
+ media: string[];
678
+ variants: {
679
+ sku: string;
680
+ attributes: Record<string, string>;
681
+ in_stock: boolean;
682
+ image?: string | undefined;
683
+ available?: number | undefined;
684
+ }[];
685
+ compare_at?: {
686
+ amount: number;
687
+ currency: string;
688
+ } | undefined;
689
+ description?: string | undefined;
690
+ badges?: string[] | undefined;
691
+ promo?: {
692
+ message: string;
693
+ ends_at?: string | undefined;
694
+ } | undefined;
695
+ size_guide?: {
696
+ title: string;
697
+ columns?: string[] | undefined;
698
+ rows?: string[][] | undefined;
699
+ notes?: string[] | undefined;
700
+ } | undefined;
701
+ }, {
702
+ sku: string;
703
+ title: string;
704
+ category: string;
705
+ price: {
706
+ amount: number;
707
+ currency: string;
708
+ };
709
+ media: string[];
710
+ variants: {
711
+ sku: string;
712
+ attributes: Record<string, string>;
713
+ in_stock: boolean;
714
+ image?: string | undefined;
715
+ available?: number | undefined;
716
+ }[];
717
+ compare_at?: {
718
+ amount: number;
719
+ currency: string;
720
+ } | undefined;
721
+ description?: string | undefined;
722
+ badges?: string[] | undefined;
723
+ promo?: {
724
+ message: string;
725
+ ends_at?: string | undefined;
726
+ } | undefined;
727
+ size_guide?: {
728
+ title: string;
729
+ columns?: string[] | undefined;
730
+ rows?: string[][] | undefined;
731
+ notes?: string[] | undefined;
732
+ } | undefined;
733
+ }>;
734
+ }, "strip", z.ZodTypeAny, {
735
+ kind: "featured";
736
+ product: {
737
+ sku: string;
738
+ title: string;
739
+ category: string;
740
+ price: {
741
+ amount: number;
742
+ currency: string;
743
+ };
744
+ media: string[];
745
+ variants: {
746
+ sku: string;
747
+ attributes: Record<string, string>;
748
+ in_stock: boolean;
749
+ image?: string | undefined;
750
+ available?: number | undefined;
751
+ }[];
752
+ compare_at?: {
753
+ amount: number;
754
+ currency: string;
755
+ } | undefined;
756
+ description?: string | undefined;
757
+ badges?: string[] | undefined;
758
+ promo?: {
759
+ message: string;
760
+ ends_at?: string | undefined;
761
+ } | undefined;
762
+ size_guide?: {
763
+ title: string;
764
+ columns?: string[] | undefined;
765
+ rows?: string[][] | undefined;
766
+ notes?: string[] | undefined;
767
+ } | undefined;
768
+ };
769
+ }, {
770
+ kind: "featured";
771
+ product: {
772
+ sku: string;
773
+ title: string;
774
+ category: string;
775
+ price: {
776
+ amount: number;
777
+ currency: string;
778
+ };
779
+ media: string[];
780
+ variants: {
781
+ sku: string;
782
+ attributes: Record<string, string>;
783
+ in_stock: boolean;
784
+ image?: string | undefined;
785
+ available?: number | undefined;
786
+ }[];
787
+ compare_at?: {
788
+ amount: number;
789
+ currency: string;
790
+ } | undefined;
791
+ description?: string | undefined;
792
+ badges?: string[] | undefined;
793
+ promo?: {
794
+ message: string;
795
+ ends_at?: string | undefined;
796
+ } | undefined;
797
+ size_guide?: {
798
+ title: string;
799
+ columns?: string[] | undefined;
800
+ rows?: string[][] | undefined;
801
+ notes?: string[] | undefined;
802
+ } | undefined;
803
+ };
804
+ }>, z.ZodObject<{
805
+ kind: z.ZodLiteral<"lifestyle_image">;
806
+ images: z.ZodArray<z.ZodString, "many">;
807
+ }, "strip", z.ZodTypeAny, {
808
+ kind: "lifestyle_image";
809
+ images: string[];
810
+ }, {
811
+ kind: "lifestyle_image";
812
+ images: string[];
813
+ }>, z.ZodObject<{
814
+ kind: z.ZodLiteral<"grid">;
815
+ title: z.ZodString;
816
+ products: z.ZodArray<z.ZodObject<{
817
+ sku: z.ZodString;
818
+ title: z.ZodString;
819
+ category: z.ZodString;
820
+ price: z.ZodObject<{
821
+ amount: z.ZodNumber;
822
+ currency: z.ZodString;
823
+ }, "strict", z.ZodTypeAny, {
824
+ amount: number;
825
+ currency: string;
826
+ }, {
827
+ amount: number;
828
+ currency: string;
829
+ }>;
830
+ compare_at: z.ZodOptional<z.ZodObject<{
831
+ amount: z.ZodNumber;
832
+ currency: z.ZodString;
833
+ }, "strict", z.ZodTypeAny, {
834
+ amount: number;
835
+ currency: string;
836
+ }, {
837
+ amount: number;
838
+ currency: string;
839
+ }>>;
840
+ media: z.ZodArray<z.ZodString, "many">;
841
+ variants: z.ZodArray<z.ZodObject<{
842
+ sku: z.ZodString;
843
+ attributes: z.ZodRecord<z.ZodString, z.ZodString>;
844
+ in_stock: z.ZodBoolean;
845
+ image: z.ZodOptional<z.ZodString>;
846
+ available: z.ZodOptional<z.ZodNumber>;
847
+ }, "strip", z.ZodTypeAny, {
848
+ sku: string;
849
+ attributes: Record<string, string>;
850
+ in_stock: boolean;
851
+ image?: string | undefined;
852
+ available?: number | undefined;
853
+ }, {
854
+ sku: string;
855
+ attributes: Record<string, string>;
856
+ in_stock: boolean;
857
+ image?: string | undefined;
858
+ available?: number | undefined;
859
+ }>, "many">;
860
+ description: z.ZodOptional<z.ZodString>;
861
+ badges: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
862
+ promo: z.ZodOptional<z.ZodObject<{
863
+ message: z.ZodString;
864
+ ends_at: z.ZodOptional<z.ZodString>;
865
+ }, "strip", z.ZodTypeAny, {
866
+ message: string;
867
+ ends_at?: string | undefined;
868
+ }, {
869
+ message: string;
870
+ ends_at?: string | undefined;
871
+ }>>;
872
+ size_guide: z.ZodOptional<z.ZodObject<{
873
+ title: z.ZodString;
874
+ columns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
875
+ rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>;
876
+ notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
877
+ }, "strip", z.ZodTypeAny, {
878
+ title: string;
879
+ columns?: string[] | undefined;
880
+ rows?: string[][] | undefined;
881
+ notes?: string[] | undefined;
882
+ }, {
883
+ title: string;
884
+ columns?: string[] | undefined;
885
+ rows?: string[][] | undefined;
886
+ notes?: string[] | undefined;
887
+ }>>;
888
+ }, "strip", z.ZodTypeAny, {
889
+ sku: string;
890
+ title: string;
891
+ category: string;
892
+ price: {
893
+ amount: number;
894
+ currency: string;
895
+ };
896
+ media: string[];
897
+ variants: {
898
+ sku: string;
899
+ attributes: Record<string, string>;
900
+ in_stock: boolean;
901
+ image?: string | undefined;
902
+ available?: number | undefined;
903
+ }[];
904
+ compare_at?: {
905
+ amount: number;
906
+ currency: string;
907
+ } | undefined;
908
+ description?: string | undefined;
909
+ badges?: string[] | undefined;
910
+ promo?: {
911
+ message: string;
912
+ ends_at?: string | undefined;
913
+ } | undefined;
914
+ size_guide?: {
915
+ title: string;
916
+ columns?: string[] | undefined;
917
+ rows?: string[][] | undefined;
918
+ notes?: string[] | undefined;
919
+ } | undefined;
920
+ }, {
921
+ sku: string;
922
+ title: string;
923
+ category: string;
924
+ price: {
925
+ amount: number;
926
+ currency: string;
927
+ };
928
+ media: string[];
929
+ variants: {
930
+ sku: string;
931
+ attributes: Record<string, string>;
932
+ in_stock: boolean;
933
+ image?: string | undefined;
934
+ available?: number | undefined;
935
+ }[];
936
+ compare_at?: {
937
+ amount: number;
938
+ currency: string;
939
+ } | undefined;
940
+ description?: string | undefined;
941
+ badges?: string[] | undefined;
942
+ promo?: {
943
+ message: string;
944
+ ends_at?: string | undefined;
945
+ } | undefined;
946
+ size_guide?: {
947
+ title: string;
948
+ columns?: string[] | undefined;
949
+ rows?: string[][] | undefined;
950
+ notes?: string[] | undefined;
951
+ } | undefined;
952
+ }>, "many">;
953
+ }, "strip", z.ZodTypeAny, {
954
+ title: string;
955
+ kind: "grid";
956
+ products: {
957
+ sku: string;
958
+ title: string;
959
+ category: string;
960
+ price: {
961
+ amount: number;
962
+ currency: string;
963
+ };
964
+ media: string[];
965
+ variants: {
966
+ sku: string;
967
+ attributes: Record<string, string>;
968
+ in_stock: boolean;
969
+ image?: string | undefined;
970
+ available?: number | undefined;
971
+ }[];
972
+ compare_at?: {
973
+ amount: number;
974
+ currency: string;
975
+ } | undefined;
976
+ description?: string | undefined;
977
+ badges?: string[] | undefined;
978
+ promo?: {
979
+ message: string;
980
+ ends_at?: string | undefined;
981
+ } | undefined;
982
+ size_guide?: {
983
+ title: string;
984
+ columns?: string[] | undefined;
985
+ rows?: string[][] | undefined;
986
+ notes?: string[] | undefined;
987
+ } | undefined;
988
+ }[];
989
+ }, {
990
+ title: string;
991
+ kind: "grid";
992
+ products: {
993
+ sku: string;
994
+ title: string;
995
+ category: string;
996
+ price: {
997
+ amount: number;
998
+ currency: string;
999
+ };
1000
+ media: string[];
1001
+ variants: {
1002
+ sku: string;
1003
+ attributes: Record<string, string>;
1004
+ in_stock: boolean;
1005
+ image?: string | undefined;
1006
+ available?: number | undefined;
1007
+ }[];
1008
+ compare_at?: {
1009
+ amount: number;
1010
+ currency: string;
1011
+ } | undefined;
1012
+ description?: string | undefined;
1013
+ badges?: string[] | undefined;
1014
+ promo?: {
1015
+ message: string;
1016
+ ends_at?: string | undefined;
1017
+ } | undefined;
1018
+ size_guide?: {
1019
+ title: string;
1020
+ columns?: string[] | undefined;
1021
+ rows?: string[][] | undefined;
1022
+ notes?: string[] | undefined;
1023
+ } | undefined;
1024
+ }[];
1025
+ }>, z.ZodObject<{
1026
+ kind: z.ZodLiteral<"review">;
1027
+ quotes: z.ZodArray<z.ZodObject<{
1028
+ quote: z.ZodString;
1029
+ author: z.ZodString;
1030
+ }, "strip", z.ZodTypeAny, {
1031
+ quote: string;
1032
+ author: string;
1033
+ }, {
1034
+ quote: string;
1035
+ author: string;
1036
+ }>, "many">;
1037
+ }, "strip", z.ZodTypeAny, {
1038
+ kind: "review";
1039
+ quotes: {
1040
+ quote: string;
1041
+ author: string;
1042
+ }[];
1043
+ }, {
1044
+ kind: "review";
1045
+ quotes: {
1046
+ quote: string;
1047
+ author: string;
1048
+ }[];
1049
+ }>, z.ZodObject<{
1050
+ kind: z.ZodLiteral<"why_us">;
1051
+ items: z.ZodArray<z.ZodObject<{
1052
+ icon: z.ZodOptional<z.ZodEnum<["payment", "delivery", "returns"]>>;
1053
+ title: z.ZodString;
1054
+ body: z.ZodString;
1055
+ }, "strip", z.ZodTypeAny, {
1056
+ title: string;
1057
+ body: string;
1058
+ icon?: "payment" | "delivery" | "returns" | undefined;
1059
+ }, {
1060
+ title: string;
1061
+ body: string;
1062
+ icon?: "payment" | "delivery" | "returns" | undefined;
1063
+ }>, "many">;
1064
+ }, "strip", z.ZodTypeAny, {
1065
+ items: {
1066
+ title: string;
1067
+ body: string;
1068
+ icon?: "payment" | "delivery" | "returns" | undefined;
1069
+ }[];
1070
+ kind: "why_us";
1071
+ }, {
1072
+ items: {
1073
+ title: string;
1074
+ body: string;
1075
+ icon?: "payment" | "delivery" | "returns" | undefined;
1076
+ }[];
1077
+ kind: "why_us";
1078
+ }>, z.ZodObject<{
1079
+ kind: z.ZodLiteral<"explore">;
1080
+ title: z.ZodString;
1081
+ links: z.ZodArray<z.ZodObject<{
1082
+ label: z.ZodString;
1083
+ code: z.ZodString;
1084
+ }, "strip", z.ZodTypeAny, {
1085
+ code: string;
1086
+ label: string;
1087
+ }, {
1088
+ code: string;
1089
+ label: string;
1090
+ }>, "many">;
1091
+ }, "strip", z.ZodTypeAny, {
1092
+ title: string;
1093
+ kind: "explore";
1094
+ links: {
1095
+ code: string;
1096
+ label: string;
1097
+ }[];
1098
+ }, {
1099
+ title: string;
1100
+ kind: "explore";
1101
+ links: {
1102
+ code: string;
1103
+ label: string;
1104
+ }[];
1105
+ }>]>, "many">;
1106
+ }, "strip", z.ZodTypeAny, {
1107
+ code: string;
1108
+ hero: {
1109
+ title: string;
1110
+ image?: string | undefined;
1111
+ subtitle?: string | undefined;
1112
+ };
1113
+ blocks: ({
1114
+ title: string;
1115
+ kind: "rail";
1116
+ products: {
1117
+ sku: string;
1118
+ title: string;
1119
+ category: string;
1120
+ price: {
1121
+ amount: number;
1122
+ currency: string;
1123
+ };
1124
+ media: string[];
1125
+ variants: {
1126
+ sku: string;
1127
+ attributes: Record<string, string>;
1128
+ in_stock: boolean;
1129
+ image?: string | undefined;
1130
+ available?: number | undefined;
1131
+ }[];
1132
+ compare_at?: {
1133
+ amount: number;
1134
+ currency: string;
1135
+ } | undefined;
1136
+ description?: string | undefined;
1137
+ badges?: string[] | undefined;
1138
+ promo?: {
1139
+ message: string;
1140
+ ends_at?: string | undefined;
1141
+ } | undefined;
1142
+ size_guide?: {
1143
+ title: string;
1144
+ columns?: string[] | undefined;
1145
+ rows?: string[][] | undefined;
1146
+ notes?: string[] | undefined;
1147
+ } | undefined;
1148
+ }[];
1149
+ } | {
1150
+ kind: "lifestyle_text";
1151
+ text: string;
1152
+ align: "center" | "left";
1153
+ } | {
1154
+ kind: "featured";
1155
+ product: {
1156
+ sku: string;
1157
+ title: string;
1158
+ category: string;
1159
+ price: {
1160
+ amount: number;
1161
+ currency: string;
1162
+ };
1163
+ media: string[];
1164
+ variants: {
1165
+ sku: string;
1166
+ attributes: Record<string, string>;
1167
+ in_stock: boolean;
1168
+ image?: string | undefined;
1169
+ available?: number | undefined;
1170
+ }[];
1171
+ compare_at?: {
1172
+ amount: number;
1173
+ currency: string;
1174
+ } | undefined;
1175
+ description?: string | undefined;
1176
+ badges?: string[] | undefined;
1177
+ promo?: {
1178
+ message: string;
1179
+ ends_at?: string | undefined;
1180
+ } | undefined;
1181
+ size_guide?: {
1182
+ title: string;
1183
+ columns?: string[] | undefined;
1184
+ rows?: string[][] | undefined;
1185
+ notes?: string[] | undefined;
1186
+ } | undefined;
1187
+ };
1188
+ } | {
1189
+ kind: "lifestyle_image";
1190
+ images: string[];
1191
+ } | {
1192
+ title: string;
1193
+ kind: "grid";
1194
+ products: {
1195
+ sku: string;
1196
+ title: string;
1197
+ category: string;
1198
+ price: {
1199
+ amount: number;
1200
+ currency: string;
1201
+ };
1202
+ media: string[];
1203
+ variants: {
1204
+ sku: string;
1205
+ attributes: Record<string, string>;
1206
+ in_stock: boolean;
1207
+ image?: string | undefined;
1208
+ available?: number | undefined;
1209
+ }[];
1210
+ compare_at?: {
1211
+ amount: number;
1212
+ currency: string;
1213
+ } | undefined;
1214
+ description?: string | undefined;
1215
+ badges?: string[] | undefined;
1216
+ promo?: {
1217
+ message: string;
1218
+ ends_at?: string | undefined;
1219
+ } | undefined;
1220
+ size_guide?: {
1221
+ title: string;
1222
+ columns?: string[] | undefined;
1223
+ rows?: string[][] | undefined;
1224
+ notes?: string[] | undefined;
1225
+ } | undefined;
1226
+ }[];
1227
+ } | {
1228
+ kind: "review";
1229
+ quotes: {
1230
+ quote: string;
1231
+ author: string;
1232
+ }[];
1233
+ } | {
1234
+ items: {
1235
+ title: string;
1236
+ body: string;
1237
+ icon?: "payment" | "delivery" | "returns" | undefined;
1238
+ }[];
1239
+ kind: "why_us";
1240
+ } | {
1241
+ title: string;
1242
+ kind: "explore";
1243
+ links: {
1244
+ code: string;
1245
+ label: string;
1246
+ }[];
1247
+ })[];
1248
+ }, {
1249
+ code: string;
1250
+ hero: {
1251
+ title: string;
1252
+ image?: string | undefined;
1253
+ subtitle?: string | undefined;
1254
+ };
1255
+ blocks: ({
1256
+ title: string;
1257
+ kind: "rail";
1258
+ products: {
1259
+ sku: string;
1260
+ title: string;
1261
+ category: string;
1262
+ price: {
1263
+ amount: number;
1264
+ currency: string;
1265
+ };
1266
+ media: string[];
1267
+ variants: {
1268
+ sku: string;
1269
+ attributes: Record<string, string>;
1270
+ in_stock: boolean;
1271
+ image?: string | undefined;
1272
+ available?: number | undefined;
1273
+ }[];
1274
+ compare_at?: {
1275
+ amount: number;
1276
+ currency: string;
1277
+ } | undefined;
1278
+ description?: string | undefined;
1279
+ badges?: string[] | undefined;
1280
+ promo?: {
1281
+ message: string;
1282
+ ends_at?: string | undefined;
1283
+ } | undefined;
1284
+ size_guide?: {
1285
+ title: string;
1286
+ columns?: string[] | undefined;
1287
+ rows?: string[][] | undefined;
1288
+ notes?: string[] | undefined;
1289
+ } | undefined;
1290
+ }[];
1291
+ } | {
1292
+ kind: "lifestyle_text";
1293
+ text: string;
1294
+ align?: "center" | "left" | undefined;
1295
+ } | {
1296
+ kind: "featured";
1297
+ product: {
1298
+ sku: string;
1299
+ title: string;
1300
+ category: string;
1301
+ price: {
1302
+ amount: number;
1303
+ currency: string;
1304
+ };
1305
+ media: string[];
1306
+ variants: {
1307
+ sku: string;
1308
+ attributes: Record<string, string>;
1309
+ in_stock: boolean;
1310
+ image?: string | undefined;
1311
+ available?: number | undefined;
1312
+ }[];
1313
+ compare_at?: {
1314
+ amount: number;
1315
+ currency: string;
1316
+ } | undefined;
1317
+ description?: string | undefined;
1318
+ badges?: string[] | undefined;
1319
+ promo?: {
1320
+ message: string;
1321
+ ends_at?: string | undefined;
1322
+ } | undefined;
1323
+ size_guide?: {
1324
+ title: string;
1325
+ columns?: string[] | undefined;
1326
+ rows?: string[][] | undefined;
1327
+ notes?: string[] | undefined;
1328
+ } | undefined;
1329
+ };
1330
+ } | {
1331
+ kind: "lifestyle_image";
1332
+ images: string[];
1333
+ } | {
1334
+ title: string;
1335
+ kind: "grid";
1336
+ products: {
1337
+ sku: string;
1338
+ title: string;
1339
+ category: string;
1340
+ price: {
1341
+ amount: number;
1342
+ currency: string;
1343
+ };
1344
+ media: string[];
1345
+ variants: {
1346
+ sku: string;
1347
+ attributes: Record<string, string>;
1348
+ in_stock: boolean;
1349
+ image?: string | undefined;
1350
+ available?: number | undefined;
1351
+ }[];
1352
+ compare_at?: {
1353
+ amount: number;
1354
+ currency: string;
1355
+ } | undefined;
1356
+ description?: string | undefined;
1357
+ badges?: string[] | undefined;
1358
+ promo?: {
1359
+ message: string;
1360
+ ends_at?: string | undefined;
1361
+ } | undefined;
1362
+ size_guide?: {
1363
+ title: string;
1364
+ columns?: string[] | undefined;
1365
+ rows?: string[][] | undefined;
1366
+ notes?: string[] | undefined;
1367
+ } | undefined;
1368
+ }[];
1369
+ } | {
1370
+ kind: "review";
1371
+ quotes: {
1372
+ quote: string;
1373
+ author: string;
1374
+ }[];
1375
+ } | {
1376
+ items: {
1377
+ title: string;
1378
+ body: string;
1379
+ icon?: "payment" | "delivery" | "returns" | undefined;
1380
+ }[];
1381
+ kind: "why_us";
1382
+ } | {
1383
+ title: string;
1384
+ kind: "explore";
1385
+ links: {
1386
+ code: string;
1387
+ label: string;
1388
+ }[];
1389
+ })[];
1390
+ }>;
1391
+
1392
+ declare type CategoryPage = z.infer<typeof CategoryPage>;
1393
+
1394
+ /**
1395
+ * A whole category screen, data fetching included — a **level 1 view**.
1396
+ *
1397
+ * This is the smallest integration surface a host can take: give it a code, it renders,
1398
+ * and it owns its own loading, empty and error states. The screen itself is
1399
+ * {@link CategoryContent}; everything it is built from (`RailCarousel`, `ProductTile`, …)
1400
+ * stays exported for a host that would rather compose and fetch it themselves.
1401
+ *
1402
+ * Fetching happens on mount, client-side, through the provider's client — so it inherits
1403
+ * the host's session and market without being told about either.
1404
+ */
1405
+ export declare function CategoryView({ code, initialData, onNotFound }: CategoryViewProps): JSX.Element;
1406
+
1407
+ export declare interface CategoryViewProps {
1408
+ /** Category code — the leaf, e.g. `hoodies`. */
1409
+ code: string;
1410
+ /**
1411
+ * An already-fetched page, from a host that server-rendered this screen. Painted
1412
+ * synchronously on the first frame — no skeleton, no waterfall — and revalidated in the
1413
+ * background, because a server-rendered page can be arbitrarily old by the time it is
1414
+ * interactive and price and availability are the two things worst served by being stale.
1415
+ */
1416
+ initialData?: CategoryPage;
1417
+ /**
1418
+ * Replaces the built-in empty state when the category does not exist, so a host can
1419
+ * render their own 404 in their own chrome. Only for `category_not_found`: a genuine
1420
+ * fault still renders the recoverable error state, because inviting a retry is right
1421
+ * there and wrong for a category that is gone.
1422
+ */
1423
+ onNotFound?: () => ReactNode;
1424
+ }
1425
+
1426
+ declare interface ClientOptions {
1427
+ /** The `/v1` base URL, e.g. `https://storefront.example.com/v1`. */
1428
+ baseUrl: string;
1429
+ /** Override the fetch implementation (defaults to the global `fetch`). */
1430
+ fetch?: typeof fetch;
1431
+ /** Supplies the session bearer token, if not relying solely on the session cookie. */
1432
+ getToken?: () => string | undefined | Promise<string | undefined>;
1433
+ /** Extra headers applied to every request. */
1434
+ defaultHeaders?: Record<string, string>;
1435
+ /** Credentials mode (browser); defaults to `include` so the session cookie is sent. */
1436
+ credentials?: RequestCredentials;
1437
+ }
1438
+
1439
+ /** Extract the API error `code` from a thrown value, if present. */
1440
+ export declare function codeOf(err: unknown): ErrorCode | undefined;
1441
+
1442
+ /**
1443
+ * PDP colour selector (Figma `mobile/pdp/color`): a "Colour: <name>" header and a row of
1444
+ * 79×105 image tiles. The selected tile gets a dark border; sold-out colours are disabled
1445
+ * with an overlay. A real radiogroup for keyboard + screen-reader support.
1446
+ */
1447
+ export declare function ColorSelector({ colours, selected, onSelect }: ColorSelectorProps): JSX.Element;
1448
+
1449
+ export declare interface ColorSelectorProps {
1450
+ colours: ColourOption[];
1451
+ /** Selected colour value, or null when none is chosen yet. */
1452
+ selected: string | null;
1453
+ onSelect: (value: string) => void;
1454
+ }
1455
+
1456
+ /** The attribute key a product uses for colour, if any (`colour` or `color`). */
1457
+ export declare function colourKeyOf(variants: ProductVariant[]): string | null;
1458
+
1459
+ export declare interface ColourOption {
1460
+ value: string;
1461
+ available: boolean;
1462
+ image?: string;
1463
+ /** Remaining stock for this colour (summed across in-stock sizes); drives "N left". */
1464
+ remaining?: number;
1465
+ }
1466
+
1467
+ /**
1468
+ * Distinct colours with a representative image and remaining stock. `available` and
1469
+ * `remaining` are filtered to the given size when one is selected.
1470
+ */
1471
+ export declare function colourOptions(variants: ProductVariant[], size?: string | null): ColourOption[];
1472
+
1473
+ /**
1474
+ * A typed, schema-validating client for the `/v1` API. Every response is parsed with
1475
+ * its contract schema; a non-2xx response throws a {@link DaznApiError}.
1476
+ */
1477
+ declare function createClient(options: ClientOptions): {
1478
+ catalog: {
1479
+ listProducts: (query?: ListProductsQuery) => Promise<{
1480
+ data: {
1481
+ sku: string;
1482
+ title: string;
1483
+ category: string;
1484
+ price: {
1485
+ amount: number;
1486
+ currency: string;
1487
+ };
1488
+ media: string[];
1489
+ variants: {
1490
+ sku: string;
1491
+ attributes: Record<string, string>;
1492
+ in_stock: boolean;
1493
+ image?: string | undefined;
1494
+ available?: number | undefined;
1495
+ }[];
1496
+ compare_at?: {
1497
+ amount: number;
1498
+ currency: string;
1499
+ } | undefined;
1500
+ description?: string | undefined;
1501
+ badges?: string[] | undefined;
1502
+ promo?: {
1503
+ message: string;
1504
+ ends_at?: string | undefined;
1505
+ } | undefined;
1506
+ size_guide?: {
1507
+ title: string;
1508
+ columns?: string[] | undefined;
1509
+ rows?: string[][] | undefined;
1510
+ notes?: string[] | undefined;
1511
+ } | undefined;
1512
+ }[];
1513
+ page: {
1514
+ cursor: string | null;
1515
+ has_more: boolean;
1516
+ };
1517
+ }>;
1518
+ getProduct: (sku: string, query?: CatalogReadQuery) => Promise<{
1519
+ sku: string;
1520
+ title: string;
1521
+ category: string;
1522
+ price: {
1523
+ amount: number;
1524
+ currency: string;
1525
+ };
1526
+ media: string[];
1527
+ variants: {
1528
+ sku: string;
1529
+ attributes: Record<string, string>;
1530
+ in_stock: boolean;
1531
+ image?: string | undefined;
1532
+ available?: number | undefined;
1533
+ }[];
1534
+ compare_at?: {
1535
+ amount: number;
1536
+ currency: string;
1537
+ } | undefined;
1538
+ description?: string | undefined;
1539
+ badges?: string[] | undefined;
1540
+ promo?: {
1541
+ message: string;
1542
+ ends_at?: string | undefined;
1543
+ } | undefined;
1544
+ size_guide?: {
1545
+ title: string;
1546
+ columns?: string[] | undefined;
1547
+ rows?: string[][] | undefined;
1548
+ notes?: string[] | undefined;
1549
+ } | undefined;
1550
+ }>;
1551
+ getRecommendations: (sku: string, query?: CatalogReadQuery) => Promise<{
1552
+ data: {
1553
+ sku: string;
1554
+ title: string;
1555
+ category: string;
1556
+ price: {
1557
+ amount: number;
1558
+ currency: string;
1559
+ };
1560
+ media: string[];
1561
+ variants: {
1562
+ sku: string;
1563
+ attributes: Record<string, string>;
1564
+ in_stock: boolean;
1565
+ image?: string | undefined;
1566
+ available?: number | undefined;
1567
+ }[];
1568
+ compare_at?: {
1569
+ amount: number;
1570
+ currency: string;
1571
+ } | undefined;
1572
+ description?: string | undefined;
1573
+ badges?: string[] | undefined;
1574
+ promo?: {
1575
+ message: string;
1576
+ ends_at?: string | undefined;
1577
+ } | undefined;
1578
+ size_guide?: {
1579
+ title: string;
1580
+ columns?: string[] | undefined;
1581
+ rows?: string[][] | undefined;
1582
+ notes?: string[] | undefined;
1583
+ } | undefined;
1584
+ }[];
1585
+ }>;
1586
+ /**
1587
+ * The code travels as a QUERY parameter, not a path segment.
1588
+ *
1589
+ * Category codes contain a slash, which in a path segment must be sent as `%2F` —
1590
+ * and Apache rejects that by default (`AllowEncodedSlashes Off`), returning its own
1591
+ * HTML 404 before the request reaches the API at all. Every real category code has
1592
+ * a slash in it, so the path form served none of them.
1593
+ *
1594
+ * The engine still exposes the path form for slash-free codes; this client does not
1595
+ * use it, because a query parameter works regardless of how any proxy in the chain
1596
+ * is configured.
1597
+ */
1598
+ getCategoryPage: (code: string, query?: CatalogReadQuery) => Promise<{
1599
+ code: string;
1600
+ hero: {
1601
+ title: string;
1602
+ image?: string | undefined;
1603
+ subtitle?: string | undefined;
1604
+ };
1605
+ blocks: ({
1606
+ title: string;
1607
+ kind: "rail";
1608
+ products: {
1609
+ sku: string;
1610
+ title: string;
1611
+ category: string;
1612
+ price: {
1613
+ amount: number;
1614
+ currency: string;
1615
+ };
1616
+ media: string[];
1617
+ variants: {
1618
+ sku: string;
1619
+ attributes: Record<string, string>;
1620
+ in_stock: boolean;
1621
+ image?: string | undefined;
1622
+ available?: number | undefined;
1623
+ }[];
1624
+ compare_at?: {
1625
+ amount: number;
1626
+ currency: string;
1627
+ } | undefined;
1628
+ description?: string | undefined;
1629
+ badges?: string[] | undefined;
1630
+ promo?: {
1631
+ message: string;
1632
+ ends_at?: string | undefined;
1633
+ } | undefined;
1634
+ size_guide?: {
1635
+ title: string;
1636
+ columns?: string[] | undefined;
1637
+ rows?: string[][] | undefined;
1638
+ notes?: string[] | undefined;
1639
+ } | undefined;
1640
+ }[];
1641
+ } | {
1642
+ kind: "lifestyle_text";
1643
+ text: string;
1644
+ align?: "center" | "left" | undefined;
1645
+ } | {
1646
+ kind: "featured";
1647
+ product: {
1648
+ sku: string;
1649
+ title: string;
1650
+ category: string;
1651
+ price: {
1652
+ amount: number;
1653
+ currency: string;
1654
+ };
1655
+ media: string[];
1656
+ variants: {
1657
+ sku: string;
1658
+ attributes: Record<string, string>;
1659
+ in_stock: boolean;
1660
+ image?: string | undefined;
1661
+ available?: number | undefined;
1662
+ }[];
1663
+ compare_at?: {
1664
+ amount: number;
1665
+ currency: string;
1666
+ } | undefined;
1667
+ description?: string | undefined;
1668
+ badges?: string[] | undefined;
1669
+ promo?: {
1670
+ message: string;
1671
+ ends_at?: string | undefined;
1672
+ } | undefined;
1673
+ size_guide?: {
1674
+ title: string;
1675
+ columns?: string[] | undefined;
1676
+ rows?: string[][] | undefined;
1677
+ notes?: string[] | undefined;
1678
+ } | undefined;
1679
+ };
1680
+ } | {
1681
+ kind: "lifestyle_image";
1682
+ images: string[];
1683
+ } | {
1684
+ title: string;
1685
+ kind: "grid";
1686
+ products: {
1687
+ sku: string;
1688
+ title: string;
1689
+ category: string;
1690
+ price: {
1691
+ amount: number;
1692
+ currency: string;
1693
+ };
1694
+ media: string[];
1695
+ variants: {
1696
+ sku: string;
1697
+ attributes: Record<string, string>;
1698
+ in_stock: boolean;
1699
+ image?: string | undefined;
1700
+ available?: number | undefined;
1701
+ }[];
1702
+ compare_at?: {
1703
+ amount: number;
1704
+ currency: string;
1705
+ } | undefined;
1706
+ description?: string | undefined;
1707
+ badges?: string[] | undefined;
1708
+ promo?: {
1709
+ message: string;
1710
+ ends_at?: string | undefined;
1711
+ } | undefined;
1712
+ size_guide?: {
1713
+ title: string;
1714
+ columns?: string[] | undefined;
1715
+ rows?: string[][] | undefined;
1716
+ notes?: string[] | undefined;
1717
+ } | undefined;
1718
+ }[];
1719
+ } | {
1720
+ kind: "review";
1721
+ quotes: {
1722
+ quote: string;
1723
+ author: string;
1724
+ }[];
1725
+ } | {
1726
+ kind: "why_us";
1727
+ items: {
1728
+ title: string;
1729
+ body: string;
1730
+ icon?: "payment" | "delivery" | "returns" | undefined;
1731
+ }[];
1732
+ } | {
1733
+ title: string;
1734
+ kind: "explore";
1735
+ links: {
1736
+ code: string;
1737
+ label: string;
1738
+ }[];
1739
+ })[];
1740
+ }>;
1741
+ };
1742
+ inventory: {
1743
+ get: (sku: string) => Promise<{
1744
+ sku: string;
1745
+ in_stock: boolean;
1746
+ available: number;
1747
+ }>;
1748
+ };
1749
+ cart: {
1750
+ create: () => Promise<{
1751
+ currency: string;
1752
+ items: {
1753
+ sku: string;
1754
+ qty: number;
1755
+ unit_price: {
1756
+ amount: number;
1757
+ currency: string;
1758
+ };
1759
+ line_total: {
1760
+ amount: number;
1761
+ currency: string;
1762
+ };
1763
+ attributes?: Record<string, string> | undefined;
1764
+ image?: string | undefined;
1765
+ available?: number | undefined;
1766
+ title?: string | undefined;
1767
+ }[];
1768
+ cart_id: string;
1769
+ totals: {
1770
+ subtotal: {
1771
+ amount: number;
1772
+ currency: string;
1773
+ };
1774
+ shipping: {
1775
+ amount: number;
1776
+ currency: string;
1777
+ };
1778
+ tax: {
1779
+ amount: number;
1780
+ currency: string;
1781
+ };
1782
+ total: {
1783
+ amount: number;
1784
+ currency: string;
1785
+ };
1786
+ };
1787
+ locked?: {
1788
+ since: string;
1789
+ reason: "checkout_handoff";
1790
+ expires_at?: string | undefined;
1791
+ } | undefined;
1792
+ }>;
1793
+ get: () => Promise<{
1794
+ currency: string;
1795
+ items: {
1796
+ sku: string;
1797
+ qty: number;
1798
+ unit_price: {
1799
+ amount: number;
1800
+ currency: string;
1801
+ };
1802
+ line_total: {
1803
+ amount: number;
1804
+ currency: string;
1805
+ };
1806
+ attributes?: Record<string, string> | undefined;
1807
+ image?: string | undefined;
1808
+ available?: number | undefined;
1809
+ title?: string | undefined;
1810
+ }[];
1811
+ cart_id: string;
1812
+ totals: {
1813
+ subtotal: {
1814
+ amount: number;
1815
+ currency: string;
1816
+ };
1817
+ shipping: {
1818
+ amount: number;
1819
+ currency: string;
1820
+ };
1821
+ tax: {
1822
+ amount: number;
1823
+ currency: string;
1824
+ };
1825
+ total: {
1826
+ amount: number;
1827
+ currency: string;
1828
+ };
1829
+ };
1830
+ locked?: {
1831
+ since: string;
1832
+ reason: "checkout_handoff";
1833
+ expires_at?: string | undefined;
1834
+ } | undefined;
1835
+ }>;
1836
+ addItem: (body: AddCartItemRequest) => Promise<{
1837
+ currency: string;
1838
+ items: {
1839
+ sku: string;
1840
+ qty: number;
1841
+ unit_price: {
1842
+ amount: number;
1843
+ currency: string;
1844
+ };
1845
+ line_total: {
1846
+ amount: number;
1847
+ currency: string;
1848
+ };
1849
+ attributes?: Record<string, string> | undefined;
1850
+ image?: string | undefined;
1851
+ available?: number | undefined;
1852
+ title?: string | undefined;
1853
+ }[];
1854
+ cart_id: string;
1855
+ totals: {
1856
+ subtotal: {
1857
+ amount: number;
1858
+ currency: string;
1859
+ };
1860
+ shipping: {
1861
+ amount: number;
1862
+ currency: string;
1863
+ };
1864
+ tax: {
1865
+ amount: number;
1866
+ currency: string;
1867
+ };
1868
+ total: {
1869
+ amount: number;
1870
+ currency: string;
1871
+ };
1872
+ };
1873
+ locked?: {
1874
+ since: string;
1875
+ reason: "checkout_handoff";
1876
+ expires_at?: string | undefined;
1877
+ } | undefined;
1878
+ }>;
1879
+ updateItem: (sku: string, body: UpdateCartItemRequest) => Promise<{
1880
+ currency: string;
1881
+ items: {
1882
+ sku: string;
1883
+ qty: number;
1884
+ unit_price: {
1885
+ amount: number;
1886
+ currency: string;
1887
+ };
1888
+ line_total: {
1889
+ amount: number;
1890
+ currency: string;
1891
+ };
1892
+ attributes?: Record<string, string> | undefined;
1893
+ image?: string | undefined;
1894
+ available?: number | undefined;
1895
+ title?: string | undefined;
1896
+ }[];
1897
+ cart_id: string;
1898
+ totals: {
1899
+ subtotal: {
1900
+ amount: number;
1901
+ currency: string;
1902
+ };
1903
+ shipping: {
1904
+ amount: number;
1905
+ currency: string;
1906
+ };
1907
+ tax: {
1908
+ amount: number;
1909
+ currency: string;
1910
+ };
1911
+ total: {
1912
+ amount: number;
1913
+ currency: string;
1914
+ };
1915
+ };
1916
+ locked?: {
1917
+ since: string;
1918
+ reason: "checkout_handoff";
1919
+ expires_at?: string | undefined;
1920
+ } | undefined;
1921
+ }>;
1922
+ removeItem: (sku: string) => Promise<{
1923
+ currency: string;
1924
+ items: {
1925
+ sku: string;
1926
+ qty: number;
1927
+ unit_price: {
1928
+ amount: number;
1929
+ currency: string;
1930
+ };
1931
+ line_total: {
1932
+ amount: number;
1933
+ currency: string;
1934
+ };
1935
+ attributes?: Record<string, string> | undefined;
1936
+ image?: string | undefined;
1937
+ available?: number | undefined;
1938
+ title?: string | undefined;
1939
+ }[];
1940
+ cart_id: string;
1941
+ totals: {
1942
+ subtotal: {
1943
+ amount: number;
1944
+ currency: string;
1945
+ };
1946
+ shipping: {
1947
+ amount: number;
1948
+ currency: string;
1949
+ };
1950
+ tax: {
1951
+ amount: number;
1952
+ currency: string;
1953
+ };
1954
+ total: {
1955
+ amount: number;
1956
+ currency: string;
1957
+ };
1958
+ };
1959
+ locked?: {
1960
+ since: string;
1961
+ reason: "checkout_handoff";
1962
+ expires_at?: string | undefined;
1963
+ } | undefined;
1964
+ }>;
1965
+ /**
1966
+ * Hand the cart to DAZN checkout and get the URL to send the fan to.
1967
+ *
1968
+ * The URL is opaque — redirect to it, never parse it. This is the last call in our
1969
+ * journey; payment, order and confirmation all happen on DAZN.
1970
+ */
1971
+ handoff: () => Promise<{
1972
+ checkout_url: string;
1973
+ }>;
1974
+ };
1975
+ auth: {
1976
+ /** Exchange a single-use DAZN identity JWT for a session (sets the cookie). */
1977
+ exchangeSession: (body: SessionExchangeRequest) => Promise<{
1978
+ user_id: string;
1979
+ }>;
1980
+ /**
1981
+ * Redeem the `session` + `code` a DAZN redirect arrives with (sets the cookie).
1982
+ * The entry path from the DAZN app; see `ShopSessionRequest`.
1983
+ */
1984
+ redeemShopSession: (body: ShopSessionRequest) => Promise<{
1985
+ expires_at: number;
1986
+ shop_session_id: string;
1987
+ session_token: string;
1988
+ email?: string | undefined;
1989
+ product?: {
1990
+ title: string;
1991
+ id: string;
1992
+ vendor_id: string;
1993
+ } | null | undefined;
1994
+ }>;
1995
+ };
1996
+ };
1997
+
1998
+ /**
1999
+ * The session seam, implemented once at the transport level.
2000
+ *
2001
+ * Retry-on-401 lives here rather than wrapped around each API method for a reason: the
2002
+ * typed client is a nested object of namespaces, so wrapping the surface means a proxy
2003
+ * or ~20 hand-written wrappers, and either would drift the first time a method is added.
2004
+ * A `fetch` is one function and every request already passes through it.
2005
+ *
2006
+ * **The retry budget is exactly one.** A host whose `getToken` cannot recover — expired
2007
+ * refresh, revoked session, signed-out user — would otherwise have every request spin
2008
+ * against its identity service. One retry, then we stop and say so.
2009
+ */
2010
+ export declare function createShopFetch(opts: {
2011
+ getToken: GetToken;
2012
+ onEvent?: OnEvent;
2013
+ fetchImpl?: typeof fetch;
2014
+ /** Needed only to detect the silent-anonymous-session trap below. */
2015
+ sessionTransport?: 'bearer' | 'cookie';
2016
+ }): typeof fetch;
2017
+
2018
+ /** Thrown on any non-2xx `/v1` response, carrying the standard error object. */
2019
+ declare class DaznApiError extends Error {
2020
+ readonly status: number;
2021
+ readonly code?: ErrorCode_2;
2022
+ readonly requestId?: string;
2023
+ readonly body?: ApiErrorBody;
2024
+ constructor(status: number, parsed?: {
2025
+ error: ApiErrorBody;
2026
+ request_id: string;
2027
+ });
2028
+ }
2029
+
2030
+ declare type DaznClient = ReturnType<typeof createClient>;
2031
+
2032
+ export declare interface DaznShopConfig {
2033
+ apiBaseUrl: string;
2034
+ getToken: GetToken;
2035
+ /**
2036
+ * How the session travels to the commerce API. Default `'bearer'`.
2037
+ *
2038
+ * - `'bearer'` — the token from {@link GetToken} goes in `Authorization`, and cookies
2039
+ * are **not** sent. Correct when mounted in a host's page: the SDK is cross-origin
2040
+ * with the API, holds no credential of its own, and a cookie would be neither sent
2041
+ * nor wanted.
2042
+ * - `'cookie'` — the engine's httpOnly session cookie is sent instead. Correct for a
2043
+ * first-party web view that authenticates by cookie and has no token for JS to hold.
2044
+ *
2045
+ * Getting this wrong does not fail loudly. Requests simply arrive unauthenticated, and
2046
+ * an environment that issues anonymous sessions will hand out a **new** one per call —
2047
+ * so an item is added to one cart and read back from another, and the basket looks
2048
+ * empty rather than broken. That is exactly how this was found.
2049
+ */
2050
+ sessionTransport?: 'bearer' | 'cookie';
2051
+ /**
2052
+ * Origin (or path prefix) the SDK's own static assets are served from — the design
2053
+ * imagery the shop-entry screen renders. Default: none, i.e. paths are left as written.
2054
+ *
2055
+ * These are **root-relative** in the source (`/figma/home/hero.jpg`), which is correct
2056
+ * in our own app and wrong everywhere else: a browser resolves them against the page's
2057
+ * origin, and mounted in a host's page that is the **host's** origin. The host does not
2058
+ * serve our imagery, so every one of them 404s and the entry screen renders with broken
2059
+ * images while the rest of the SDK works perfectly — which is exactly how it presents.
2060
+ *
2061
+ * Only root-relative paths are rebased. Absolute and protocol-relative URLs are left
2062
+ * untouched, because catalogue media arrives from the API already absolute and must not
2063
+ * be rewritten.
2064
+ */
2065
+ assetBaseUrl?: string;
2066
+ market?: Market;
2067
+ navigate?: Navigate;
2068
+ /**
2069
+ * @deprecated No longer affects how anything renders, and safe to remove.
2070
+ *
2071
+ * The storefront has one design per screen, not two: the shop entry and category pages
2072
+ * are drawn dark, the PDP and cart light. There is no light version of the dark hero to
2073
+ * switch to, so this never selected between two designs — it repainted the tokens under
2074
+ * a screen built for the other palette, which is how `theme="light"` produced near-black
2075
+ * button text on a dark photograph.
2076
+ *
2077
+ * Still accepted so that passing it does not break a build, and still readable from
2078
+ * context by a host that wants to know what was asked for. It decides nothing.
2079
+ */
2080
+ theme?: 'light' | 'dark' | 'auto';
2081
+ notifications?: 'internal' | 'host';
2082
+ onEvent?: OnEvent;
2083
+ locale?: string;
2084
+ }
2085
+
2086
+ /**
2087
+ * Mount once, high in the host tree. Owns configuration, the API client, the session
2088
+ * seam and event fan-out, so two components rendering the same cart cannot disagree.
2089
+ *
2090
+ * Nesting providers is supported but each gets an independent client — don't, unless
2091
+ * that is what you mean.
2092
+ */
2093
+ export declare function DaznShopProvider({ children, ...config }: DaznShopConfig & {
2094
+ children: ReactNode;
2095
+ }): JSX.Element;
2096
+
2097
+ /** A row this surface cannot put a number against yet. */
2098
+ export declare type DeferredLine = 'shipping' | 'tax';
2099
+
2100
+ /**
2101
+ * A whole-number discount percentage derived from two server-provided Money values.
2102
+ * This is presentational rounding of values the server already computed — it does NOT
2103
+ * price anything. Returns undefined when there is no genuine discount.
2104
+ */
2105
+ export declare function discountPct(price: Money, compareAt?: Money): number | undefined;
2106
+
2107
+ /** Distinct values for an attribute key, in first-seen order. */
2108
+ export declare function distinctValues(variants: ProductVariant[], key: string): string[];
2109
+
2110
+ /** Neutral empty state with a title, optional body, and optional action. */
2111
+ export declare function EmptyState({ title, body, action, icon }: EmptyStateProps): JSX.Element;
2112
+
2113
+ export declare interface EmptyStateProps {
2114
+ title: string;
2115
+ body?: string;
2116
+ /** Optional call-to-action (e.g. a Button or Link). */
2117
+ action?: ReactNode;
2118
+ icon?: ReactNode;
2119
+ }
2120
+
2121
+ /**
2122
+ * The stable error `code` enum. Clients branch on `code`, never on `message`.
2123
+ * Additive-only: a new code is a minor bump consumers must tolerate.
2124
+ *
2125
+ * | code | HTTP | when |
2126
+ * | ------------------ | ---- | --------------------------------------------- |
2127
+ * | invalid_request | 400 | malformed body or missing field |
2128
+ * | unauthorized | 401 | missing/invalid session or credentials |
2129
+ * | out_of_stock | 409 | requested qty exceeds available stock |
2130
+ * | cart_locked | 409 | cart handed to checkout; mutations refused |
2131
+ * | sku_not_found | 404 | a referenced SKU/product does not exist |
2132
+ * | category_not_found | 404 | a referenced category code does not exist |
2133
+ * | amount_mismatch | 422 | totals do not match the authorised amount |
2134
+ * | duplicate_order | 200 | idempotent replay — existing order returned |
2135
+ * | forbidden | 403 | authenticated, but the role may not do this |
2136
+ * | invalid_transition | 409 | fulfilment move not allowed from this status |
2137
+ * | rate_limited | 429 | too many requests |
2138
+ * | server_error | 500 | unexpected error (safe to retry idempotent) |
2139
+ */
2140
+ declare const ErrorCode: z.ZodEnum<["invalid_request", "unauthorized", "out_of_stock", "cart_locked", "sku_not_found", "category_not_found", "amount_mismatch", "duplicate_order", "order_not_found", "order_not_cancellable", "forbidden", "invalid_transition", "rate_limited", "server_error"]>;
2141
+
2142
+ declare type ErrorCode = z.infer<typeof ErrorCode>;
2143
+
2144
+ /**
2145
+ * The stable error `code` enum. Clients branch on `code`, never on `message`.
2146
+ * Additive-only: a new code is a minor bump consumers must tolerate.
2147
+ *
2148
+ * | code | HTTP | when |
2149
+ * | ------------------ | ---- | --------------------------------------------- |
2150
+ * | invalid_request | 400 | malformed body or missing field |
2151
+ * | unauthorized | 401 | missing/invalid session or credentials |
2152
+ * | out_of_stock | 409 | requested qty exceeds available stock |
2153
+ * | cart_locked | 409 | cart handed to checkout; mutations refused |
2154
+ * | sku_not_found | 404 | a referenced SKU/product does not exist |
2155
+ * | category_not_found | 404 | a referenced category code does not exist |
2156
+ * | amount_mismatch | 422 | totals do not match the authorised amount |
2157
+ * | duplicate_order | 200 | idempotent replay — existing order returned |
2158
+ * | forbidden | 403 | authenticated, but the role may not do this |
2159
+ * | invalid_transition | 409 | fulfilment move not allowed from this status |
2160
+ * | rate_limited | 429 | too many requests |
2161
+ * | server_error | 500 | unexpected error (safe to retry idempotent) |
2162
+ */
2163
+ declare const ErrorCode_2: z.ZodEnum<["invalid_request", "unauthorized", "out_of_stock", "cart_locked", "sku_not_found", "category_not_found", "amount_mismatch", "duplicate_order", "order_not_found", "order_not_cancellable", "forbidden", "invalid_transition", "rate_limited", "server_error"]>;
2164
+
2165
+ declare type ErrorCode_2 = z.infer<typeof ErrorCode_2>;
2166
+
2167
+ /**
2168
+ * Error state whose copy is derived from the API error `code`. The retry affordance is
2169
+ * shown only for retriable codes (or when the caller forces `onRetry`).
2170
+ */
2171
+ export declare function ErrorState({ code, title, body, onRetry, retryLabel }: ErrorStateProps): JSX.Element;
2172
+
2173
+ export declare interface ErrorStateProps {
2174
+ /** API error code — copy is mapped from this, never from a raw message. */
2175
+ code?: ErrorCode;
2176
+ /** Override the mapped title/body if a screen needs bespoke copy. */
2177
+ title?: string;
2178
+ body?: string;
2179
+ /** Retry handler; only rendered when the error is retriable. */
2180
+ onRetry?: () => void;
2181
+ retryLabel?: string;
2182
+ }
2183
+
2184
+ /**
2185
+ * UI treatment for each API error `code`. The storefront ALWAYS branches on the
2186
+ * machine `code` (never the human message). Copy is display-only; the engine remains
2187
+ * authoritative for what actually happened.
2188
+ */
2189
+ export declare interface ErrorTreatment {
2190
+ title: string;
2191
+ body: string;
2192
+ /** May the user safely retry the same action? */
2193
+ retriable: boolean;
2194
+ /** Should this trigger the silent DAZN re-handshake instead of a visible error? */
2195
+ reauth?: boolean;
2196
+ }
2197
+
2198
+ declare interface ExploreLink {
2199
+ label: string;
2200
+ code: string;
2201
+ }
2202
+
2203
+ /** "Shop more" — links to sibling categories. */
2204
+ export declare function ExploreList({ title, links }: {
2205
+ title: string;
2206
+ links: ExploreLink[];
2207
+ }): JSX.Element;
2208
+
2209
+ /** Horizontal chip filter bar for the PLP. Chips are toggle buttons. */
2210
+ export declare function FilterBar({ options, active, onToggle, onClear }: FilterBarProps): JSX.Element;
2211
+
2212
+ export declare interface FilterBarProps {
2213
+ options: FilterOption[];
2214
+ /** Currently active filter values. */
2215
+ active: string[];
2216
+ onToggle: (value: string) => void;
2217
+ onClear?: () => void;
2218
+ }
2219
+
2220
+ export declare interface FilterOption {
2221
+ value: string;
2222
+ label: string;
2223
+ }
2224
+
2225
+ /**
2226
+ * Format a {@link Money} for display in the given locale. Divides minor units by the
2227
+ * currency exponent purely for presentation.
2228
+ */
2229
+ export declare function formatMoney(money: Money, locale?: string): string;
2230
+
2231
+ /**
2232
+ * Supplies the session bearer token. Called before each authenticated request and again,
2233
+ * once, after a `401`.
2234
+ *
2235
+ * Returning `undefined` or an empty string is legal and means "no session yet":
2236
+ * catalogue browsing works without one, and cart operations raise `session_required`.
2237
+ * The SDK never persists what this returns and never holds a refresh credential — that
2238
+ * stays with the host, which is the whole point of it being a callback.
2239
+ */
2240
+ export declare type GetToken = () => string | undefined | Promise<string | undefined>;
2241
+
2242
+ declare function HomeShowcase(): JSX.Element;
2243
+ export { HomeShowcase }
2244
+ export { HomeShowcase as ShopHome }
2245
+
2246
+ /**
2247
+ * 4.5 Identity gate. **DAZN owns identity; the storefront runs no login**, so this asks
2248
+ * the host to take over rather than doing anything itself.
2249
+ *
2250
+ * In the app this called a `window.DAZNBridge` method. That was right for a web view and
2251
+ * wrong for a mounted component: reaching for a global the host may not have installed
2252
+ * is a hidden coupling, and a component that silently does nothing when the global is
2253
+ * absent is worse than one that says so. It now raises `session_required` — the same
2254
+ * request, made through the contract the host already handles.
2255
+ *
2256
+ * The cart is session-scoped server-side and survives the round trip; we never drop it.
2257
+ */
2258
+ export declare function IdentityGate({ returnTo }: IdentityGateProps): JSX.Element;
2259
+
2260
+ export declare interface IdentityGateProps {
2261
+ /** Where the host should return the fan after its sign-in flow. */
2262
+ returnTo: string;
2263
+ }
2264
+
2265
+ /**
2266
+ * `GET /v1/inventory/{sku}` — read-model availability. Authoritative stock is Odoo;
2267
+ * a live check is also run at checkout.
2268
+ */
2269
+ declare const Inventory: z.ZodObject<{
2270
+ sku: z.ZodString;
2271
+ available: z.ZodNumber;
2272
+ in_stock: z.ZodBoolean;
2273
+ }, "strip", z.ZodTypeAny, {
2274
+ sku: string;
2275
+ available: number;
2276
+ in_stock: boolean;
2277
+ }, {
2278
+ sku: string;
2279
+ available: number;
2280
+ in_stock: boolean;
2281
+ }>;
2282
+
2283
+ declare type Inventory = z.infer<typeof Inventory>;
2284
+
2285
+ /** A duplicate order (200 replay) is a SUCCESS, never an error to surface. */
2286
+ export declare function isDuplicateOrder(err: unknown): boolean;
2287
+
2288
+ /** A 401 anywhere in the flow means the session lapsed — re-handshake, keep the cart. */
2289
+ export declare function isUnauthorized(err: unknown): boolean;
2290
+
2291
+ /** Full-width lifestyle image (rounded), inset from the page edges. */
2292
+ export declare function LifestyleImage({ images }: {
2293
+ images: string[];
2294
+ }): JSX.Element | null;
2295
+
2296
+ /** Editorial text band between PCP sections. */
2297
+ export declare function LifestyleText({ text, align }: LifestyleTextProps): JSX.Element;
2298
+
2299
+ export declare interface LifestyleTextProps {
2300
+ text: string;
2301
+ align?: 'center' | 'left';
2302
+ }
2303
+
2304
+ /**
2305
+ * Query params for the product list. Extends {@link MarketQuery}, so `country` and
2306
+ * `lang` are accepted here as on every other catalogue read.
2307
+ */
2308
+ declare const ListProductsQuery: z.ZodObject<{
2309
+ country: z.ZodOptional<z.ZodString>;
2310
+ lang: z.ZodOptional<z.ZodString>;
2311
+ } & {
2312
+ category: z.ZodOptional<z.ZodString>;
2313
+ cursor: z.ZodOptional<z.ZodString>;
2314
+ limit: z.ZodOptional<z.ZodNumber>;
2315
+ }, "strip", z.ZodTypeAny, {
2316
+ country?: string | undefined;
2317
+ lang?: string | undefined;
2318
+ cursor?: string | undefined;
2319
+ limit?: number | undefined;
2320
+ category?: string | undefined;
2321
+ }, {
2322
+ country?: string | undefined;
2323
+ lang?: string | undefined;
2324
+ cursor?: string | undefined;
2325
+ limit?: number | undefined;
2326
+ category?: string | undefined;
2327
+ }>;
2328
+
2329
+ declare type ListProductsQuery = z.infer<typeof ListProductsQuery>;
2330
+
2331
+ export declare interface Market {
2332
+ /** ISO 3166-1 alpha-2. Selects pricing, currency and merchandising. */
2333
+ country: string;
2334
+ /** IETF language tag. Display copy only — deliberately independent of `country`. */
2335
+ lang: string;
2336
+ }
2337
+
2338
+ /**
2339
+ * Swipeable image gallery with dot indicators. Reduced-motion aware (smooth-scroll is
2340
+ * disabled globally under prefers-reduced-motion). Keyboard: dots are real buttons.
2341
+ */
2342
+ export declare function MediaGallery({ media, alt }: MediaGalleryProps): JSX.Element;
2343
+
2344
+ export declare interface MediaGalleryProps {
2345
+ media: string[];
2346
+ alt: string;
2347
+ }
2348
+
2349
+ export declare function minorUnitExponent(currency: string): number;
2350
+
2351
+ /**
2352
+ * Money is **always** integer minor units + an ISO-4217 currency.
2353
+ * `{ amount: 2500, currency: 'GBP' }` = £25.00. Never a float, never a bare number.
2354
+ *
2355
+ * Strict: the shape is fixed, so unknown keys are rejected everywhere Money appears.
2356
+ */
2357
+ declare const Money: z.ZodObject<{
2358
+ amount: z.ZodNumber;
2359
+ currency: z.ZodString;
2360
+ }, "strict", z.ZodTypeAny, {
2361
+ amount: number;
2362
+ currency: string;
2363
+ }, {
2364
+ amount: number;
2365
+ currency: string;
2366
+ }>;
2367
+
2368
+ declare type Money = z.infer<typeof Money>;
2369
+
2370
+ /** Host router adapter. Omit it and navigable elements only raise `navigate` events. */
2371
+ export declare type Navigate = (to: ShopRoute) => void;
2372
+
2373
+ /**
2374
+ * Events are informational. **No event is a control point** — a handler cannot cancel
2375
+ * one, and the SDK does not await it. Gate an action in host UI before mounting the
2376
+ * component instead.
2377
+ */
2378
+ export declare type OnEvent = (event: ShopEvent) => void;
2379
+
2380
+ /**
2381
+ * Fulfilment order status. Lives here (not in a synchronous order contract) because the
2382
+ * engine no longer owns orders (ADR-0008) — order/fulfilment status reaches us only as
2383
+ * async events (Odoo → engine → consumers). DAZN is authoritative for completed orders.
2384
+ */
2385
+ declare const OrderStatus: z.ZodEnum<["confirmed", "packed", "shipped", "delivered", "cancelled", "refunded"]>;
2386
+
2387
+ declare type OrderStatus = z.infer<typeof OrderStatus>;
2388
+
2389
+ /**
2390
+ * The pricing breakdown. DISPLAY ONLY — every value is server-authoritative; the UI
2391
+ * never sums or computes anything here.
2392
+ */
2393
+ export declare function OrderSummary({ pricing, deferred }: OrderSummaryProps): JSX.Element;
2394
+
2395
+ export declare interface OrderSummaryProps {
2396
+ pricing: Pricing;
2397
+ /**
2398
+ * Rows whose value is **not ours to state**, rendered as "Calculated at checkout"
2399
+ * instead of the figure in `pricing`.
2400
+ *
2401
+ * This exists because a zero is ambiguous and the ambiguity is dangerous. `Pricing`
2402
+ * requires all four values, so the engine sends `0` for shipping and tax it has not
2403
+ * computed — and rendering that gives the fan a confident "Tax £0.00" and a total that
2404
+ * changes at checkout. DAZN owns checkout and therefore owns delivery and tax
2405
+ * (ADR-0008), so in the cart both are genuinely unknown rather than genuinely free.
2406
+ *
2407
+ * When anything is deferred the grand total is withheld too: a total that omits
2408
+ * delivery is not a total. Subtotal carries the emphasis instead.
2409
+ */
2410
+ deferred?: DeferredLine[];
2411
+ }
2412
+
2413
+ /** PCP hero — category title + subtitle over a full-bleed lifestyle image (dark). */
2414
+ export declare function PcpHero({ hero }: {
2415
+ hero: CategoryHero;
2416
+ }): JSX.Element;
2417
+
2418
+ /**
2419
+ * Row of merchandising badges shown above the PDP title (mobile/badge/top-pick — light
2420
+ * surface, dark text). Renders nothing when the catalogue provides no flags, so the
2421
+ * layout collapses cleanly rather than showing an empty row.
2422
+ */
2423
+ export declare function PdpBadges({ badges }: PdpBadgesProps): JSX.Element | null;
2424
+
2425
+ export declare interface PdpBadgesProps {
2426
+ /** Merchandising flags (Product.badges), e.g. ["Top pick", "99% Happy Customers"]. */
2427
+ badges: string[];
2428
+ }
2429
+
2430
+ /**
2431
+ * Displays a price with an optional discount. DISPLAY ONLY — it never computes totals;
2432
+ * any percentage shown is a presentational rounding of values the server already
2433
+ * provided. Matches the DAZN design: discounts render as the green sale badge (PDP) or a
2434
+ * struck-through was-price (cards) — never as red discount text.
2435
+ */
2436
+ export declare function PriceBlock({ price, compareAt, discountPct, size, variant, align, }: PriceBlockProps): JSX.Element;
2437
+
2438
+ export declare interface PriceBlockProps {
2439
+ /** Current server-authoritative price. */
2440
+ price: Money;
2441
+ /** Optional strike-through "was" price (server-provided). */
2442
+ compareAt?: Money;
2443
+ /** Optional explicit discount %. If omitted, derived from price/compareAt. */
2444
+ discountPct?: number;
2445
+ size?: 'sm' | 'md' | 'lg';
2446
+ /**
2447
+ * How a discount is shown:
2448
+ * - `strikethrough` (default, product cards): current price + struck was-price.
2449
+ * - `badge` (PDP): current price + struck was-price + a green `-NN%` sale badge.
2450
+ */
2451
+ variant?: 'strikethrough' | 'badge';
2452
+ /** Cross-axis alignment: `baseline` (default), `center`, or `end` (cards). */
2453
+ align?: 'baseline' | 'center' | 'end';
2454
+ }
2455
+
2456
+ /**
2457
+ * The four-line pricing breakdown shared by the cart (`totals`) and the fan-facing
2458
+ * order (`pricing`). Every line is Money in the same currency.
2459
+ */
2460
+ declare const Pricing: z.ZodObject<{
2461
+ subtotal: z.ZodObject<{
2462
+ amount: z.ZodNumber;
2463
+ currency: z.ZodString;
2464
+ }, "strict", z.ZodTypeAny, {
2465
+ amount: number;
2466
+ currency: string;
2467
+ }, {
2468
+ amount: number;
2469
+ currency: string;
2470
+ }>;
2471
+ shipping: z.ZodObject<{
2472
+ amount: z.ZodNumber;
2473
+ currency: z.ZodString;
2474
+ }, "strict", z.ZodTypeAny, {
2475
+ amount: number;
2476
+ currency: string;
2477
+ }, {
2478
+ amount: number;
2479
+ currency: string;
2480
+ }>;
2481
+ tax: z.ZodObject<{
2482
+ amount: z.ZodNumber;
2483
+ currency: z.ZodString;
2484
+ }, "strict", z.ZodTypeAny, {
2485
+ amount: number;
2486
+ currency: string;
2487
+ }, {
2488
+ amount: number;
2489
+ currency: string;
2490
+ }>;
2491
+ total: z.ZodObject<{
2492
+ amount: z.ZodNumber;
2493
+ currency: z.ZodString;
2494
+ }, "strict", z.ZodTypeAny, {
2495
+ amount: number;
2496
+ currency: string;
2497
+ }, {
2498
+ amount: number;
2499
+ currency: string;
2500
+ }>;
2501
+ }, "strip", z.ZodTypeAny, {
2502
+ subtotal: {
2503
+ amount: number;
2504
+ currency: string;
2505
+ };
2506
+ shipping: {
2507
+ amount: number;
2508
+ currency: string;
2509
+ };
2510
+ tax: {
2511
+ amount: number;
2512
+ currency: string;
2513
+ };
2514
+ total: {
2515
+ amount: number;
2516
+ currency: string;
2517
+ };
2518
+ }, {
2519
+ subtotal: {
2520
+ amount: number;
2521
+ currency: string;
2522
+ };
2523
+ shipping: {
2524
+ amount: number;
2525
+ currency: string;
2526
+ };
2527
+ tax: {
2528
+ amount: number;
2529
+ currency: string;
2530
+ };
2531
+ total: {
2532
+ amount: number;
2533
+ currency: string;
2534
+ };
2535
+ }>;
2536
+
2537
+ declare type Pricing = z.infer<typeof Pricing>;
2538
+
2539
+ /** A catalog product, served from the engine's cached read model (synced from Odoo). */
2540
+ declare const Product: z.ZodObject<{
2541
+ sku: z.ZodString;
2542
+ title: z.ZodString;
2543
+ category: z.ZodString;
2544
+ price: z.ZodObject<{
2545
+ amount: z.ZodNumber;
2546
+ currency: z.ZodString;
2547
+ }, "strict", z.ZodTypeAny, {
2548
+ amount: number;
2549
+ currency: string;
2550
+ }, {
2551
+ amount: number;
2552
+ currency: string;
2553
+ }>;
2554
+ /**
2555
+ * Optional strike-through "was" price for a sale. Additive/optional: the engine only
2556
+ * populates it once the pricing source is agreed (A2 — pricing contract). Display
2557
+ * only; never authoritative. Absent = not on sale.
2558
+ */
2559
+ compare_at: z.ZodOptional<z.ZodObject<{
2560
+ amount: z.ZodNumber;
2561
+ currency: z.ZodString;
2562
+ }, "strict", z.ZodTypeAny, {
2563
+ amount: number;
2564
+ currency: string;
2565
+ }, {
2566
+ amount: number;
2567
+ currency: string;
2568
+ }>>;
2569
+ media: z.ZodArray<z.ZodString, "many">;
2570
+ variants: z.ZodArray<z.ZodObject<{
2571
+ sku: z.ZodString;
2572
+ attributes: z.ZodRecord<z.ZodString, z.ZodString>;
2573
+ in_stock: z.ZodBoolean;
2574
+ /**
2575
+ * Optional per-variant image — e.g. the colour swatch/tile shown in the PDP colour
2576
+ * selector. Absent = fall back to the product's `media`.
2577
+ */
2578
+ image: z.ZodOptional<z.ZodString>;
2579
+ /**
2580
+ * Optional per-variant stock count, used to show "N left" low-stock badges. Display
2581
+ * only — the authoritative check runs at checkout. Absent = show no count.
2582
+ */
2583
+ available: z.ZodOptional<z.ZodNumber>;
2584
+ }, "strip", z.ZodTypeAny, {
2585
+ sku: string;
2586
+ attributes: Record<string, string>;
2587
+ in_stock: boolean;
2588
+ image?: string | undefined;
2589
+ available?: number | undefined;
2590
+ }, {
2591
+ sku: string;
2592
+ attributes: Record<string, string>;
2593
+ in_stock: boolean;
2594
+ image?: string | undefined;
2595
+ available?: number | undefined;
2596
+ }>, "many">;
2597
+ /** Optional long-form product description (PDP details accordion). Absent = no body. */
2598
+ description: z.ZodOptional<z.ZodString>;
2599
+ /**
2600
+ * Optional merchandising flags shown as badges on the PDP/cards (e.g. "Top pick").
2601
+ * Free-form labels sourced from the catalogue; absent/empty = no badges.
2602
+ */
2603
+ badges: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2604
+ /** Optional PDP promo nudge (display only — the offer is applied at DAZN checkout). */
2605
+ promo: z.ZodOptional<z.ZodObject<{
2606
+ message: z.ZodString;
2607
+ /** ISO-8601 end time that drives the countdown; absent = show the message, no timer. */
2608
+ ends_at: z.ZodOptional<z.ZodString>;
2609
+ }, "strip", z.ZodTypeAny, {
2610
+ message: string;
2611
+ ends_at?: string | undefined;
2612
+ }, {
2613
+ message: string;
2614
+ ends_at?: string | undefined;
2615
+ }>>;
2616
+ /**
2617
+ * Optional category-wide sizing help for the PDP "Size guide" overlay. Absent = the
2618
+ * storefront hides the control rather than opening an empty overlay.
2619
+ */
2620
+ size_guide: z.ZodOptional<z.ZodObject<{
2621
+ title: z.ZodString;
2622
+ /** Column headings for `rows`, e.g. `['Size', 'Chest (cm)', 'Length (cm)']`. */
2623
+ columns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2624
+ /** One row per size; each row's length matches `columns`. */
2625
+ rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>;
2626
+ /** Free-text fit guidance, e.g. "Relaxed, slightly oversized fit". */
2627
+ notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2628
+ }, "strip", z.ZodTypeAny, {
2629
+ title: string;
2630
+ columns?: string[] | undefined;
2631
+ rows?: string[][] | undefined;
2632
+ notes?: string[] | undefined;
2633
+ }, {
2634
+ title: string;
2635
+ columns?: string[] | undefined;
2636
+ rows?: string[][] | undefined;
2637
+ notes?: string[] | undefined;
2638
+ }>>;
2639
+ }, "strip", z.ZodTypeAny, {
2640
+ sku: string;
2641
+ title: string;
2642
+ category: string;
2643
+ price: {
2644
+ amount: number;
2645
+ currency: string;
2646
+ };
2647
+ media: string[];
2648
+ variants: {
2649
+ sku: string;
2650
+ attributes: Record<string, string>;
2651
+ in_stock: boolean;
2652
+ image?: string | undefined;
2653
+ available?: number | undefined;
2654
+ }[];
2655
+ compare_at?: {
2656
+ amount: number;
2657
+ currency: string;
2658
+ } | undefined;
2659
+ description?: string | undefined;
2660
+ badges?: string[] | undefined;
2661
+ promo?: {
2662
+ message: string;
2663
+ ends_at?: string | undefined;
2664
+ } | undefined;
2665
+ size_guide?: {
2666
+ title: string;
2667
+ columns?: string[] | undefined;
2668
+ rows?: string[][] | undefined;
2669
+ notes?: string[] | undefined;
2670
+ } | undefined;
2671
+ }, {
2672
+ sku: string;
2673
+ title: string;
2674
+ category: string;
2675
+ price: {
2676
+ amount: number;
2677
+ currency: string;
2678
+ };
2679
+ media: string[];
2680
+ variants: {
2681
+ sku: string;
2682
+ attributes: Record<string, string>;
2683
+ in_stock: boolean;
2684
+ image?: string | undefined;
2685
+ available?: number | undefined;
2686
+ }[];
2687
+ compare_at?: {
2688
+ amount: number;
2689
+ currency: string;
2690
+ } | undefined;
2691
+ description?: string | undefined;
2692
+ badges?: string[] | undefined;
2693
+ promo?: {
2694
+ message: string;
2695
+ ends_at?: string | undefined;
2696
+ } | undefined;
2697
+ size_guide?: {
2698
+ title: string;
2699
+ columns?: string[] | undefined;
2700
+ rows?: string[][] | undefined;
2701
+ notes?: string[] | undefined;
2702
+ } | undefined;
2703
+ }>;
2704
+
2705
+ declare type Product = z.infer<typeof Product>;
2706
+
2707
+ /**
2708
+ * 4.3 PDP interactive surface, aligned to the DAZN mobile PDP design. Sections render
2709
+ * from server-authoritative contract data: badges, title + price, promo nudge, description
2710
+ * accordion, size + colour selectors, and static shipping/returns. Size and colour are
2711
+ * independent dimensions; the SKU is resolved from both. A required selection must be made
2712
+ * before add-to-cart — tapping without one scrolls to the first missing selector (and flips
2713
+ * the size error). Add-to-cart calls the engine through the session so a 401 silently
2714
+ * re-handshakes.
2715
+ */
2716
+ export declare function ProductDetail({ product, inventory, recommendations }: ProductDetailProps): JSX.Element;
2717
+
2718
+ export declare interface ProductDetailProps {
2719
+ product: Product;
2720
+ inventory: Inventory | null;
2721
+ /** "You may like it" rail — related products from the engine. Empty = rail hidden. */
2722
+ recommendations?: Product[];
2723
+ }
2724
+
2725
+ /**
2726
+ * PDP "Product details" accordion (mobile/pdp/details). Collapsed clamps the body to
2727
+ * three lines with a more/less toggle. Only renders when the catalogue provides a
2728
+ * description — no placeholder copy is invented.
2729
+ */
2730
+ export declare function ProductDetails({ description }: ProductDetailsProps): JSX.Element | null;
2731
+
2732
+ export declare interface ProductDetailsProps {
2733
+ /** Long-form description (Product.description). The block is not rendered when empty. */
2734
+ description: string;
2735
+ }
2736
+
2737
+ /**
2738
+ * PLP grid with cursor-based infinite scroll and a chip FilterBar.
2739
+ *
2740
+ * `initialProducts` is the host's first page — server-rendered where the host can, or
2741
+ * simply fetched before mount. Subsequent pages come from the provider's client, so the
2742
+ * grid inherits the host's session and market without being told about either.
2743
+ *
2744
+ * Filtering is a client-side view over what has loaded; it deliberately does not refetch,
2745
+ * because a filter that resets an infinite scroll loses the fan's place.
2746
+ */
2747
+ export declare function ProductGrid({ category, initialProducts, initialCursor, filters, }: ProductGridProps): JSX.Element;
2748
+
2749
+ export declare interface ProductGridProps {
2750
+ category: string;
2751
+ initialProducts: Product[];
2752
+ initialCursor: string | null;
2753
+ filters?: FilterOption[];
2754
+ }
2755
+
2756
+ /**
2757
+ * Optional PDP promo nudge (Figma `mobile/pdp/promo`). DISPLAY ONLY — the `message` and
2758
+ * the `ends_at` countdown are presentational; the actual offer is applied by DAZN at
2759
+ * checkout. The storefront never computes a discount from this.
2760
+ */
2761
+ declare const ProductPromo: z.ZodObject<{
2762
+ message: z.ZodString;
2763
+ /** ISO-8601 end time that drives the countdown; absent = show the message, no timer. */
2764
+ ends_at: z.ZodOptional<z.ZodString>;
2765
+ }, "strip", z.ZodTypeAny, {
2766
+ message: string;
2767
+ ends_at?: string | undefined;
2768
+ }, {
2769
+ message: string;
2770
+ ends_at?: string | undefined;
2771
+ }>;
2772
+
2773
+ declare type ProductPromo = z.infer<typeof ProductPromo>;
2774
+
2775
+ /**
2776
+ * Product card (Figma `mobile/product-card`) — the shared rail/grid card. Media is a
2777
+ * 165:220 image (radius-6) with a TOP PICK flag top-left and a green sale badge
2778
+ * bottom-left; below sit the title (2-line clamp) and price with a struck was-price.
2779
+ */
2780
+ export declare function ProductTile({ product, priority }: ProductTileProps): JSX.Element;
2781
+
2782
+ export declare interface ProductTileProps {
2783
+ product: Product;
2784
+ /** Priority-load the image (above-the-fold tiles). */
2785
+ priority?: boolean;
2786
+ }
2787
+
2788
+ /** `GET /v1/catalog/products` — a single purchasable variant of a product. */
2789
+ declare const ProductVariant: z.ZodObject<{
2790
+ sku: z.ZodString;
2791
+ attributes: z.ZodRecord<z.ZodString, z.ZodString>;
2792
+ in_stock: z.ZodBoolean;
2793
+ /**
2794
+ * Optional per-variant image — e.g. the colour swatch/tile shown in the PDP colour
2795
+ * selector. Absent = fall back to the product's `media`.
2796
+ */
2797
+ image: z.ZodOptional<z.ZodString>;
2798
+ /**
2799
+ * Optional per-variant stock count, used to show "N left" low-stock badges. Display
2800
+ * only — the authoritative check runs at checkout. Absent = show no count.
2801
+ */
2802
+ available: z.ZodOptional<z.ZodNumber>;
2803
+ }, "strip", z.ZodTypeAny, {
2804
+ sku: string;
2805
+ attributes: Record<string, string>;
2806
+ in_stock: boolean;
2807
+ image?: string | undefined;
2808
+ available?: number | undefined;
2809
+ }, {
2810
+ sku: string;
2811
+ attributes: Record<string, string>;
2812
+ in_stock: boolean;
2813
+ image?: string | undefined;
2814
+ available?: number | undefined;
2815
+ }>;
2816
+
2817
+ declare type ProductVariant = z.infer<typeof ProductVariant>;
2818
+
2819
+ /**
2820
+ * A whole product-detail screen, data fetching included — a **level 1 view**.
2821
+ *
2822
+ * Fetches the product, then enriches it with inventory and recommendations. Those two are
2823
+ * deliberately best-effort: the PDP renders perfectly well without either, so a failure in
2824
+ * one must not fault the screen. `Promise.allSettled` rather than `all` is what makes that
2825
+ * true — with `all`, a recommendations outage would blank a page the fan could otherwise
2826
+ * have bought from.
2827
+ *
2828
+ * `sku_not_found` is separated from the generic error path because it is not a fault: the
2829
+ * product is gone, and "this is no longer available" is the honest thing to say. An error
2830
+ * card inviting a retry would be a lie.
2831
+ */
2832
+ export declare function ProductView({ sku, initialData, showRecommendations, onNotFound, }: ProductViewProps): JSX.Element;
2833
+
2834
+ export declare interface ProductViewProps {
2835
+ /** The product's SKU. */
2836
+ sku: string;
2837
+ /**
2838
+ * An already-fetched product, from a host that server-rendered this screen. Painted on
2839
+ * the first frame and revalidated in the background.
2840
+ *
2841
+ * `inventory` is optional even here: it is the one field guaranteed to be stale by the
2842
+ * time a server-rendered page is interactive, so a host is not obliged to have fetched
2843
+ * it, and the revalidation replaces it either way.
2844
+ */
2845
+ initialData?: {
2846
+ product: Product;
2847
+ inventory?: Inventory | null;
2848
+ };
2849
+ /** The "you may like it" rail. Default `true`. */
2850
+ showRecommendations?: boolean;
2851
+ /**
2852
+ * Replaces the built-in empty state when the SKU does not exist. Only for
2853
+ * `sku_not_found` — a genuine fault still renders the recoverable error state.
2854
+ */
2855
+ onNotFound?: () => ReactNode;
2856
+ }
2857
+
2858
+ /**
2859
+ * PDP promo banner (Figma `mobile/pdp/promo`). DISPLAY ONLY — the message and countdown
2860
+ * are a merchandising nudge; the actual offer is applied by DAZN at checkout. The timer
2861
+ * is mounted client-side (starts null to match SSR, so there is no hydration mismatch)
2862
+ * and hides itself once elapsed or when the promo has no `ends_at`.
2863
+ */
2864
+ export declare function PromoBanner({ promo }: {
2865
+ promo: ProductPromo;
2866
+ }): JSX.Element;
2867
+
2868
+ /**
2869
+ * Quantity stepper. At qty=1 the decrement turns into a delete affordance; the
2870
+ * increment is capped at max stock. Values are never used to compute money.
2871
+ *
2872
+ * When the cap is reached the reason is stated inline rather than the control simply
2873
+ * going dead — a disabled `+` with no explanation reads as a bug, and the fan cannot
2874
+ * tell "we have no more" from "this is broken".
2875
+ */
2876
+ export declare function QtyStepper({ qty, max, onChange, onRemove, disabled }: QtyStepperProps): JSX.Element;
2877
+
2878
+ export declare interface QtyStepperProps {
2879
+ qty: number;
2880
+ /** Max selectable quantity (capped at available stock). */
2881
+ max?: number;
2882
+ onChange: (qty: number) => void;
2883
+ /** Called when decrementing from 1 (the decrement becomes a delete). */
2884
+ onRemove?: () => void;
2885
+ disabled?: boolean;
2886
+ }
2887
+
2888
+ /**
2889
+ * Horizontally scrollable product rail. Renders NOTHING when there are no products
2890
+ * (empty rails are hidden entirely, per spec).
2891
+ */
2892
+ export declare function RailCarousel({ title, products, seeAllTo, inset }: RailCarouselProps): JSX.Element | null;
2893
+
2894
+ export declare interface RailCarouselProps {
2895
+ title: string;
2896
+ products: Product[];
2897
+ /**
2898
+ * "See all" destination, as a storefront route.
2899
+ *
2900
+ * Was a raw `href` on a plain anchor. In a mounted component that hard-navigates the
2901
+ * HOST's page out from under the fan — the exact thing the navigation seam exists to
2902
+ * prevent — so it now goes through {@link ShopLink} and the host's router.
2903
+ */
2904
+ seeAllTo?: ShopRoute;
2905
+ /**
2906
+ * Inset variant: the rail lives inside an already-padded column (the PDP content
2907
+ * block) rather than bleeding edge-to-edge like the home rails, so it adds no side
2908
+ * padding of its own and uses fixed-width cards.
2909
+ */
2910
+ inset?: boolean;
2911
+ }
2912
+
2913
+ /**
2914
+ * Resolve an asset path against the host-configured asset base.
2915
+ *
2916
+ * Mounted in DAZN's page, a root-relative `src` resolves against DAZN's origin — so the
2917
+ * SDK's own design imagery is requested from a server that has never heard of it. This
2918
+ * is the one rewrite that fixes it, and it is deliberately narrow.
2919
+ *
2920
+ * Left alone, always:
2921
+ * - **absolute URLs** (`https://…`, `data:`, `blob:`) — catalogue media arrives from the
2922
+ * API already absolute, and rewriting it would break the images that currently work.
2923
+ * - **protocol-relative** (`//cdn…`) — already origin-bearing.
2924
+ * - **relative** (`./x`, `x.jpg`) — resolves against the document, which is the host's
2925
+ * business, not ours.
2926
+ *
2927
+ * With no base configured this is the identity function, so our own app — which serves
2928
+ * these paths itself — needs no configuration and keeps working unchanged.
2929
+ */
2930
+ export declare function resolveAssetUrl(src: string, base?: string): string;
2931
+
2932
+ /**
2933
+ * Resolve the variant SKU for the selected size/colour, considering only the dimensions
2934
+ * the product actually has. Returns null until every present dimension is chosen.
2935
+ */
2936
+ export declare function resolveSku(variants: ProductVariant[], size: string | null, colour: string | null): string | null;
2937
+
2938
+ declare interface Review {
2939
+ quote: string;
2940
+ author: string;
2941
+ }
2942
+
2943
+ /** Testimonial carousel — swipeable light cards with dot pagination. */
2944
+ export declare function ReviewCarousel({ quotes }: {
2945
+ quotes: Review[];
2946
+ }): JSX.Element;
2947
+
2948
+ /**
2949
+ * The single green sale/discount badge (Figma `mobile/badge/sale`): success-soft
2950
+ * surface, success text, a discount-tag icon and `-NN%`. One look everywhere — the PDP
2951
+ * price row and the product-card image overlay both use it.
2952
+ */
2953
+ export declare function SaleBadge({ pct }: SaleBadgeProps): JSX.Element;
2954
+
2955
+ export declare interface SaleBadgeProps {
2956
+ /** Whole-number discount percentage, e.g. 30 → "-30%". */
2957
+ pct: number;
2958
+ }
2959
+
2960
+ /**
2961
+ * What a whole screen looks like while its data is in flight.
2962
+ *
2963
+ * This replaces `<Skeleton />` on its own, which was not a loading state at all: with no
2964
+ * width, height or content, a `display: block` span computes to **zero pixels tall**. The
2965
+ * views returned it, it painted nothing, and whatever the host had behind us showed
2966
+ * through — which in DAZN's dark app is a black screen between tapping a link and the
2967
+ * product arriving. It looked like a navigation bug and was a CSS one.
2968
+ *
2969
+ * Two things follow from that and are the whole point of this component. It has real
2970
+ * dimensions, so there is something on screen for the entire wait. And it is a
2971
+ * `ShopSurface`, so the wait is painted in the palette of the screen you are waiting for
2972
+ * — a light PDP that resolves out of a light wait, rather than out of the host's black.
2973
+ *
2974
+ * The blocks are laid out roughly where the real content lands, so the screen settles into
2975
+ * place instead of rearranging itself once the data is in.
2976
+ */
2977
+ export declare function ScreenSkeleton({ tone, shape }: ScreenSkeletonProps): JSX.Element;
2978
+
2979
+ export declare interface ScreenSkeletonProps {
2980
+ /** The palette of the screen being waited for, so the wait is not a different colour. */
2981
+ tone: ShopTone;
2982
+ /** `media` leads with an image block (PDP, category). `list` is rows (cart, search). */
2983
+ shape?: 'media' | 'list';
2984
+ }
2985
+
2986
+ /** Request: the single-use DAZN identity JWT to exchange. */
2987
+ declare const SessionExchangeRequest: z.ZodObject<{
2988
+ token: z.ZodString;
2989
+ }, "strict", z.ZodTypeAny, {
2990
+ token: string;
2991
+ }, {
2992
+ token: string;
2993
+ }>;
2994
+
2995
+ declare type SessionExchangeRequest = z.infer<typeof SessionExchangeRequest>;
2996
+
2997
+ /**
2998
+ * PDP shipping & returns block (mobile/pdp/shipping-info). Static informational copy —
2999
+ * no free-shipping threshold (per design). The shopper picks the delivery tier at
3000
+ * DAZN's checkout; this only signals that the options exist. Returns window is fixed.
3001
+ */
3002
+ export declare function ShippingInfo(): JSX.Element;
3003
+
3004
+ declare interface ShopContextValue {
3005
+ api: DaznClient;
3006
+ market?: Market;
3007
+ theme: 'light' | 'dark' | 'auto';
3008
+ notifications: 'internal' | 'host';
3009
+ locale?: string;
3010
+ assetBaseUrl?: string;
3011
+ emit: OnEvent;
3012
+ /** Listen to the same event stream the host sees. Returns an unsubscribe. */
3013
+ subscribe: (listener: OnEvent) => () => void;
3014
+ navigate: (to: ShopRoute) => void;
3015
+ }
3016
+
3017
+ export declare type ShopEvent = {
3018
+ type: 'ready';
3019
+ } | {
3020
+ type: 'navigate';
3021
+ to: ShopRoute;
3022
+ } | {
3023
+ type: 'product_viewed';
3024
+ sku: string;
3025
+ } | {
3026
+ type: 'cart_updated';
3027
+ itemCount: number;
3028
+ subtotal: Money;
3029
+ } | {
3030
+ type: 'add_to_cart';
3031
+ sku: string;
3032
+ qty: number;
3033
+ } | {
3034
+ type: 'remove_from_cart';
3035
+ sku: string;
3036
+ qty: number;
3037
+ } | {
3038
+ type: 'checkout_handoff';
3039
+ cartId: string;
3040
+ url?: string;
3041
+ } | {
3042
+ type: 'session_required';
3043
+ action: string;
3044
+ } | {
3045
+ type: 'session_expired';
3046
+ } | {
3047
+ type: 'notification';
3048
+ tone: 'neutral' | 'success' | 'critical';
3049
+ message: string;
3050
+ } | {
3051
+ type: 'error';
3052
+ code: ErrorCode | undefined;
3053
+ scope: string;
3054
+ };
3055
+
3056
+ /**
3057
+ * The 56px shop header from Figma `mobile/header` (context=shop): back chevron on the
3058
+ * left, cart and overflow on the right.
3059
+ *
3060
+ * **Optional, and off by default in every view.** In the real embed the DAZN app provides
3061
+ * this chrome — it is their back button and their menu — which is why nothing in the SDK
3062
+ * renders it for you. It is exported because DAZN's team asked for it after finding the
3063
+ * bar they see in our own storefront was not available to them: rebuilding it is a waste
3064
+ * if the design is ours, and a mismatch if only half of it is.
3065
+ *
3066
+ * The cart count is real, never fabricated: `useCartCount` returns `null` until it knows,
3067
+ * and the badge stays hidden for both `null` and `0` — an empty cart wearing a "0" is
3068
+ * noise, and a guessed number is worse.
3069
+ *
3070
+ * Mount it inside `DaznShopProvider`, and inside `CartSheet` if `showCart` is on, since
3071
+ * the trigger opens that sheet.
3072
+ */
3073
+ export declare function ShopHeader({ onBack, onMenu, showCart, backLabel, menuLabel, className, }: ShopHeaderProps): JSX.Element;
3074
+
3075
+ export declare interface ShopHeaderProps {
3076
+ /**
3077
+ * Back control. Omit it and the slot stays empty but reserved — a host whose own
3078
+ * navigation already provides a back affordance should not get a second one.
3079
+ */
3080
+ onBack?: () => void;
3081
+ /** Overflow menu. Omitted by default: a control that opens nothing is worse than none. */
3082
+ onMenu?: () => void;
3083
+ /** The cart icon and its live count. */
3084
+ showCart?: boolean;
3085
+ /** Accessible name for the back control, for a host that is not in English. */
3086
+ backLabel?: string;
3087
+ menuLabel?: string;
3088
+ className?: string;
3089
+ }
3090
+
3091
+ /**
3092
+ * The replacement for `next/image` inside the SDK.
3093
+ *
3094
+ * `next/image` cannot come with us: it needs the Next runtime and its optimiser
3095
+ * endpoint, and the SDK runs in DAZN's bundle where neither exists. So this is a plain
3096
+ * `<img>` with the platform features that actually carry the weight — native lazy
3097
+ * loading, `decoding="async"`, `sizes`, and `fetchpriority` for the LCP image.
3098
+ *
3099
+ * **What is genuinely lost is on-the-fly resizing and format negotiation** (AVIF/WebP
3100
+ * per browser). That is not something a component can polyfill — it is a serving
3101
+ * concern. Whoever serves the catalogue media should emit `srcset`-ready derivatives
3102
+ * and modern formats; until then, this renders the source as given rather than
3103
+ * pretending to optimise it.
3104
+ *
3105
+ * The one rewrite it does perform is rebasing **root-relative** paths onto the host's
3106
+ * `assetBaseUrl` — see {@link resolveAssetUrl}. Without it the SDK's own design imagery
3107
+ * is requested from the host's origin, which does not serve it.
3108
+ */
3109
+ export declare function ShopImage({ src, alt, fill, width, height, sizes, className, style, priority, }: ShopImageProps): JSX.Element;
3110
+
3111
+ export declare interface ShopImageProps {
3112
+ src: string;
3113
+ alt: string;
3114
+ /**
3115
+ * Fill the positioned ancestor, as `next/image`'s `fill` does. The parent must be
3116
+ * `position: relative` (or absolute/fixed) — same requirement, same reason.
3117
+ */
3118
+ fill?: boolean;
3119
+ width?: number;
3120
+ height?: number;
3121
+ sizes?: string;
3122
+ className?: string;
3123
+ style?: CSSProperties;
3124
+ /**
3125
+ * Above-the-fold. Sets `fetchpriority="high"` and opts out of lazy loading.
3126
+ *
3127
+ * Named `priority` to match the prop the components already pass, so porting a
3128
+ * component is an import change rather than a rename at every call site.
3129
+ */
3130
+ priority?: boolean;
3131
+ }
3132
+
3133
+ /**
3134
+ * The replacement for `next/link` inside the SDK. **We never touch the address bar** —
3135
+ * this calls the host's `navigate` and lets the host decide what a route change means.
3136
+ *
3137
+ * It still renders a real `<a href>` rather than a `<div onClick>`, because that is what
3138
+ * gives keyboard activation, focus order, a middle-click that opens a new tab, and a
3139
+ * screen-reader announcement of "link" — none of which a click handler provides. The
3140
+ * default is then prevented so the host router, not the browser, performs the
3141
+ * navigation.
3142
+ *
3143
+ * Modified clicks (⌘/ctrl/shift/alt, or a non-primary button) are deliberately left
3144
+ * alone: a fan holding ⌘ is asking the *browser* for a new tab, and intercepting that
3145
+ * is the single most irritating thing a client-side router can do.
3146
+ */
3147
+ export declare function ShopLink({ to, children, ...rest }: ShopLinkProps): JSX.Element;
3148
+
3149
+ export declare interface ShopLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'onClick'> {
3150
+ to: ShopRoute;
3151
+ children: ReactNode;
3152
+ }
3153
+
3154
+ /**
3155
+ * The public surface DAZN programs against. Everything here is a **contract**, not an
3156
+ * implementation detail — see the versioning policy in the component SDK specification.
3157
+ */
3158
+ /** A storefront-relative identifier. NOT a URL we expect to exist on the host. */
3159
+ export declare type ShopRoute = '/' | `/category/${string}` | `/product/${string}` | '/cart';
3160
+
3161
+ /**
3162
+ * Request: the shopping session id and the one-time code, exactly as they arrive on the
3163
+ * redirect URL's `session` and `code` query parameters.
3164
+ *
3165
+ * Named for what DAZN's redirect calls them rather than for what `validate-session` calls
3166
+ * them — DAZN's own two endpoints disagree (`session` on the way in, `sessionId` on the
3167
+ * way out), and the storefront only ever sees the redirect.
3168
+ */
3169
+ declare const ShopSessionRequest: z.ZodObject<{
3170
+ session: z.ZodString;
3171
+ code: z.ZodString;
3172
+ }, "strict", z.ZodTypeAny, {
3173
+ code: string;
3174
+ session: string;
3175
+ }, {
3176
+ code: string;
3177
+ session: string;
3178
+ }>;
3179
+
3180
+ declare type ShopSessionRequest = z.infer<typeof ShopSessionRequest>;
3181
+
3182
+ /**
3183
+ * The root element of a storefront screen: base typography, element defaults, and a
3184
+ * palette.
3185
+ *
3186
+ * Both halves of this were previously the host's job by accident. The typography lived in
3187
+ * our Next app's `globals.css` on `<body>`, and the dark palette was switched on by a
3188
+ * route-watching `ThemeController` that set `data-theme` on `<html>`. Neither travelled
3189
+ * with the components into this package, so a host mounting `<CategoryView>` got a screen
3190
+ * designed dark rendered on a light palette in the host's own body font — every component
3191
+ * individually correct, the screen as a whole wrong.
3192
+ *
3193
+ * **`tone` is the only input, and the provider's `theme` no longer overrides it.**
3194
+ *
3195
+ * It used to. The reasoning was that a host wanting one palette throughout should be able
3196
+ * to say so — but the storefront has no light and dark of the same screen to choose
3197
+ * between. Each screen is drawn once, in one palette: the shop entry and category pages
3198
+ * are dark, the PDP and cart are light. There is no light version of the dark hero, so
3199
+ * `theme="light"` did not render one; it repainted the tokens underneath a screen whose
3200
+ * artwork, photography and contrast were all built for the other palette.
3201
+ *
3202
+ * DAZN set `theme="light"` and got a dark hero photograph with near-black button text on
3203
+ * it — a screen that exists in no design, produced by an option that sounded reasonable.
3204
+ * The prop is still accepted so that setting it does not break a host's build, and it
3205
+ * still reaches the context for hosts reading it, but it no longer decides what a screen
3206
+ * looks like. Black screens stay black; light screens stay light.
3207
+ *
3208
+ * `data-shop-theme` is a real attribute rather than a hashed module class so the token
3209
+ * remap can live once in `theme.css` beside the values it overrides, and so a host can
3210
+ * scope our palette onto a subtree of their own if they ever need to.
3211
+ *
3212
+ * **Anything that paints its own background must be one of these, not merely look like
3213
+ * one.** Setting `background: var(--color-surface-default)` on an element without also
3214
+ * setting `color` leaves the text inheriting from the host's `<body>` — so a light sheet
3215
+ * in a dark host app renders white text on white. That is what happened to the cart
3216
+ * bottom sheet, and it was invisible in our own app because our `<body>` happened to
3217
+ * carry a matching colour. Hence the forwarded props and ref: a surface has to be usable
3218
+ * **as** a dialog or a layout element, or callers will reach for the CSS shortcut again.
3219
+ */
3220
+ export declare const ShopSurface: ForwardRefExoticComponent<ShopSurfaceProps & RefAttributes<HTMLDivElement>>;
3221
+
3222
+ export declare interface ShopSurfaceProps extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
3223
+ /**
3224
+ * The palette this screen is **designed** in — dark for the shop entry and category,
3225
+ * light for the PDP and cart. This is the whole answer: nothing overrides it.
3226
+ */
3227
+ tone: ShopTone;
3228
+ /**
3229
+ * Paint the full height of the viewport, not just the height of the content.
3230
+ *
3231
+ * On for a screen, off for anything mounted inside one. A screen that stops at its
3232
+ * content leaves the host's own background showing below it, and the host's background
3233
+ * is not ours to predict — DAZN's app is dark, so a short light cart rendered as a white
3234
+ * card floating on black.
3235
+ */
3236
+ fill?: boolean;
3237
+ children: ReactNode;
3238
+ className?: string;
3239
+ }
3240
+
3241
+ export declare type ShopTone = 'dark' | 'light';
3242
+
3243
+ /**
3244
+ * Category-wide sizing help, shown in the PDP "Size guide" overlay.
3245
+ *
3246
+ * `rows` is a measurement table (one row per size). It is **optional and may be empty**:
3247
+ * DAZN has not supplied a measurement chart, and garment measurements are the kind of
3248
+ * value that must never be guessed — a fabricated chest measurement sends a fan the
3249
+ * wrong size. A guide with `notes` but no `rows` is valid and renders as fit guidance
3250
+ * alone; a consumer must not synthesise the missing numbers.
3251
+ *
3252
+ * The array fields are `.optional()` rather than `.default([])` deliberately. A zod
3253
+ * default makes the schema's input and output types diverge, and this package ships two
3254
+ * declaration copies (`dist/` for the root export, `client/` for the typed client) — so
3255
+ * a divergent field makes the two `Product` types structurally unequal and every
3256
+ * consumer that mixes the entry points fails to compile.
3257
+ */
3258
+ declare const SizeGuide: z.ZodObject<{
3259
+ title: z.ZodString;
3260
+ /** Column headings for `rows`, e.g. `['Size', 'Chest (cm)', 'Length (cm)']`. */
3261
+ columns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
3262
+ /** One row per size; each row's length matches `columns`. */
3263
+ rows: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodString, "many">, "many">>;
3264
+ /** Free-text fit guidance, e.g. "Relaxed, slightly oversized fit". */
3265
+ notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
3266
+ }, "strip", z.ZodTypeAny, {
3267
+ title: string;
3268
+ columns?: string[] | undefined;
3269
+ rows?: string[][] | undefined;
3270
+ notes?: string[] | undefined;
3271
+ }, {
3272
+ title: string;
3273
+ columns?: string[] | undefined;
3274
+ rows?: string[][] | undefined;
3275
+ notes?: string[] | undefined;
3276
+ }>;
3277
+
3278
+ declare type SizeGuide = z.infer<typeof SizeGuide>;
3279
+
3280
+ /**
3281
+ * The category-wide size-guide overlay (28 Jul kickoff: "a category-wide size guide
3282
+ * overlay pop-up"), presented as a bottom sheet to match the other PDP overlays.
3283
+ *
3284
+ * Renders whatever the guide actually carries. DAZN has supplied **no measurement
3285
+ * chart**, so today every guide has `notes` and no `rows`, and the table simply does not
3286
+ * appear — the component must never fabricate measurements to fill it, because a wrong
3287
+ * chest figure sends a fan the wrong size. When the real chart arrives the table renders
3288
+ * with no code change.
3289
+ */
3290
+ export declare function SizeGuideSheet({ guide, onClose }: {
3291
+ guide: SizeGuide;
3292
+ onClose: () => void;
3293
+ }): JSX.Element;
3294
+
3295
+ export declare interface SizeOption {
3296
+ value: string;
3297
+ available: boolean;
3298
+ }
3299
+
3300
+ /**
3301
+ * Distinct sizes. `available` is true when a variant of that size is in stock — filtered
3302
+ * to the given colour when one is selected (so picking a colour disables its unavailable
3303
+ * sizes).
3304
+ */
3305
+ export declare function sizeOptions(variants: ProductVariant[], colour?: string | null): SizeOption[];
3306
+
3307
+ /**
3308
+ * PDP size selector — square chips (per the DAZN mobile/pdp/size component). A selection
3309
+ * is REQUIRED before add-to-cart; out-of-stock sizes are disabled. On Add without a
3310
+ * choice the parent flips `error` and the message + scroll target appear (buttons stay
3311
+ * enabled per the design note). Chips are a real radiogroup for keyboard + SR support.
3312
+ */
3313
+ export declare function SizeSelector({ sizes, selected, onSelect, error, guide, }: SizeSelectorProps): JSX.Element;
3314
+
3315
+ export declare interface SizeSelectorProps {
3316
+ sizes: SizeOption[];
3317
+ /** Selected size value, or null when none is chosen yet. */
3318
+ selected: string | null;
3319
+ onSelect: (value: string) => void;
3320
+ /** Show the "select a size" error (set after Add-to-cart is tapped with no selection). */
3321
+ error?: boolean;
3322
+ /**
3323
+ * Category-wide sizing help. Absent = the control is not rendered at all, rather than
3324
+ * opening an empty overlay — until DAZN supplies a measurement chart most categories
3325
+ * have no guide, and a dead "Size guide" button is worse than none.
3326
+ */
3327
+ guide?: SizeGuide;
3328
+ }
3329
+
3330
+ /**
3331
+ * Content placeholder for loading states. Prefer skeletons over full-page spinners.
3332
+ * Marked aria-hidden and respects reduced motion (shimmer disabled globally).
3333
+ */
3334
+ export declare function Skeleton({ width, height, radius, className, circle }: SkeletonProps): JSX.Element;
3335
+
3336
+ export declare interface SkeletonProps {
3337
+ width?: number | string;
3338
+ height?: number | string;
3339
+ radius?: number | string;
3340
+ className?: string;
3341
+ /** Render as a circle (avatars/thumbnails). */
3342
+ circle?: boolean;
3343
+ }
3344
+
3345
+ /** Order-status pill mapping the OrderStatus enum to a success/neutral/critical tone. */
3346
+ export declare function StatusBadge({ status }: StatusBadgeProps): JSX.Element;
3347
+
3348
+ export declare interface StatusBadgeProps {
3349
+ status: OrderStatus;
3350
+ }
3351
+
3352
+ /** Bottom-pinned CTA bar, safe-area aware. Holds one or two primary actions. */
3353
+ export declare function StickyCtaBar({ children, info }: StickyCtaBarProps): JSX.Element;
3354
+
3355
+ export declare interface StickyCtaBarProps {
3356
+ children: ReactNode;
3357
+ /** Optional content shown above the actions (e.g. a total). */
3358
+ info?: ReactNode;
3359
+ }
3360
+
3361
+ export declare function swatchColour(name: string): string;
3362
+
3363
+ /** An optional inline action, e.g. the undo offered after removing a cart line. */
3364
+ export declare interface ToastAction {
3365
+ label: string;
3366
+ onAction: () => void;
3367
+ }
3368
+
3369
+ declare interface ToastApi {
3370
+ show: (message: string, tone?: ToastTone, action?: ToastAction) => void;
3371
+ }
3372
+
3373
+ export declare function ToastProvider({ children }: {
3374
+ children: ReactNode;
3375
+ }): JSX.Element;
3376
+
3377
+ export declare type ToastTone = 'neutral' | 'success' | 'critical';
3378
+
3379
+ /** Resolve UI copy + behaviour for an API error code (unknown → generic retriable). */
3380
+ export declare function treatmentFor(code: ErrorCode | undefined): ErrorTreatment;
3381
+
3382
+ /** `PATCH /v1/cart/items/{sku}` — change the quantity. */
3383
+ declare const UpdateCartItemRequest: z.ZodObject<{
3384
+ qty: z.ZodNumber;
3385
+ }, "strict", z.ZodTypeAny, {
3386
+ qty: number;
3387
+ }, {
3388
+ qty: number;
3389
+ }>;
3390
+
3391
+ declare type UpdateCartItemRequest = z.infer<typeof UpdateCartItemRequest>;
3392
+
3393
+ /**
3394
+ * Resolve an SDK asset path against the host's configured base. Identity outside a
3395
+ * provider, and identity when no base is configured — so our own app, which serves these
3396
+ * paths itself, needs no configuration.
3397
+ */
3398
+ export declare function useAssetUrl(): (src: string) => string;
3399
+
3400
+ /**
3401
+ * How many units are in the cart, for a header badge.
3402
+ *
3403
+ * `null` means **not known** — no provider, no session yet, or the read failed — and is
3404
+ * deliberately distinct from `0`. A badge must render nothing for `null` and nothing for
3405
+ * `0`, but a caller that wants to say "your cart is empty" needs to tell the two apart,
3406
+ * and a hook that collapses them takes that away.
3407
+ *
3408
+ * Reads once on mount, then follows the `cart_updated` event rather than re-fetching. The
3409
+ * event carries the authoritative count from the mutation's own response, so the badge is
3410
+ * exact rather than optimistic, and adding from the PDP updates a badge in the header
3411
+ * without either component knowing the other exists.
3412
+ *
3413
+ * Counts **units, not lines** — three of one tee reads as 3. That is the convention the
3414
+ * `cart_updated` event already uses, and the two must not disagree.
3415
+ */
3416
+ export declare function useCartCount(): number | null;
3417
+
3418
+ /**
3419
+ * Access the cart sheet. Returns `null` outside the provider rather than throwing, so a
3420
+ * component can offer "open the sheet" where it is available and fall back to the
3421
+ * `/cart` route where it is not — a header rendered in a test harness without the
3422
+ * provider should not crash the harness.
3423
+ */
3424
+ export declare function useCartSheet(): CartSheetApi | null;
3425
+
3426
+ /**
3427
+ * Format server-authoritative {@link Money} for display. Formatting ONLY — never
3428
+ * computes totals, tax or discounts, and never does float arithmetic on money.
3429
+ *
3430
+ * The locale resolution order changed when this moved into the SDK, and the order
3431
+ * matters: **provider `locale` → provider `market.lang` → browser locale**.
3432
+ *
3433
+ * The app version read `navigator.language` alone. Mounted in a host page that is
3434
+ * already rendering a chosen market, the browser locale is the *wrong* answer — a fan
3435
+ * in Spain with an English-language device would see Spanish-market prices formatted to
3436
+ * en-GB conventions. The host tells us the market, so the host's answer wins.
3437
+ *
3438
+ * Works without a provider, falling back to the browser locale exactly as before.
3439
+ * Formatting a price needs no API client, and requiring one would make every component
3440
+ * that shows a price untestable in isolation.
3441
+ */
3442
+ export declare function useMoney(defaultLocale?: string): {
3443
+ format: (money: Money, localeOverride?: string) => string;
3444
+ };
3445
+
3446
+ /**
3447
+ * Navigate to a storefront route, or `null` outside a provider.
3448
+ *
3449
+ * `null` rather than a throw or a no-op: it lets {@link ShopLink} tell the difference
3450
+ * between "the host wants to handle this" and "nobody is listening", and fall back to
3451
+ * being an ordinary link in the second case. A no-op would instead swallow the click and
3452
+ * leave a link that looks fine and does nothing.
3453
+ */
3454
+ export declare function useNavigate(): ((to: ShopRoute) => void) | null;
3455
+
3456
+ /** The typed, schema-validating commerce client. Requires a provider. */
3457
+ export declare function useShopApi(): DaznClient;
3458
+
3459
+ /** Raise an event to the host. Silently dropped outside a provider. */
3460
+ export declare function useShopEvent(): OnEvent;
3461
+
3462
+ /**
3463
+ * Read the shop context, or `null` outside a provider.
3464
+ *
3465
+ * Not every capability needs a provider, and pretending otherwise makes the components
3466
+ * harder to test and harder to adopt piecemeal. Formatting a price and rendering a link
3467
+ * are both meaningful on their own; reaching the API is not. So the strictness is per
3468
+ * capability, below, rather than blanket.
3469
+ */
3470
+ export declare function useShopOptional(): ShopContextValue | null;
3471
+
3472
+ /**
3473
+ * Access the toast API. Always returns something usable.
3474
+ *
3475
+ * This used to throw outside `<ToastProvider>`, and that was wrong on the project's own
3476
+ * principle that strictness is **per capability**: `useShopApi` throws because reaching
3477
+ * the API genuinely cannot degrade, but a toast can. Worse, it made the specification's
3478
+ * opening example — one `<DaznShopProvider>` with `<CartView>` beneath it — crash the
3479
+ * entire tree on mount, because `CartView` raises toasts and nothing had mounted a
3480
+ * provider. With no error boundary above it, a host following our own documentation got
3481
+ * a blank page.
3482
+ *
3483
+ * Without a `<ToastProvider>` the message still goes somewhere useful: it is emitted to
3484
+ * the host as a `notification` event if there is a shop provider to emit through, which
3485
+ * is exactly what `notifications: 'host'` does deliberately. Failing that it is dropped,
3486
+ * because a missing toast is a cosmetic loss and taking the storefront down over one is
3487
+ * not a trade worth making.
3488
+ */
3489
+ export declare function useToast(): ToastApi;
3490
+
3491
+ /**
3492
+ * A one-line label for a chosen variant, e.g. `Black · M`.
3493
+ *
3494
+ * Composed here rather than server-side because the separator and the order are
3495
+ * presentation. Colour leads and size follows, matching how the PDP asks for them;
3496
+ * anything else the catalogue carries is appended in its own order rather than dropped,
3497
+ * so a product with a third dimension is not silently flattened.
3498
+ *
3499
+ * Returns `undefined` for an absent or empty map, so a caller can skip the element
3500
+ * entirely instead of rendering a blank line.
3501
+ */
3502
+ export declare function variantLabel(attributes?: Record<string, string>): string | undefined;
3503
+
3504
+ /**
3505
+ * Variant picker. A selection is REQUIRED before add-to-cart; out-of-stock options are
3506
+ * disabled and cannot be chosen. When nothing is selected the parent blocks the CTA.
3507
+ */
3508
+ export declare function VariantSelector({ variants, selectedSku, onSelect, labelAttribute, }: VariantSelectorProps): JSX.Element;
3509
+
3510
+ export declare interface VariantSelectorProps {
3511
+ variants: ProductVariant[];
3512
+ /** SKU of the selected variant, or null when none is chosen yet. */
3513
+ selectedSku: string | null;
3514
+ onSelect: (sku: string) => void;
3515
+ /** Attribute key to render as the option label (e.g. "size"). */
3516
+ labelAttribute?: string;
3517
+ }
3518
+
3519
+ /** "Why shop with us" reassurance rows. */
3520
+ export declare function WhyUs({ items }: {
3521
+ items: WhyUsItem[];
3522
+ }): JSX.Element;
3523
+
3524
+ declare interface WhyUsItem {
3525
+ icon?: 'payment' | 'delivery' | 'returns';
3526
+ title: string;
3527
+ body: string;
3528
+ }
3529
+
3530
+ export { }