@shoppexio/storefront-react 0.4.0 → 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.
- package/CHANGELOG.md +13 -0
- package/dist/account.css +22 -0
- package/dist/account.js +2311 -302
- package/dist/account.js.map +1 -1
- package/dist/index.d.ts +137 -18
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -1
- package/eject/account/account-payment-method.ts +7 -1
- package/eject/account/account-subscriptions.ts +162 -8
- package/eject/account/buyer-invoice-status.ts +100 -0
- package/eject/account/downloads.tsx +45 -13
- package/eject/account/messages/ar.json +114 -14
- package/eject/account/messages/de.json +114 -14
- package/eject/account/messages/en.json +114 -14
- package/eject/account/messages/es.json +114 -14
- package/eject/account/messages/fi.json +114 -14
- package/eject/account/messages/fr.json +114 -14
- package/eject/account/messages/hi.json +114 -14
- package/eject/account/messages/it.json +114 -14
- package/eject/account/messages/lt.json +114 -14
- package/eject/account/messages/nl.json +114 -14
- package/eject/account/messages/pl.json +114 -14
- package/eject/account/messages/pt.json +114 -14
- package/eject/account/messages/ru.json +114 -14
- package/eject/account/messages/tr.json +114 -14
- package/eject/account/messages/uk.json +114 -14
- package/eject/account/messages/zh.json +114 -14
- package/eject/account/orders.tsx +115 -41
- package/eject/account/reseller.tsx +3 -2
- package/eject/account/shared.tsx +21 -3
- package/eject/account/subscriptions.tsx +371 -49
- package/package.json +1 -1
package/eject/account/orders.tsx
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* `account-route.ts`. Order detail is not a page of its own.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { BUYER_STATUS_FILTERS, buyerInvoiceStatus, type BuyerStatusFilter } from './buyer-invoice-status';
|
|
16
17
|
import { useState, type ReactNode } from 'react';
|
|
17
18
|
import { Link } from 'react-router-dom';
|
|
18
19
|
import { paymentMethodLabel } from './account-payment-method';
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
warrantyDate,
|
|
30
31
|
} from './account-warranties';
|
|
31
32
|
import { callCustomerApi, safeExternalHref } from './customer-account';
|
|
32
|
-
import type { SdkCustomerDeliverySummary, SdkCustomerWarranty } from '@shoppexio/storefront-react';
|
|
33
|
+
import type { SdkCustomerDeliverySummary, SdkCustomerOrderDetail, SdkCustomerWarranty } from '@shoppexio/storefront-react';
|
|
33
34
|
import { t, tOptional } from './i18n';
|
|
34
35
|
import {
|
|
35
36
|
ArrowLeftIcon,
|
|
@@ -48,7 +49,7 @@ import {
|
|
|
48
49
|
AccountRowsSkeleton,
|
|
49
50
|
AccountSectionTitle,
|
|
50
51
|
BusySpinner,
|
|
51
|
-
|
|
52
|
+
InvoiceStatusChip,
|
|
52
53
|
formatAccountAmount,
|
|
53
54
|
formatAccountDate,
|
|
54
55
|
formatInvoiceAmount,
|
|
@@ -57,25 +58,6 @@ import {
|
|
|
57
58
|
|
|
58
59
|
const ORDERS_PER_PAGE = 10;
|
|
59
60
|
|
|
60
|
-
/** The producer's invoice-status enum, in the order the hosted portal lists it. */
|
|
61
|
-
const STATUS_FILTERS = [
|
|
62
|
-
'COMPLETED',
|
|
63
|
-
'PENDING',
|
|
64
|
-
'PROCESSING',
|
|
65
|
-
'REFUNDED',
|
|
66
|
-
'DISPUTED',
|
|
67
|
-
'EXPIRED',
|
|
68
|
-
] as const;
|
|
69
|
-
|
|
70
|
-
const STATUS_LABEL_KEYS: Record<(typeof STATUS_FILTERS)[number], string> = {
|
|
71
|
-
COMPLETED: 'orders.statusCompleted',
|
|
72
|
-
PENDING: 'orders.statusPending',
|
|
73
|
-
PROCESSING: 'orders.statusProcessing',
|
|
74
|
-
REFUNDED: 'orders.statusRefunded',
|
|
75
|
-
DISPUTED: 'orders.statusDisputed',
|
|
76
|
-
EXPIRED: 'orders.statusExpired',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
61
|
/** The template's field skin, shared with the checkout and contact forms. */
|
|
80
62
|
const FIELD =
|
|
81
63
|
'sx-account-field sxa:w-full sxa:border sxa:bg-[color-mix(in_oklab,_var(--sx-bg)_40%,_transparent)] sxa:px-3 sxa:py-2.5 sxa:text-small sxa:shadow-card sxa:placeholder:text-muted sxa:focus-visible:border-accent sxa:focus-visible:ring-[3px] sxa:focus-visible:ring-[color-mix(in_oklab,_var(--sx-accent)_50%,_transparent)] sxa:focus-visible:outline-none';
|
|
@@ -123,7 +105,7 @@ export function OrderRow({
|
|
|
123
105
|
<p className="sxa:mt-1 sxa:text-micro sxa:text-muted">{formatAccountDate(createdAt)}</p>
|
|
124
106
|
</div>
|
|
125
107
|
<span className="sxa:shrink-0 sxa:text-small sxa:font-semibold sxa:tabular-nums">{amount}</span>
|
|
126
|
-
<
|
|
108
|
+
<InvoiceStatusChip status={status} />
|
|
127
109
|
</AccountCard>
|
|
128
110
|
</Link>
|
|
129
111
|
);
|
|
@@ -131,32 +113,33 @@ export function OrderRow({
|
|
|
131
113
|
|
|
132
114
|
export function AccountOrders() {
|
|
133
115
|
const [page, setPage] = useState(1);
|
|
134
|
-
const [
|
|
116
|
+
const [statusGroup, setStatusGroup] = useState<BuyerStatusFilter | ''>('');
|
|
135
117
|
const [search, setSearch] = useState('');
|
|
136
118
|
/** What was last SENT, as against what is currently being typed. */
|
|
137
|
-
const [applied, setApplied] = useState({
|
|
119
|
+
const [applied, setApplied] = useState<{ statusGroup: BuyerStatusFilter | ''; search: string }>({ statusGroup: '', search: '' });
|
|
138
120
|
|
|
139
121
|
const { state, reload } = useAccountResource(
|
|
140
|
-
`orders:${page}:${applied.
|
|
122
|
+
`orders:${page}:${applied.statusGroup}:${applied.search}`,
|
|
141
123
|
() => callCustomerApi((sdk) => sdk.orders({
|
|
142
124
|
page,
|
|
143
125
|
limit: ORDERS_PER_PAGE,
|
|
144
|
-
|
|
126
|
+
// One group of the buyer status table, filtered by the producer: "Paid" is two statuses.
|
|
127
|
+
...(applied.statusGroup ? { statusGroup: applied.statusGroup } : {}),
|
|
145
128
|
...(applied.search ? { search: applied.search } : {}),
|
|
146
129
|
})),
|
|
147
130
|
);
|
|
148
131
|
|
|
149
132
|
// A changed filter is a different list. Staying on page four of the old one
|
|
150
133
|
// would ask for a page the new list may not have and answer with nothing.
|
|
151
|
-
const applyFilters = (next: {
|
|
152
|
-
const merged = {
|
|
153
|
-
|
|
134
|
+
const applyFilters = (next: { statusGroup?: BuyerStatusFilter | ''; search?: string }) => {
|
|
135
|
+
const merged = { statusGroup: next.statusGroup ?? statusGroup, search: next.search ?? search };
|
|
136
|
+
setStatusGroup(merged.statusGroup);
|
|
154
137
|
setSearch(merged.search);
|
|
155
138
|
setApplied(merged);
|
|
156
139
|
setPage(1);
|
|
157
140
|
};
|
|
158
141
|
|
|
159
|
-
const filtering = applied.
|
|
142
|
+
const filtering = applied.statusGroup !== '' || applied.search !== '';
|
|
160
143
|
|
|
161
144
|
const filterBar = (
|
|
162
145
|
<form
|
|
@@ -176,14 +159,15 @@ export function AccountOrders() {
|
|
|
176
159
|
/>
|
|
177
160
|
<div className="sxa:relative">
|
|
178
161
|
<select
|
|
179
|
-
value={
|
|
162
|
+
value={statusGroup}
|
|
180
163
|
aria-label={t('orders.status')}
|
|
181
|
-
onChange={(event) => applyFilters({
|
|
164
|
+
onChange={(event) => applyFilters({ statusGroup: BUYER_STATUS_FILTERS.find((filter) => filter.id === event.target.value)?.id ?? '' })}
|
|
182
165
|
className="sxa:h-10 sxa:w-full sxa:appearance-none sxa:border sxa:bg-surface sxa:pr-9 sxa:pl-4 sxa:text-small sxa:focus:outline-none sxa:sm:w-auto sxa:rounded-input"
|
|
183
166
|
>
|
|
184
167
|
<option value="">{t('orders.statusAll')}</option>
|
|
185
|
-
{
|
|
186
|
-
|
|
168
|
+
{/* The table's groups, in the words of the status chips (Design Batch 9, C2). */}
|
|
169
|
+
{BUYER_STATUS_FILTERS.map((filter) => (
|
|
170
|
+
<option key={filter.id} value={filter.id}>{t(`invoiceStatus.${filter.labelKey}`)}</option>
|
|
187
171
|
))}
|
|
188
172
|
</select>
|
|
189
173
|
<div className="sxa:pointer-events-none sxa:absolute sxa:inset-y-0 sxa:right-0 sxa:flex sxa:items-center sxa:px-3 sxa:text-muted">
|
|
@@ -304,6 +288,63 @@ export function AccountOrders() {
|
|
|
304
288
|
);
|
|
305
289
|
}
|
|
306
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Why an order offers no receipt, when the buyer status table gives it none
|
|
293
|
+
* (Design Batch 9, C7). Only where the sentence is true: an expired order that
|
|
294
|
+
* took no payment, and one the shop cancelled. An unpaid order still waits for
|
|
295
|
+
* payment, and a disputed one says nothing more.
|
|
296
|
+
*/
|
|
297
|
+
export function noReceiptKey(order: Pick<SdkCustomerOrderDetail, 'status' | 'expiredPayment'>): string | null {
|
|
298
|
+
if (order.status === 'VOIDED') return 'orders.noReceiptVoided';
|
|
299
|
+
if (order.status === 'EXPIRED' && order.expiredPayment === 'NONE') return 'orders.noReceiptUnpaid';
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* When the order was paid, for one the buyer status table counts as paid (a
|
|
305
|
+
* held order waiting for stock is; a disputed one is not), else when it was
|
|
306
|
+
* placed (Design Batch 9, C1).
|
|
307
|
+
*/
|
|
308
|
+
export function orderDateLine(order: Pick<SdkCustomerOrderDetail, 'status' | 'createdAt' | 'paidAt'>): string {
|
|
309
|
+
return order.paidAt && buyerInvoiceStatus(order.status)?.paid
|
|
310
|
+
? t('orders.paidOn', { date: formatAccountDate(order.paidAt) })
|
|
311
|
+
: formatAccountDate(order.createdAt);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* What a paid order held for a restock says (C1). The restock delivers the
|
|
316
|
+
* whole order at once and the wire does not say which line is short, so only
|
|
317
|
+
* a single item is named.
|
|
318
|
+
*/
|
|
319
|
+
export function waitingStockNotice(order: Pick<SdkCustomerOrderDetail, 'status' | 'lineItems'>): string | null {
|
|
320
|
+
if (order.status !== 'AWAITING_STOCK') return null;
|
|
321
|
+
const [only] = order.lineItems;
|
|
322
|
+
const body = order.lineItems.length === 1 && only!.quantity === 1
|
|
323
|
+
? t('orders.waitingStock.body', { item: only!.productTitle })
|
|
324
|
+
: t('orders.waitingStock.bodyOrder');
|
|
325
|
+
return `${t('orders.waitingStock.title')} ${body}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** A line of a held order waits for the restock; "Pending" would read as unpaid (C1). */
|
|
329
|
+
export function lineDeliveryChipStatus(
|
|
330
|
+
orderStatus: string,
|
|
331
|
+
line: Pick<SdkCustomerOrderDetail['lineItems'][number], 'deliveryStatus'>,
|
|
332
|
+
): string {
|
|
333
|
+
return orderStatus === 'AWAITING_STOCK' && line.deliveryStatus === 'PENDING' ? 'WAITING' : line.deliveryStatus;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The sentence under a line the seller delivered without anything to hand
|
|
338
|
+
* over (C7). Withheld goods (a gift, a reveal not taken) are not "nothing",
|
|
339
|
+
* and neither is a backend that did not say.
|
|
340
|
+
*/
|
|
341
|
+
export function nothingToDownloadDate(
|
|
342
|
+
line: Pick<SdkCustomerOrderDetail['lineItems'][number], 'deliveredAt' | 'deliverySummary'>,
|
|
343
|
+
deliveryWithheld: boolean | undefined,
|
|
344
|
+
): string | null {
|
|
345
|
+
return line.deliveredAt !== null && line.deliverySummary === null && deliveryWithheld === false ? line.deliveredAt : null;
|
|
346
|
+
}
|
|
347
|
+
|
|
307
348
|
export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
308
349
|
const { state, reload } = useAccountResource(`order:${orderId}`, () =>
|
|
309
350
|
callCustomerApi((sdk) => sdk.order(orderId)));
|
|
@@ -326,6 +367,9 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
326
367
|
<div>
|
|
327
368
|
{backLink}
|
|
328
369
|
<div className="sxa:mt-5">
|
|
370
|
+
<AccountSectionTitle>{t('orders.detailError', { id: orderId })}</AccountSectionTitle>
|
|
371
|
+
</div>
|
|
372
|
+
<div className="sxa:mt-4">
|
|
329
373
|
<AccountError message={state.message} onRetry={reload} />
|
|
330
374
|
</div>
|
|
331
375
|
</div>
|
|
@@ -333,6 +377,8 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
333
377
|
}
|
|
334
378
|
|
|
335
379
|
const order = state.data;
|
|
380
|
+
const noReceipt = noReceiptKey(order);
|
|
381
|
+
const waiting = waitingStockNotice(order);
|
|
336
382
|
|
|
337
383
|
return (
|
|
338
384
|
<div>
|
|
@@ -341,9 +387,9 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
341
387
|
<div className="sxa:mt-5 sxa:flex sxa:flex-wrap sxa:items-start sxa:justify-between sxa:gap-4">
|
|
342
388
|
<div>
|
|
343
389
|
<AccountSectionTitle>{t('orders.detailTitle', { id: order.uniqid })}</AccountSectionTitle>
|
|
344
|
-
<p className="sxa:mt-1 sxa:text-small sxa:text-muted">{
|
|
390
|
+
<p className="sxa:mt-1 sxa:text-small sxa:text-muted">{orderDateLine(order)}</p>
|
|
345
391
|
</div>
|
|
346
|
-
<
|
|
392
|
+
<InvoiceStatusChip status={order.status} />
|
|
347
393
|
</div>
|
|
348
394
|
|
|
349
395
|
{/*
|
|
@@ -356,7 +402,18 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
356
402
|
<p className="sxa:mt-3 sxa:text-small sxa:leading-relaxed sxa:text-muted">{order.statusDetails}</p>
|
|
357
403
|
) : null}
|
|
358
404
|
|
|
359
|
-
|
|
405
|
+
{waiting ? (
|
|
406
|
+
<AccountCard className="sxa:mt-4 sxa:px-5 sxa:py-4">
|
|
407
|
+
<p role="status" className="sxa:text-small sxa:leading-relaxed">{waiting}</p>
|
|
408
|
+
</AccountCard>
|
|
409
|
+
) : null}
|
|
410
|
+
|
|
411
|
+
{/* The receipt follows the buyer status table: unpaid, expired, cancelled and disputed orders have none. */}
|
|
412
|
+
{buyerInvoiceStatus(order.status)?.receipt ? (
|
|
413
|
+
<InvoicePdfButton orderUniqid={order.uniqid} />
|
|
414
|
+
) : noReceipt ? (
|
|
415
|
+
<p className="sxa:mt-4 sxa:text-small sxa:text-muted">{t(noReceipt)}</p>
|
|
416
|
+
) : null}
|
|
360
417
|
|
|
361
418
|
<ul className="sxa:mt-6 sxa:space-y-3">
|
|
362
419
|
{order.lineItems.map((item) => (
|
|
@@ -384,7 +441,7 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
384
441
|
rendered for every line rather than only for the unhappy ones.
|
|
385
442
|
*/}
|
|
386
443
|
<p className="sxa:mt-1.5">
|
|
387
|
-
<DeliveryChip status={item
|
|
444
|
+
<DeliveryChip status={lineDeliveryChipStatus(order.status, item)} />
|
|
388
445
|
</p>
|
|
389
446
|
</div>
|
|
390
447
|
</div>
|
|
@@ -396,6 +453,7 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
396
453
|
payment status.
|
|
397
454
|
*/}
|
|
398
455
|
{item.deliverySummary ? <DeliveredItems summary={item.deliverySummary} /> : null}
|
|
456
|
+
{nothingToDownload(item, order.deliveryWithheld)}
|
|
399
457
|
</AccountCard>
|
|
400
458
|
</li>
|
|
401
459
|
))}
|
|
@@ -461,9 +519,16 @@ export function AccountOrderDetail({ orderId }: { orderId: string }) {
|
|
|
461
519
|
* a temporary object URL on an anchor the buyer never sees; the name is the
|
|
462
520
|
* producer's, and when it sent none the browser picks one.
|
|
463
521
|
*/
|
|
522
|
+
function nothingToDownload(line: SdkCustomerOrderDetail['lineItems'][number], deliveryWithheld: boolean | undefined): ReactNode {
|
|
523
|
+
const deliveredAt = nothingToDownloadDate(line, deliveryWithheld);
|
|
524
|
+
if (deliveredAt === null) return null;
|
|
525
|
+
return <p className="sxa:mt-3 sxa:text-small sxa:text-muted">{t('orders.nothingToDownload', { date: formatAccountDate(deliveredAt) })}</p>;
|
|
526
|
+
}
|
|
527
|
+
|
|
464
528
|
function InvoicePdfButton({ orderUniqid }: { orderUniqid: string }) {
|
|
465
529
|
const [busy, setBusy] = useState(false);
|
|
466
|
-
|
|
530
|
+
/** Why the last try failed: the session ended (sign in again) or the receipt could not be made (try later). */
|
|
531
|
+
const [error, setError] = useState<'signed-out' | 'failed' | null>(null);
|
|
467
532
|
|
|
468
533
|
const download = async () => {
|
|
469
534
|
setBusy(true);
|
|
@@ -472,7 +537,7 @@ function InvoicePdfButton({ orderUniqid }: { orderUniqid: string }) {
|
|
|
472
537
|
setBusy(false);
|
|
473
538
|
|
|
474
539
|
if (!response.success || response.data === undefined) {
|
|
475
|
-
setError(response.
|
|
540
|
+
setError(response.status === 401 || response.status === 403 ? 'signed-out' : 'failed');
|
|
476
541
|
return;
|
|
477
542
|
}
|
|
478
543
|
|
|
@@ -498,7 +563,16 @@ function InvoicePdfButton({ orderUniqid }: { orderUniqid: string }) {
|
|
|
498
563
|
: <ReceiptIcon className="sxa:mr-1.5 sxa:h-3.5 sxa:w-3.5" />}
|
|
499
564
|
{busy ? t('orders.invoicePdfLoading') : t('orders.invoicePdf')}
|
|
500
565
|
</button>
|
|
501
|
-
{error ? <AccountFieldError message={
|
|
566
|
+
{error === 'failed' ? <AccountFieldError message={t('orders.pdfFailed')} /> : null}
|
|
567
|
+
{error === 'signed-out' ? (
|
|
568
|
+
<div className="sxa:mt-3 sxa:flex sxa:flex-wrap sxa:items-center sxa:gap-3">
|
|
569
|
+
<AccountFieldError message={t('orders.pdfSignedOut')} />
|
|
570
|
+
{/* A fresh load asks who is signed in and shows the sign-in form. */}
|
|
571
|
+
<button type="button" onClick={() => window.location.reload()} className="sx-account-button sxa:card-button sxa:card-button--add sxa:mt-3">
|
|
572
|
+
{t('login.submit')}
|
|
573
|
+
</button>
|
|
574
|
+
</div>
|
|
575
|
+
) : null}
|
|
502
576
|
</div>
|
|
503
577
|
);
|
|
504
578
|
}
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
BusySpinner,
|
|
55
55
|
CopyButton,
|
|
56
56
|
HostedPageLink,
|
|
57
|
+
InvoiceStatusChip,
|
|
57
58
|
StatusChip,
|
|
58
59
|
formatAccountAmount,
|
|
59
60
|
formatAccountDate,
|
|
@@ -853,7 +854,7 @@ function WholesaleOrders() {
|
|
|
853
854
|
<span className="sxa:text-small sxa:font-semibold sxa:tabular-nums">
|
|
854
855
|
{formatAccountAmount(order.total, order.currency)}
|
|
855
856
|
</span>
|
|
856
|
-
<
|
|
857
|
+
<InvoiceStatusChip status={order.status} />
|
|
857
858
|
</button>
|
|
858
859
|
</li>
|
|
859
860
|
))}
|
|
@@ -916,7 +917,7 @@ function WholesaleOrderDetail({ uniqid, onBack }: { uniqid: string; onBack: () =
|
|
|
916
917
|
{formatAccountDateTime(order.created_at)}
|
|
917
918
|
</p>
|
|
918
919
|
</div>
|
|
919
|
-
<
|
|
920
|
+
<InvoiceStatusChip status={order.status} />
|
|
920
921
|
</div>
|
|
921
922
|
|
|
922
923
|
<AccountCard className="sxa:mt-5 sxa:p-6">
|
package/eject/account/shared.tsx
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* of them invents a value the producer did not send.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { buyerInvoiceStatus, type BuyerStatusTone } from './buyer-invoice-status';
|
|
15
16
|
import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
|
|
16
17
|
import { Link } from 'react-router-dom';
|
|
17
18
|
import { safeExternalHref, toPanelState, type AccountPanelState } from './customer-account';
|
|
@@ -213,11 +214,8 @@ const STATUS_TONES: Record<string, string> = {
|
|
|
213
214
|
CONVERTED: TONE_GOOD,
|
|
214
215
|
// In flight. The buyer is waiting on someone, or someone is waiting on them.
|
|
215
216
|
PENDING: TONE_WAITING,
|
|
216
|
-
PENDING_PAYMENT: TONE_WAITING,
|
|
217
217
|
PROCESSING: TONE_WAITING,
|
|
218
|
-
AWAITING_STOCK: TONE_WAITING,
|
|
219
218
|
AWAITING_REPLY: TONE_WAITING,
|
|
220
|
-
UNDERPAID: TONE_WAITING,
|
|
221
219
|
TRIALING: TONE_WAITING,
|
|
222
220
|
PAUSED: TONE_WAITING,
|
|
223
221
|
INCOMPLETE: TONE_WAITING,
|
|
@@ -247,6 +245,26 @@ const STATUS_TONES: Record<string, string> = {
|
|
|
247
245
|
* the tone never applied.
|
|
248
246
|
*/
|
|
249
247
|
|
|
248
|
+
const INVOICE_TONES: Record<BuyerStatusTone, string> = {
|
|
249
|
+
positive: TONE_GOOD,
|
|
250
|
+
notice: TONE_WAITING,
|
|
251
|
+
danger: TONE_BAD,
|
|
252
|
+
neutral: 'sxa:meta-chip--neutral',
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* An order's status: the word (`invoiceStatus.*`) and the tone from the one
|
|
257
|
+
* buyer status table (Design Batch 9, C2), the rows the checkout and the theme
|
|
258
|
+
* account read too. A status the table does not know falls to `StatusChip`.
|
|
259
|
+
*/
|
|
260
|
+
export function InvoiceStatusChip({ status }: { status: string }) {
|
|
261
|
+
const row = buyerInvoiceStatus(status.trim().toUpperCase());
|
|
262
|
+
if (!row) return <StatusChip status={status} />;
|
|
263
|
+
return (
|
|
264
|
+
<span className={`sx-account-status sx-account-chip sxa:meta-chip sxa:meta-chip--status ${INVOICE_TONES[row.tone]} sxa:shrink-0`}>{t(`invoiceStatus.${row.labelKey}`)}</span>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
/** `label` replaces the status word when the caller has a better one; the tone still follows `status`. */
|
|
251
269
|
export function StatusChip({ status, label }: { status: string; label?: string }) {
|
|
252
270
|
const tone = STATUS_TONES[status.trim().toUpperCase()] ?? 'sxa:meta-chip--neutral';
|