@akinon/pz-theme 2.0.37-rc.0 → 2.0.37

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 +7 -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,533 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { buildClientRequestUrl, getCookie } from '@akinon/next/utils';
5
+ import { useLocalization } from '@akinon/next/hooks';
6
+ import { getResponsiveValue, partAttrs } from '../utils';
7
+ import { formatDate, formatMoney } from './commerce-format';
8
+ import { hasItemTemplate, ItemTemplateList } from './item-template';
9
+ import { readToggle } from './product-block-utils';
10
+ import { BlockRendererProps } from './block-renderer-registry';
11
+
12
+ /**
13
+ * Anonymous lookup endpoint. Commerce exposes it under /users/, and the core
14
+ * `account` url map (`anonymousOrderTracking`) is not imported here because
15
+ * that module pulls in brand `settings` at import time. Brands that mount it
16
+ * elsewhere override it through the `endpoint` property.
17
+ */
18
+ const DEFAULT_ENDPOINT = '/users/orders/anonymous/';
19
+
20
+ interface TrackedOrderItem {
21
+ id?: number;
22
+ price?: string;
23
+ status?: { label?: string };
24
+ product?: { pk?: number; name?: string; image?: string } | null;
25
+ }
26
+
27
+ interface TrackedOrder {
28
+ id?: number;
29
+ number?: string;
30
+ amount?: string;
31
+ created_date?: string;
32
+ currency?: { value?: string };
33
+ status?: { label?: string };
34
+ orderitem_set?: TrackedOrderItem[];
35
+ }
36
+
37
+ interface ResultRow {
38
+ key: string;
39
+ label: string;
40
+ value: string;
41
+ type: 'number' | 'date' | 'status' | 'total';
42
+ }
43
+
44
+ type LookupState = 'idle' | 'busy' | 'found' | 'error';
45
+
46
+ const readCount = (value: unknown, fallback: number): number => {
47
+ const numeric = Math.round(Number(value));
48
+
49
+ return Number.isFinite(numeric) && numeric >= 0 ? numeric : fallback;
50
+ };
51
+
52
+ /**
53
+ * Anonymous order lookup: order number plus e-mail in, order status out.
54
+ * Mirrors the brand's anonymous-orders view — a CSRF-protected POST to the
55
+ * commerce anonymous order endpoint through the client proxy, which returns a
56
+ * list whose first entry is the matching order.
57
+ *
58
+ * Read-only by construction: it can display an order but has no cancel,
59
+ * return or re-order affordance, so nothing here mutates an order.
60
+ *
61
+ * Parts: root, title, form, field, label, input, submit, error, result, row,
62
+ * row-label, row-value, number, status, list, item, image, content, name,
63
+ * item-status, line-total, reset. A child marked as the item template replaces
64
+ * the built-in order line.
65
+ */
66
+ const OrderTrackingBlock = ({
67
+ block,
68
+ placeholderId,
69
+ sectionId,
70
+ selectedBlockId = null,
71
+ currentBreakpoint = 'desktop',
72
+ isDesigner = false
73
+ }: BlockRendererProps) => {
74
+ const prop = (key: string, fallback: string) =>
75
+ String(
76
+ getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
77
+ );
78
+
79
+ const boolProp = (key: string, fallback: boolean) =>
80
+ readToggle(block.properties?.[key], currentBreakpoint, fallback);
81
+
82
+ const title = prop('title', 'Track your order');
83
+ const numberLabel = prop('numberLabel', 'Order number');
84
+ const emailLabel = prop('emailLabel', 'E-mail');
85
+ const submitLabel = prop('submitLabel', 'Find my order');
86
+ const submittingLabel = prop('submittingLabel', 'Searching…');
87
+ const notFoundLabel = prop(
88
+ 'notFoundLabel',
89
+ 'No order matches that number and e-mail.'
90
+ );
91
+ const dateLabel = prop('dateLabel', 'Order date');
92
+ const statusLabel = prop('statusLabel', 'Status');
93
+ const totalLabel = prop('totalLabel', 'Total');
94
+ const resetLabel = prop('resetLabel', '').trim();
95
+ const numberPlaceholder = prop('numberPlaceholder', '');
96
+ const emailPlaceholder = prop('emailPlaceholder', '');
97
+ const endpoint = prop('endpoint', DEFAULT_ENDPOINT).trim() || DEFAULT_ENDPOINT;
98
+ const showLineItems = boolProp('showLineItems', true);
99
+ const showItemImage = boolProp('showItemImage', true);
100
+ const showItemStatus = boolProp('showItemStatus', true);
101
+ const showOrderDate = boolProp('showOrderDate', true);
102
+ const showOrderStatus = boolProp('showOrderStatus', true);
103
+ const showTotal = boolProp('showTotal', true);
104
+
105
+ const imageWidth = readCount(
106
+ getResponsiveValue(block.properties?.imageWidth, currentBreakpoint, 56),
107
+ 56
108
+ );
109
+ const imageHeight = readCount(
110
+ getResponsiveValue(block.properties?.imageHeight, currentBreakpoint, 75),
111
+ 75
112
+ );
113
+ const maxItems = readCount(
114
+ getResponsiveValue(block.properties?.maxItems, currentBreakpoint, 0),
115
+ 0
116
+ );
117
+
118
+ const { locale, currency: localeCurrency, locales } = useLocalization();
119
+
120
+ const [values, setValues] = useState({ number: '', email: '' });
121
+ const [state, setState] = useState<LookupState>('idle');
122
+ const [order, setOrder] = useState<TrackedOrder | null>(null);
123
+
124
+ const setField = (key: keyof typeof values) => (
125
+ event: React.ChangeEvent<HTMLInputElement>
126
+ ) => {
127
+ const nextValue = event.target.value;
128
+ setValues((current) => ({ ...current, [key]: nextValue }));
129
+ };
130
+
131
+ const submit = async (event: React.FormEvent) => {
132
+ event.preventDefault();
133
+
134
+ // A lookup from inside the designer would send a real shopper's e-mail to
135
+ // commerce from the editor iframe, so the form is inert there.
136
+ if (isDesigner || state === 'busy') return;
137
+
138
+ setState('busy');
139
+ setOrder(null);
140
+
141
+ // Every other client request goes through RTK Query's `customBaseQuery`
142
+ // (akinon-next/data/client/api.ts), whose `prepareHeaders` derives the
143
+ // shopper's locale and currency from the pz cookies. This raw fetch has to
144
+ // derive them the same way: without the two headers commerce answers in the
145
+ // backend default locale and currency, while the result below formats the
146
+ // amounts with the shopper's currency — so a USD shopper reads TRY numbers
147
+ // labelled USD.
148
+ const activeLocale = getCookie('pz-locale') || locale;
149
+ // Commerce expects the locale's `apiValue` ("en-us"), not the pz locale
150
+ // value ("en"), which is why the settings locale list is looked up.
151
+ const localeApiValue = locales?.find(
152
+ (entry) => entry.value === activeLocale
153
+ )?.apiValue;
154
+ const activeCurrency = getCookie('pz-currency') || localeCurrency;
155
+
156
+ const headers: Record<string, string> = {
157
+ // Commerce rejects the POST without the Django CSRF header; the
158
+ // cookie is set by the storefront on first render.
159
+ 'X-CSRFToken': getCookie('csrftoken') || ''
160
+ };
161
+
162
+ // Only sent once resolved: a literal "undefined" header value would make
163
+ // commerce reject the negotiation instead of falling back on its default.
164
+ if (localeApiValue) headers['Accept-Language'] = localeApiValue;
165
+ if (activeCurrency) headers['x-currency'] = activeCurrency;
166
+
167
+ try {
168
+ const response = await fetch(
169
+ buildClientRequestUrl(endpoint, {
170
+ contentType: 'application/json',
171
+ responseType: 'json'
172
+ }),
173
+ {
174
+ method: 'POST',
175
+ headers,
176
+ body: JSON.stringify({
177
+ email: values.email.trim(),
178
+ number: values.number.trim()
179
+ })
180
+ }
181
+ );
182
+
183
+ if (!response.ok) {
184
+ setState('error');
185
+
186
+ return;
187
+ }
188
+
189
+ const payload = await response.json();
190
+ // The endpoint answers with a list; an empty list means "no match",
191
+ // which is a not-found, not a transport failure.
192
+ const found = Array.isArray(payload) ? payload[0] : payload;
193
+
194
+ if (!found) {
195
+ setState('error');
196
+
197
+ return;
198
+ }
199
+
200
+ setOrder(found as TrackedOrder);
201
+ setState('found');
202
+ } catch {
203
+ setState('error');
204
+ }
205
+ };
206
+
207
+ // The lookup never runs in the designer, so the author would only ever see
208
+ // the idle form. `previewState` lets them open the other two states to style
209
+ // them; on the storefront the real state always wins.
210
+ const previewState = prop('previewState', 'idle') as LookupState;
211
+ const activeState: LookupState = isDesigner ? previewState : state;
212
+
213
+ const currency = order?.currency?.value || localeCurrency || '';
214
+ const allItems = Array.isArray(order?.orderitem_set) ? order.orderitem_set : [];
215
+ const items = maxItems > 0 ? allItems.slice(0, maxItems) : allItems;
216
+
217
+ const resultRows: ResultRow[] = [
218
+ {
219
+ key: 'number',
220
+ label: numberLabel,
221
+ value: order?.number || (order?.id ? `#${order.id}` : ''),
222
+ type: 'number'
223
+ }
224
+ ];
225
+
226
+ if (showOrderDate && order?.created_date) {
227
+ resultRows.push({
228
+ key: 'date',
229
+ label: dateLabel,
230
+ value: formatDate(order.created_date, locale),
231
+ type: 'date'
232
+ });
233
+ }
234
+
235
+ if (showOrderStatus && order?.status?.label) {
236
+ resultRows.push({
237
+ key: 'status',
238
+ label: statusLabel,
239
+ value: order.status.label,
240
+ type: 'status'
241
+ });
242
+ }
243
+
244
+ if (showTotal) {
245
+ resultRows.push({
246
+ key: 'total',
247
+ label: totalLabel,
248
+ value: formatMoney(order?.amount, { locale, currency }),
249
+ type: 'total'
250
+ });
251
+ }
252
+
253
+ // Plain objects so a designer binds item.name / item.image / item.price /
254
+ // item.status in their own row layout.
255
+ const templateItems = items.map((item, index) => ({
256
+ key: String(item.id ?? item.product?.pk ?? index),
257
+ name: item.product?.name || '',
258
+ image: item.product?.image || '',
259
+ status: item.status?.label || '',
260
+ price: formatMoney(item.price, { locale, currency })
261
+ }));
262
+
263
+ const customRows = hasItemTemplate(block, currentBreakpoint) ? (
264
+ <ItemTemplateList
265
+ block={block}
266
+ items={templateItems as unknown as Record<string, unknown>[]}
267
+ placeholderId={placeholderId}
268
+ sectionId={sectionId}
269
+ isDesigner={isDesigner}
270
+ selectedBlockId={selectedBlockId}
271
+ currentBreakpoint={currentBreakpoint}
272
+ />
273
+ ) : null;
274
+
275
+ const inputStyle: React.CSSProperties = {
276
+ width: '100%',
277
+ border: '1px solid var(--order-tracking-border, #d1d5db)',
278
+ background: 'transparent',
279
+ color: 'inherit',
280
+ padding: '10px 12px',
281
+ outline: 'none'
282
+ };
283
+
284
+ const rowStyle: React.CSSProperties = {
285
+ display: 'flex',
286
+ justifyContent: 'space-between',
287
+ gap: '12px',
288
+ padding: '4px 0'
289
+ };
290
+
291
+ const isBusy = activeState === 'busy';
292
+ const hasResult = activeState === 'found' && (order !== null || isDesigner);
293
+
294
+ return (
295
+ <div
296
+ data-order-tracking=""
297
+ data-order-tracking-state={activeState}
298
+ {...partAttrs('root', { loading: isBusy })}
299
+ style={{
300
+ display: 'flex',
301
+ flexDirection: 'column',
302
+ gap: 'var(--order-tracking-gap, 16px)'
303
+ }}
304
+ >
305
+ {title && (
306
+ <div
307
+ data-order-tracking-title=""
308
+ {...partAttrs('title')}
309
+ style={{ fontWeight: 600 }}
310
+ >
311
+ {title}
312
+ </div>
313
+ )}
314
+
315
+ {activeState !== 'found' && (
316
+ <form
317
+ data-order-tracking-form=""
318
+ onSubmit={submit}
319
+ {...partAttrs('form', { loading: isBusy })}
320
+ style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}
321
+ >
322
+ <label
323
+ data-order-tracking-field="number"
324
+ {...partAttrs('field')}
325
+ style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
326
+ >
327
+ <span {...partAttrs('label')}>{numberLabel}</span>
328
+ <input
329
+ type="text"
330
+ name="number"
331
+ value={values.number}
332
+ onChange={setField('number')}
333
+ autoComplete="off"
334
+ required
335
+ placeholder={numberPlaceholder || undefined}
336
+ {...partAttrs('input', { invalid: activeState === 'error' })}
337
+ style={inputStyle}
338
+ />
339
+ </label>
340
+
341
+ <label
342
+ data-order-tracking-field="email"
343
+ {...partAttrs('field')}
344
+ style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
345
+ >
346
+ <span {...partAttrs('label')}>{emailLabel}</span>
347
+ <input
348
+ type="email"
349
+ name="email"
350
+ value={values.email}
351
+ onChange={setField('email')}
352
+ autoComplete="email"
353
+ required
354
+ placeholder={emailPlaceholder || undefined}
355
+ {...partAttrs('input', { invalid: activeState === 'error' })}
356
+ style={inputStyle}
357
+ />
358
+ </label>
359
+
360
+ {activeState === 'error' && (
361
+ <div
362
+ data-order-tracking-error=""
363
+ role="alert"
364
+ {...partAttrs('error', { invalid: true })}
365
+ >
366
+ {notFoundLabel}
367
+ </div>
368
+ )}
369
+
370
+ <button
371
+ type="submit"
372
+ data-order-tracking-submit=""
373
+ disabled={isBusy}
374
+ aria-busy={isBusy}
375
+ {...partAttrs('submit', { disabled: isBusy, loading: isBusy })}
376
+ style={{
377
+ padding: '12px 20px',
378
+ background: 'var(--order-tracking-cta-bg, #111827)',
379
+ color: 'var(--order-tracking-cta-color, #ffffff)',
380
+ border: 'none',
381
+ cursor: isBusy ? 'not-allowed' : 'pointer',
382
+ opacity: isBusy ? 0.7 : 1
383
+ }}
384
+ >
385
+ {isBusy ? submittingLabel : submitLabel}
386
+ </button>
387
+ </form>
388
+ )}
389
+
390
+ {hasResult && (
391
+ <div data-order-tracking-result="" {...partAttrs('result')}>
392
+ {resultRows.map((row) =>
393
+ row.type === 'total' ? (
394
+ <div
395
+ key={row.key}
396
+ style={{ ...rowStyle, fontWeight: 600 }}
397
+ data-order-tracking-row="total"
398
+ {...partAttrs('row')}
399
+ >
400
+ <span {...partAttrs('row-label')}>{row.label}</span>
401
+ <span {...partAttrs('row-value')}>{row.value}</span>
402
+ </div>
403
+ ) : (
404
+ <div
405
+ key={row.key}
406
+ style={rowStyle}
407
+ data-order-tracking-row={row.type}
408
+ {...partAttrs('row')}
409
+ >
410
+ <span {...partAttrs('row-label')}>{row.label}</span>
411
+ <span
412
+ {...(row.type === 'number'
413
+ ? { 'data-order-tracking-number': '' }
414
+ : null)}
415
+ {...(row.type === 'status'
416
+ ? { 'data-order-tracking-status': '' }
417
+ : null)}
418
+ {...partAttrs(row.type === 'status' ? 'status' : 'row-value')}
419
+ >
420
+ {row.value}
421
+ </span>
422
+ </div>
423
+ )
424
+ )}
425
+
426
+ {showLineItems && (items.length > 0 || customRows) && (
427
+ <ul
428
+ data-order-tracking-items=""
429
+ {...partAttrs('list')}
430
+ style={{
431
+ display: 'flex',
432
+ flexDirection: 'column',
433
+ gap: '12px',
434
+ listStyle: 'none',
435
+ margin: '12px 0 0',
436
+ padding: 0
437
+ }}
438
+ >
439
+ {customRows ??
440
+ items.map((item, index) => (
441
+ <li
442
+ key={item.id ?? item.product?.pk ?? index}
443
+ data-order-tracking-item=""
444
+ {...partAttrs('item')}
445
+ style={{
446
+ display: 'flex',
447
+ alignItems: 'flex-start',
448
+ gap: '12px'
449
+ }}
450
+ >
451
+ {showItemImage && item.product?.image && (
452
+ // Plain img: order thumbnails come from arbitrary commerce
453
+ // CDNs that the brand next.config may not allow.
454
+ // eslint-disable-next-line @next/next/no-img-element
455
+ <img
456
+ src={item.product.image}
457
+ alt={item.product?.name || ''}
458
+ width={imageWidth}
459
+ height={imageHeight}
460
+ {...partAttrs('image')}
461
+ style={{
462
+ objectFit: 'cover',
463
+ display: 'block',
464
+ flex: '0 0 auto'
465
+ }}
466
+ />
467
+ )}
468
+
469
+ <div
470
+ {...partAttrs('content')}
471
+ style={{ flex: '1 1 auto', minWidth: 0 }}
472
+ >
473
+ <span
474
+ data-order-tracking-item-name=""
475
+ {...partAttrs('name')}
476
+ >
477
+ {item.product?.name || ''}
478
+ </span>
479
+ {showItemStatus && item.status?.label && (
480
+ <div
481
+ data-order-tracking-item-status=""
482
+ {...partAttrs('item-status')}
483
+ style={{ marginTop: '2px', opacity: 0.8 }}
484
+ >
485
+ {item.status.label}
486
+ </div>
487
+ )}
488
+ </div>
489
+
490
+ <span
491
+ data-order-tracking-item-price=""
492
+ {...partAttrs('line-total')}
493
+ style={{ flex: '0 0 auto' }}
494
+ >
495
+ {formatMoney(item.price, { locale, currency })}
496
+ </span>
497
+ </li>
498
+ ))}
499
+ </ul>
500
+ )}
501
+
502
+ {resetLabel && (
503
+ <button
504
+ type="button"
505
+ data-order-tracking-reset=""
506
+ onClick={() => {
507
+ // Designer clicks must not clear a preview the author is styling.
508
+ if (isDesigner) return;
509
+
510
+ setOrder(null);
511
+ setState('idle');
512
+ }}
513
+ {...partAttrs('reset')}
514
+ style={{
515
+ marginTop: '12px',
516
+ background: 'none',
517
+ border: 'none',
518
+ color: 'inherit',
519
+ cursor: 'pointer',
520
+ textDecoration: 'underline',
521
+ padding: 0
522
+ }}
523
+ >
524
+ {resetLabel}
525
+ </button>
526
+ )}
527
+ </div>
528
+ )}
529
+ </div>
530
+ );
531
+ };
532
+
533
+ export default OrderTrackingBlock;