@akinon/pz-theme 2.0.37-rc.0 → 2.0.38-beta.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +15 -31
  2. package/package.json +4 -4
  3. package/src/__tests__/__snapshots__/facet-filters-block.test.tsx.snap +11 -0
  4. package/src/__tests__/custom-css.test.ts +181 -0
  5. package/src/__tests__/designer-chrome-clipping.test.tsx +497 -0
  6. package/src/__tests__/facet-filters-block.test.tsx +707 -0
  7. package/src/__tests__/item-template.test.tsx +184 -0
  8. package/src/__tests__/listing-controls.test.tsx +961 -0
  9. package/src/__tests__/part-style-scope.test.tsx +229 -0
  10. package/src/__tests__/part-styles.test.ts +311 -0
  11. package/src/__tests__/registry-parity.test.ts +42 -0
  12. package/src/__tests__/row-visibility.test.ts +672 -0
  13. package/src/__tests__/sort-select-page-cursor.test.tsx +336 -0
  14. package/src/__tests__/theme-block-html-id.test.tsx +167 -0
  15. package/src/blocks/__tests__/__snapshots__/product-gallery-block.test.tsx.snap +15 -0
  16. package/src/blocks/__tests__/accordion-block-state.test.tsx +201 -0
  17. package/src/blocks/__tests__/facet-filters-block.test.tsx +190 -0
  18. package/src/blocks/__tests__/listing-context-card-model.test.tsx +230 -0
  19. package/src/blocks/__tests__/listing-lookup-hardening.test.ts +80 -0
  20. package/src/blocks/__tests__/product-gallery-block.test.tsx +974 -0
  21. package/src/blocks/__tests__/product-grid-composition.test.tsx +570 -0
  22. package/src/blocks/__tests__/product-grid-lookups.test.tsx +230 -0
  23. package/src/blocks/__tests__/product-price-block.test.tsx +248 -0
  24. package/src/blocks/__tests__/product-recommendations-block.test.tsx +702 -0
  25. package/src/blocks/__tests__/share-block.test.tsx +392 -0
  26. package/src/blocks/__tests__/store-locator-block.test.tsx +691 -0
  27. package/src/blocks/__tests__/variant-selector-block.test.tsx +817 -0
  28. package/src/blocks/__tests__/variant-selector-golden.json +20 -0
  29. package/src/blocks/account-menu-block.tsx +264 -0
  30. package/src/blocks/add-to-cart-block.tsx +417 -0
  31. package/src/blocks/address-list-block.tsx +348 -0
  32. package/src/blocks/applied-filters-block.tsx +288 -0
  33. package/src/blocks/auth-form-block.tsx +295 -0
  34. package/src/blocks/basket-items-block.tsx +457 -0
  35. package/src/blocks/basket-summary-block.tsx +404 -0
  36. package/src/blocks/block-renderer-registry.tsx +94 -0
  37. package/src/blocks/change-password-form-block.tsx +240 -0
  38. package/src/blocks/checkout-steps-block.tsx +288 -0
  39. package/src/blocks/checkout-summary-block.tsx +449 -0
  40. package/src/blocks/countdown-block.tsx +182 -0
  41. package/src/blocks/coupon-list-block.tsx +292 -0
  42. package/src/blocks/facet-filters-block.tsx +1686 -0
  43. package/src/blocks/faq-accordion-block.tsx +358 -0
  44. package/src/blocks/favorite-button-block.tsx +252 -0
  45. package/src/blocks/favourites-grid-block.tsx +510 -0
  46. package/src/blocks/forgot-password-block.tsx +155 -0
  47. package/src/blocks/item-template-utils.ts +131 -0
  48. package/src/blocks/item-template.tsx +164 -0
  49. package/src/blocks/listing-breadcrumb-block.tsx +175 -0
  50. package/src/blocks/listing-context.ts +850 -0
  51. package/src/blocks/listing-pagination-block.tsx +923 -0
  52. package/src/blocks/listing-result-count-block.tsx +126 -0
  53. package/src/blocks/listing-title-block.tsx +115 -0
  54. package/src/blocks/oauth-buttons-block.tsx +205 -0
  55. package/src/blocks/order-detail-block.tsx +474 -0
  56. package/src/blocks/order-list-block.tsx +274 -0
  57. package/src/blocks/order-success-block.tsx +641 -0
  58. package/src/blocks/order-tracking-block.tsx +533 -0
  59. package/src/blocks/product-attributes-block.tsx +307 -0
  60. package/src/blocks/product-block-utils.tsx +639 -0
  61. package/src/blocks/product-breadcrumb-block.tsx +215 -0
  62. package/src/blocks/product-description-block.tsx +169 -0
  63. package/src/blocks/product-gallery-block.tsx +1523 -0
  64. package/src/blocks/product-grid-block.tsx +966 -0
  65. package/src/blocks/product-price-block.tsx +373 -0
  66. package/src/blocks/product-recommendations-block.tsx +1433 -0
  67. package/src/blocks/product-title-block.tsx +129 -0
  68. package/src/blocks/profile-form-block.tsx +297 -0
  69. package/src/blocks/recently-viewed-store.ts +201 -0
  70. package/src/blocks/register-form-block.tsx +362 -0
  71. package/src/blocks/share-block.tsx +667 -0
  72. package/src/blocks/size-guide-block.tsx +313 -0
  73. package/src/blocks/sort-select-block.tsx +636 -0
  74. package/src/blocks/stock-status-block.tsx +168 -0
  75. package/src/blocks/store-locator-block.tsx +1184 -0
  76. package/src/blocks/variant-selector-block.tsx +1110 -0
  77. package/src/sections/__tests__/tablet-raw-bucket-readers.test.tsx +186 -0
  78. package/src/utils/__tests__/part-ownership-guard.test.tsx +29 -4
@@ -0,0 +1,404 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import NextLink from 'next/link';
5
+ import {
6
+ basketApi,
7
+ useGetBasketQuery,
8
+ useApplyVoucherCodeMutation,
9
+ useRemoveVoucherCodeMutation
10
+ } from '@akinon/next/data/client/basket';
11
+ import { useAppDispatch } from '@akinon/next/redux/hooks';
12
+ import { useLocalization } from '@akinon/next/hooks';
13
+ import { getResponsiveValue, partAttrs } from '../utils';
14
+ import { useThemePageContext } from '../theme-page-context';
15
+ import { formatMoney, parseAmount } from './commerce-format';
16
+ import { hasItemTemplate, ItemTemplateList } from './item-template';
17
+ import { readToggle } from './product-block-utils';
18
+ import { BlockRendererProps } from './block-renderer-registry';
19
+
20
+ interface BasketTotalsLike {
21
+ total_amount?: string;
22
+ total_product_amount?: string;
23
+ total_discount_amount?: string;
24
+ total_quantity?: number;
25
+ voucher_code?: string | null;
26
+ basketitem_set?: Array<{ currency_type?: string }>;
27
+ }
28
+
29
+ interface TotalsRow {
30
+ key: string;
31
+ label: string;
32
+ value: string;
33
+ /** `total` is the grand total; it carries its own part and weight. */
34
+ type: 'subtotal' | 'discount' | 'total';
35
+ }
36
+
37
+ /**
38
+ * Last-resort checkout target. Only used when the editor left `checkoutHref`
39
+ * empty and no page context supplied one.
40
+ */
41
+ const DEFAULT_CHECKOUT_HREF = '/orders/checkout';
42
+
43
+ /**
44
+ * Functional commerce block: basket totals, optional voucher field and the
45
+ * checkout call to action. Totals stay commerce-driven; the editor owns the
46
+ * checkout label/target, every row label, whether the voucher field is exposed
47
+ * and the styling.
48
+ *
49
+ * Parts: root, totals, row, row-label, row-value, total, total-label,
50
+ * total-value, coupon, coupon-applied, coupon-code, coupon-remove,
51
+ * coupon-input, coupon-button, coupon-error, cta, empty. A child marked as the
52
+ * item template replaces the built-in totals row.
53
+ *
54
+ * Checkout target resolution, highest priority first:
55
+ * 1. `checkoutHref` property — authoritative whenever the author sets it.
56
+ * 2. The SSR page context basket `checkoutUrl` (built from the brand ROUTES).
57
+ * 3. `/orders/checkout` — the conventional pz route, last resort only.
58
+ */
59
+ const BasketSummaryBlock = ({
60
+ block,
61
+ placeholderId,
62
+ sectionId,
63
+ selectedBlockId = null,
64
+ currentBreakpoint = 'desktop',
65
+ isDesigner = false
66
+ }: BlockRendererProps) => {
67
+ const prop = (key: string, fallback: string) =>
68
+ String(
69
+ getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
70
+ );
71
+
72
+ const boolProp = (key: string, fallback: boolean) =>
73
+ readToggle(block.properties?.[key], currentBreakpoint, fallback);
74
+
75
+ const showCoupon = boolProp('showCoupon', true);
76
+ const showSubtotal = boolProp('showSubtotal', true);
77
+ const showDiscount = boolProp('showDiscount', true);
78
+ const showCheckout = boolProp('showCheckout', true);
79
+ const checkoutLabel = prop('checkoutLabel', 'Go to checkout');
80
+ const couponLabel = prop('couponLabel', 'Apply');
81
+ const couponPlaceholder = prop('couponPlaceholder', 'Coupon code');
82
+ const couponRemoveLabel = prop('couponRemoveLabel', 'Remove');
83
+ const couponApplyError = prop(
84
+ 'couponApplyError',
85
+ 'Coupon code could not be applied.'
86
+ );
87
+ const couponRemoveError = prop(
88
+ 'couponRemoveError',
89
+ 'Coupon code could not be removed.'
90
+ );
91
+ const subtotalLabel = prop('subtotalLabel', 'Subtotal');
92
+ const discountLabel = prop('discountLabel', 'Discount');
93
+ const totalLabel = prop('totalLabel', 'Total');
94
+ const discountPrefix = prop('discountPrefix', '-');
95
+ const emptyLabel = prop('emptyLabel', '').trim();
96
+
97
+ const pageContext = useThemePageContext();
98
+ const dispatch = useAppDispatch();
99
+ const { locale, currency: localeCurrency } = useLocalization();
100
+ const { data: basket } = useGetBasketQuery(undefined as never);
101
+ const [applyVoucherCode, { isLoading: isApplying }] =
102
+ useApplyVoucherCodeMutation();
103
+ const [removeVoucherCode, { isLoading: isRemoving }] =
104
+ useRemoveVoucherCodeMutation();
105
+
106
+ const [code, setCode] = useState('');
107
+ const [error, setError] = useState('');
108
+
109
+ const contextBasket = (pageContext?.basket ?? null) as {
110
+ totalAmount?: string;
111
+ totalProductAmount?: string;
112
+ totalDiscountAmount?: string;
113
+ voucherCode?: string;
114
+ checkoutUrl?: string;
115
+ items?: Array<{ currency?: string }>;
116
+ } | null;
117
+
118
+ const liveBasket = basket as BasketTotalsLike | undefined;
119
+
120
+ const totals = {
121
+ subtotal:
122
+ liveBasket?.total_product_amount ?? contextBasket?.totalProductAmount,
123
+ discount:
124
+ liveBasket?.total_discount_amount ?? contextBasket?.totalDiscountAmount,
125
+ total: liveBasket?.total_amount ?? contextBasket?.totalAmount,
126
+ voucher: liveBasket?.voucher_code ?? contextBasket?.voucherCode ?? ''
127
+ };
128
+
129
+ // The basket reports the currency it was priced in; the storefront currency
130
+ // is only the fallback for an empty basket.
131
+ const currency =
132
+ liveBasket?.basketitem_set?.[0]?.currency_type ||
133
+ contextBasket?.items?.[0]?.currency ||
134
+ localeCurrency ||
135
+ '';
136
+
137
+ const money = (value?: string | number) =>
138
+ formatMoney(value, { locale, currency });
139
+
140
+ // No page supplies a basket page context yet, so `checkoutUrl` is inert for
141
+ // now; the read is kept because the basket page context is being wired.
142
+ const checkoutHref =
143
+ prop('checkoutHref', '').trim() ||
144
+ contextBasket?.checkoutUrl ||
145
+ DEFAULT_CHECKOUT_HREF;
146
+
147
+ const hasBasket = Boolean(liveBasket || contextBasket);
148
+
149
+ // Rows are built before the early return so the item template and the
150
+ // built-in markup read exactly the same list.
151
+ const rows: TotalsRow[] = [];
152
+
153
+ if (showSubtotal && totals.subtotal !== undefined) {
154
+ rows.push({
155
+ key: 'subtotal',
156
+ label: subtotalLabel,
157
+ value: money(totals.subtotal),
158
+ type: 'subtotal'
159
+ });
160
+ }
161
+
162
+ if (showDiscount && (parseAmount(totals.discount) ?? 0) > 0) {
163
+ rows.push({
164
+ key: 'discount',
165
+ label: discountLabel,
166
+ value: `${discountPrefix}${money(totals.discount)}`,
167
+ type: 'discount'
168
+ });
169
+ }
170
+
171
+ rows.push({
172
+ key: 'total',
173
+ label: totalLabel,
174
+ value: money(totals.total),
175
+ type: 'total'
176
+ });
177
+
178
+ const customRows = hasItemTemplate(block, currentBreakpoint) ? (
179
+ <ItemTemplateList
180
+ block={block}
181
+ items={rows as unknown as Record<string, unknown>[]}
182
+ placeholderId={placeholderId}
183
+ sectionId={sectionId}
184
+ isDesigner={isDesigner}
185
+ selectedBlockId={selectedBlockId}
186
+ currentBreakpoint={currentBreakpoint}
187
+ />
188
+ ) : null;
189
+
190
+ if (!hasBasket) {
191
+ if (!isDesigner && !emptyLabel) return null;
192
+
193
+ return (
194
+ <div
195
+ data-basket-summary=""
196
+ data-basket-summary-state="empty"
197
+ {...partAttrs('empty', { empty: true })}
198
+ >
199
+ {emptyLabel || (isDesigner ? 'Basket summary (no basket data)' : '')}
200
+ </div>
201
+ );
202
+ }
203
+
204
+ // applyVoucherCode/removeVoucherCode declare no invalidatesTags in the core,
205
+ // so the getBasket cache keeps the pre-coupon totals until a hard refresh.
206
+ // Both mutations return the recalculated basket, so it is written straight
207
+ // into the cache — the same approach the brand storefronts use.
208
+ const syncBasketCache = (nextBasket: unknown) => {
209
+ if (!nextBasket || typeof nextBasket !== 'object') return;
210
+
211
+ dispatch(
212
+ basketApi.util.updateQueryData('getBasket', undefined, (draft) => {
213
+ Object.assign(draft as Record<string, unknown>, nextBasket);
214
+ })
215
+ );
216
+ };
217
+
218
+ const submitVoucher = async (event: React.FormEvent) => {
219
+ event.preventDefault();
220
+ setError('');
221
+
222
+ const value = code.trim();
223
+
224
+ if (isDesigner || !value) return;
225
+
226
+ try {
227
+ const nextBasket = await applyVoucherCode({
228
+ voucher_code: value
229
+ }).unwrap();
230
+
231
+ syncBasketCache(nextBasket);
232
+ setCode('');
233
+ } catch {
234
+ setError(couponApplyError);
235
+ }
236
+ };
237
+
238
+ const dropVoucher = async () => {
239
+ setError('');
240
+
241
+ if (isDesigner) return;
242
+
243
+ try {
244
+ const nextBasket = await removeVoucherCode().unwrap();
245
+
246
+ syncBasketCache(nextBasket);
247
+ } catch {
248
+ setError(couponRemoveError);
249
+ }
250
+ };
251
+
252
+ const rowStyle: React.CSSProperties = {
253
+ display: 'flex',
254
+ justifyContent: 'space-between',
255
+ gap: '12px',
256
+ padding: '4px 0'
257
+ };
258
+
259
+ return (
260
+ <div
261
+ data-basket-summary=""
262
+ {...partAttrs('root')}
263
+ style={{
264
+ display: 'flex',
265
+ flexDirection: 'column',
266
+ gap: 'var(--basket-summary-gap, 12px)'
267
+ }}
268
+ >
269
+ <div data-basket-summary-totals="" {...partAttrs('totals')}>
270
+ {/* A designer who marked a child as the row layout gets their own row
271
+ repeated per total; otherwise the built-in rows below render. */}
272
+ {customRows ??
273
+ rows.map((row) =>
274
+ row.type === 'total' ? (
275
+ <div
276
+ key={row.key}
277
+ style={{ ...rowStyle, fontWeight: 600 }}
278
+ data-basket-summary-row="total"
279
+ {...partAttrs('total')}
280
+ >
281
+ <span {...partAttrs('total-label')}>{row.label}</span>
282
+ <span {...partAttrs('total-value')}>{row.value}</span>
283
+ </div>
284
+ ) : (
285
+ <div
286
+ key={row.key}
287
+ style={rowStyle}
288
+ data-basket-summary-row={row.type}
289
+ {...partAttrs('row')}
290
+ >
291
+ <span {...partAttrs('row-label')}>{row.label}</span>
292
+ <span {...partAttrs('row-value')}>{row.value}</span>
293
+ </div>
294
+ )
295
+ )}
296
+ </div>
297
+
298
+ {showCoupon && (
299
+ <div data-basket-summary-coupon="" {...partAttrs('coupon')}>
300
+ {totals.voucher ? (
301
+ <div
302
+ {...partAttrs('coupon-applied', { selected: true })}
303
+ style={{ display: 'flex', gap: '8px', alignItems: 'center' }}
304
+ >
305
+ <span {...partAttrs('coupon-code')}>{totals.voucher}</span>
306
+ <button
307
+ type="button"
308
+ disabled={isRemoving || isDesigner}
309
+ onClick={dropVoucher}
310
+ {...partAttrs('coupon-remove', {
311
+ disabled: isRemoving || isDesigner,
312
+ loading: isRemoving
313
+ })}
314
+ style={{
315
+ background: 'none',
316
+ border: 'none',
317
+ cursor: 'pointer',
318
+ color: 'inherit',
319
+ textDecoration: 'underline',
320
+ padding: 0
321
+ }}
322
+ >
323
+ {couponRemoveLabel}
324
+ </button>
325
+ </div>
326
+ ) : (
327
+ <form onSubmit={submitVoucher} style={{ display: 'flex', gap: '8px' }}>
328
+ <input
329
+ type="text"
330
+ value={code}
331
+ onChange={(event) => setCode(event.target.value)}
332
+ placeholder={couponPlaceholder}
333
+ aria-label={couponPlaceholder}
334
+ aria-invalid={error ? true : undefined}
335
+ {...partAttrs('coupon-input', { invalid: Boolean(error) })}
336
+ style={{
337
+ flex: 1,
338
+ minWidth: 0,
339
+ border: '1px solid var(--basket-summary-border, #d1d5db)',
340
+ background: 'transparent',
341
+ color: 'inherit',
342
+ padding: '8px 12px',
343
+ outline: 'none'
344
+ }}
345
+ />
346
+ <button
347
+ type="submit"
348
+ disabled={isApplying}
349
+ {...partAttrs('coupon-button', {
350
+ disabled: isApplying,
351
+ loading: isApplying
352
+ })}
353
+ style={{
354
+ border: '1px solid currentColor',
355
+ background: 'transparent',
356
+ color: 'inherit',
357
+ cursor: 'pointer',
358
+ padding: '8px 16px'
359
+ }}
360
+ >
361
+ {couponLabel}
362
+ </button>
363
+ </form>
364
+ )}
365
+ {error && (
366
+ <div
367
+ data-basket-summary-error=""
368
+ role="alert"
369
+ {...partAttrs('coupon-error', { invalid: true })}
370
+ style={{ marginTop: '6px' }}
371
+ >
372
+ {error}
373
+ </div>
374
+ )}
375
+ </div>
376
+ )}
377
+
378
+ {showCheckout && (
379
+ <NextLink
380
+ href={isDesigner ? '#' : checkoutHref}
381
+ data-basket-checkout=""
382
+ {...partAttrs('cta')}
383
+ // Navigating inside the designer iframe would drop the author out of
384
+ // the page they are editing, so the CTA is inert there.
385
+ onClick={(event) => {
386
+ if (isDesigner) event.preventDefault();
387
+ }}
388
+ style={{
389
+ display: 'block',
390
+ textAlign: 'center',
391
+ padding: '12px 20px',
392
+ background: 'var(--basket-summary-cta-bg, #111827)',
393
+ color: 'var(--basket-summary-cta-color, #ffffff)',
394
+ textDecoration: 'none'
395
+ }}
396
+ >
397
+ {checkoutLabel}
398
+ </NextLink>
399
+ )}
400
+ </div>
401
+ );
402
+ };
403
+
404
+ export default BasketSummaryBlock;
@@ -18,6 +18,48 @@ import LinkBlock from './link-block';
18
18
  import LottieBlock from './lottie-block';
19
19
  import EmbedBlock from './embed-block';
20
20
  import MapBlock from './map-block';
21
+ import ProductGalleryBlock from './product-gallery-block';
22
+ import ProductTitleBlock from './product-title-block';
23
+ import ProductPriceBlock from './product-price-block';
24
+ import VariantSelectorBlock from './variant-selector-block';
25
+ import AddToCartBlock from './add-to-cart-block';
26
+ import FavoriteButtonBlock from './favorite-button-block';
27
+ import ProductDescriptionBlock from './product-description-block';
28
+ import StockStatusBlock from './stock-status-block';
29
+ import ProductGridBlock from './product-grid-block';
30
+ import ListingTitleBlock from './listing-title-block';
31
+ import FacetFiltersBlock from './facet-filters-block';
32
+ import SortSelectBlock from './sort-select-block';
33
+ import ListingPaginationBlock from './listing-pagination-block';
34
+ import ListingBreadcrumbBlock from './listing-breadcrumb-block';
35
+ import BasketItemsBlock from './basket-items-block';
36
+ import BasketSummaryBlock from './basket-summary-block';
37
+ import AccountMenuBlock from './account-menu-block';
38
+ import OrderListBlock from './order-list-block';
39
+ import AuthFormBlock from './auth-form-block';
40
+ import AddressListBlock from './address-list-block';
41
+ import ProfileFormBlock from './profile-form-block';
42
+ import ChangePasswordFormBlock from './change-password-form-block';
43
+ import OrderDetailBlock from './order-detail-block';
44
+ import CouponListBlock from './coupon-list-block';
45
+ import FavouritesGridBlock from './favourites-grid-block';
46
+ import CheckoutSummaryBlock from './checkout-summary-block';
47
+ import CheckoutStepsBlock from './checkout-steps-block';
48
+ import OrderSuccessBlock from './order-success-block';
49
+ import OrderTrackingBlock from './order-tracking-block';
50
+ import ProductBreadcrumbBlock from './product-breadcrumb-block';
51
+ import ProductAttributesBlock from './product-attributes-block';
52
+ import ProductRecommendationsBlock from './product-recommendations-block';
53
+ import SizeGuideBlock from './size-guide-block';
54
+ import AppliedFiltersBlock from './applied-filters-block';
55
+ import ListingResultCountBlock from './listing-result-count-block';
56
+ import CountdownBlock from './countdown-block';
57
+ import RegisterFormBlock from './register-form-block';
58
+ import ForgotPasswordBlock from './forgot-password-block';
59
+ import OauthButtonsBlock from './oauth-buttons-block';
60
+ import StoreLocatorBlock from './store-locator-block';
61
+ import FaqAccordionBlock from './faq-accordion-block';
62
+ import ShareBlock from './share-block';
21
63
 
22
64
  export interface BlockRendererProps {
23
65
  block: Block;
@@ -73,5 +115,57 @@ blockRendererRegistry.register('link', LinkBlock);
73
115
  blockRendererRegistry.register('lottie', LottieBlock);
74
116
  blockRendererRegistry.register('embed', EmbedBlock);
75
117
  blockRendererRegistry.register('map', MapBlock);
118
+ blockRendererRegistry.register('product-gallery', ProductGalleryBlock);
119
+ blockRendererRegistry.register('product-title', ProductTitleBlock);
120
+ blockRendererRegistry.register('product-price', ProductPriceBlock);
121
+ blockRendererRegistry.register('variant-selector', VariantSelectorBlock);
122
+ blockRendererRegistry.register('add-to-cart', AddToCartBlock);
123
+ blockRendererRegistry.register('favorite-button', FavoriteButtonBlock);
124
+ blockRendererRegistry.register('product-description', ProductDescriptionBlock);
125
+ blockRendererRegistry.register('stock-status', StockStatusBlock);
126
+ blockRendererRegistry.register('product-grid', ProductGridBlock);
127
+ blockRendererRegistry.register('listing-title', ListingTitleBlock);
128
+ blockRendererRegistry.register('facet-filters', FacetFiltersBlock);
129
+ blockRendererRegistry.register('sort-select', SortSelectBlock);
130
+ blockRendererRegistry.register('listing-pagination', ListingPaginationBlock);
131
+ blockRendererRegistry.register('listing-breadcrumb', ListingBreadcrumbBlock);
132
+ blockRendererRegistry.register('basket-items', BasketItemsBlock);
133
+ blockRendererRegistry.register('basket-summary', BasketSummaryBlock);
134
+ blockRendererRegistry.register('account-menu', AccountMenuBlock);
135
+ blockRendererRegistry.register('order-list', OrderListBlock);
136
+ blockRendererRegistry.register('auth-form', AuthFormBlock);
137
+
138
+ // Account
139
+ blockRendererRegistry.register('address-list', AddressListBlock);
140
+ blockRendererRegistry.register('profile-form', ProfileFormBlock);
141
+ blockRendererRegistry.register('change-password-form', ChangePasswordFormBlock);
142
+ blockRendererRegistry.register('order-detail', OrderDetailBlock);
143
+ blockRendererRegistry.register('coupon-list', CouponListBlock);
144
+ blockRendererRegistry.register('favourites-grid', FavouritesGridBlock);
145
+
146
+ // Chrome extras
147
+
148
+ // Order flow
149
+ blockRendererRegistry.register('checkout-summary', CheckoutSummaryBlock);
150
+ blockRendererRegistry.register('checkout-steps', CheckoutStepsBlock);
151
+ blockRendererRegistry.register('order-success', OrderSuccessBlock);
152
+ blockRendererRegistry.register('order-tracking', OrderTrackingBlock);
153
+
154
+ // Catalog extras
155
+ blockRendererRegistry.register('product-breadcrumb', ProductBreadcrumbBlock);
156
+ blockRendererRegistry.register('product-attributes', ProductAttributesBlock);
157
+ blockRendererRegistry.register('product-recommendations', ProductRecommendationsBlock);
158
+ blockRendererRegistry.register('size-guide', SizeGuideBlock);
159
+ blockRendererRegistry.register('applied-filters', AppliedFiltersBlock);
160
+ blockRendererRegistry.register('listing-result-count', ListingResultCountBlock);
161
+ blockRendererRegistry.register('countdown', CountdownBlock);
162
+
163
+ // Auth and content
164
+ blockRendererRegistry.register('register-form', RegisterFormBlock);
165
+ blockRendererRegistry.register('forgot-password', ForgotPasswordBlock);
166
+ blockRendererRegistry.register('oauth-buttons', OauthButtonsBlock);
167
+ blockRendererRegistry.register('store-locator', StoreLocatorBlock);
168
+ blockRendererRegistry.register('faq-accordion', FaqAccordionBlock);
169
+ blockRendererRegistry.register('share', ShareBlock);
76
170
 
77
171
  export default blockRendererRegistry;