payment-kit 1.18.14 → 1.18.16
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/api/src/index.ts +2 -0
- package/api/src/libs/invoice.ts +5 -3
- package/api/src/routes/connect/change-payment.ts +9 -1
- package/api/src/routes/connect/change-plan.ts +9 -1
- package/api/src/routes/connect/collect-batch.ts +7 -5
- package/api/src/routes/connect/recharge-account.ts +124 -0
- package/api/src/routes/connect/setup.ts +8 -1
- package/api/src/routes/connect/shared.ts +110 -48
- package/api/src/routes/connect/subscribe.ts +11 -1
- package/api/src/routes/customers.ts +149 -6
- package/api/src/routes/invoices.ts +46 -0
- package/api/src/routes/payment-currencies.ts +5 -2
- package/api/src/routes/subscriptions.ts +0 -3
- package/blocklet.yml +1 -1
- package/package.json +8 -8
- package/src/app.tsx +11 -3
- package/src/components/info-card.tsx +3 -1
- package/src/components/info-row.tsx +1 -0
- package/src/components/invoice/recharge.tsx +85 -56
- package/src/components/invoice/table.tsx +7 -1
- package/src/components/subscription/portal/actions.tsx +1 -1
- package/src/components/subscription/portal/list.tsx +6 -0
- package/src/locales/en.tsx +9 -0
- package/src/locales/zh.tsx +9 -0
- package/src/pages/admin/settings/vault-config/index.tsx +21 -6
- package/src/pages/customer/index.tsx +160 -265
- package/src/pages/customer/invoice/detail.tsx +24 -16
- package/src/pages/customer/invoice/past-due.tsx +45 -23
- package/src/pages/customer/recharge/account.tsx +515 -0
- package/src/pages/customer/{recharge.tsx → recharge/subscription.tsx} +11 -11
- package/src/pages/customer/subscription/embed.tsx +16 -1
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import DID from '@arcblock/ux/lib/DID';
|
|
2
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
2
|
import {
|
|
5
3
|
CustomerInvoiceList,
|
|
6
4
|
formatBNStr,
|
|
7
5
|
formatError,
|
|
8
|
-
getCustomerAvatar,
|
|
9
6
|
getPrefix,
|
|
10
|
-
TruncatedText,
|
|
11
|
-
useMobile,
|
|
12
7
|
usePaymentContext,
|
|
8
|
+
OverdueInvoicePayment,
|
|
13
9
|
} from '@blocklet/payment-react';
|
|
14
10
|
import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
|
|
15
|
-
import { ExpandMore } from '@mui/icons-material';
|
|
11
|
+
import { ExpandMore, AccountBalanceWalletOutlined } from '@mui/icons-material';
|
|
16
12
|
import {
|
|
17
13
|
Avatar,
|
|
18
14
|
Box,
|
|
19
15
|
Button,
|
|
20
16
|
FormControl,
|
|
21
|
-
Grid,
|
|
22
17
|
MenuItem,
|
|
23
18
|
Select,
|
|
24
19
|
Skeleton,
|
|
@@ -31,13 +26,10 @@ import type { SelectChangeEvent } from '@mui/material/Select';
|
|
|
31
26
|
import { styled, SxProps } from '@mui/system';
|
|
32
27
|
import { useRequest, useSetState } from 'ahooks';
|
|
33
28
|
import { flatten, isEmpty } from 'lodash';
|
|
34
|
-
import { memo, useEffect,
|
|
35
|
-
import { FlagEmoji, defaultCountries, parseCountry } from 'react-international-phone';
|
|
29
|
+
import { memo, useEffect, useState } from 'react';
|
|
36
30
|
import { useNavigate } from 'react-router-dom';
|
|
37
31
|
import { joinURL } from 'ufo';
|
|
38
32
|
|
|
39
|
-
import EditCustomer from '../../components/customer/edit';
|
|
40
|
-
import InfoRow from '../../components/info-row';
|
|
41
33
|
import { useTransitionContext } from '../../components/progress-bar';
|
|
42
34
|
import CurrentSubscriptions from '../../components/subscription/portal/list';
|
|
43
35
|
import { useSessionContext } from '../../contexts/session';
|
|
@@ -58,6 +50,7 @@ const CurrencyCard = memo(
|
|
|
58
50
|
data,
|
|
59
51
|
currency,
|
|
60
52
|
sx,
|
|
53
|
+
type,
|
|
61
54
|
}: {
|
|
62
55
|
label: string;
|
|
63
56
|
data: {
|
|
@@ -66,16 +59,35 @@ const CurrencyCard = memo(
|
|
|
66
59
|
currency: {
|
|
67
60
|
id: string;
|
|
68
61
|
decimal: number;
|
|
62
|
+
payment_method_id: string;
|
|
69
63
|
};
|
|
70
64
|
sx: SxProps;
|
|
65
|
+
type: 'balance' | 'spent' | 'stake' | 'refund' | 'due';
|
|
71
66
|
}) => {
|
|
67
|
+
const navigate = useNavigate();
|
|
68
|
+
const { settings } = usePaymentContext();
|
|
69
|
+
const method = settings?.paymentMethods?.find((m) => m.id === currency.payment_method_id);
|
|
70
|
+
const { t } = useLocaleContext();
|
|
72
71
|
const safeData = data || {};
|
|
73
72
|
const value = formatBNStr(safeData[currency?.id], currency?.decimal, 6, false);
|
|
73
|
+
const hideTypes = ['stake', 'refund', 'due'];
|
|
74
|
+
if (hideTypes.includes(type) && (!safeData[currency?.id] || safeData[currency?.id] === '0')) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const handleCardClick = () => {
|
|
79
|
+
if (type === 'balance' && currency?.id) {
|
|
80
|
+
navigate(`/customer/recharge/${currency.id}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
74
84
|
return (
|
|
75
85
|
<Box
|
|
76
86
|
sx={{
|
|
77
87
|
padding: '12px 16px',
|
|
78
88
|
borderRadius: 'var(--radius-m, 8px)',
|
|
89
|
+
transition: 'all 0.2s ease-in-out',
|
|
90
|
+
position: 'relative',
|
|
79
91
|
...sx,
|
|
80
92
|
}}>
|
|
81
93
|
<Typography variant="h4" gutterBottom>
|
|
@@ -84,6 +96,26 @@ const CurrencyCard = memo(
|
|
|
84
96
|
<Typography variant="h3" gutterBottom>
|
|
85
97
|
{value}
|
|
86
98
|
</Typography>
|
|
99
|
+
{type === 'balance' && method?.type === 'arcblock' && (
|
|
100
|
+
<Tooltip title={t('customer.recharge.rechargeTooltip')} arrow placement="top">
|
|
101
|
+
<Box
|
|
102
|
+
onClick={handleCardClick}
|
|
103
|
+
sx={{
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
top: '12px',
|
|
106
|
+
right: '12px',
|
|
107
|
+
display: 'flex',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
justifyContent: 'center',
|
|
110
|
+
borderRadius: '50%',
|
|
111
|
+
padding: '4px',
|
|
112
|
+
zIndex: 1,
|
|
113
|
+
cursor: 'pointer',
|
|
114
|
+
}}>
|
|
115
|
+
<AccountBalanceWalletOutlined fontSize="small" />
|
|
116
|
+
</Box>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
)}
|
|
87
119
|
</Box>
|
|
88
120
|
);
|
|
89
121
|
}
|
|
@@ -104,58 +136,19 @@ function SummaryCardSkeleton() {
|
|
|
104
136
|
return (
|
|
105
137
|
<Box className="base-card section section-summary">
|
|
106
138
|
<Skeleton variant="text" width={150} height={32} sx={{ mb: 2 }} />
|
|
107
|
-
<
|
|
108
|
-
<Stack direction="row" spacing={2}>
|
|
109
|
-
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
110
|
-
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
111
|
-
</Stack>
|
|
112
|
-
<Stack direction="row" spacing={2}>
|
|
113
|
-
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
114
|
-
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
115
|
-
</Stack>
|
|
116
|
-
<Stack direction="row">
|
|
117
|
-
<Skeleton variant="rectangular" height={88} width="calc(50% - 8px)" />
|
|
118
|
-
</Stack>
|
|
119
|
-
</Stack>
|
|
120
|
-
</Box>
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function DetailCardSkeleton() {
|
|
125
|
-
return (
|
|
126
|
-
<Box className="base-card section section-detail">
|
|
127
|
-
<Box className="section-header" display="flex" justifyContent="space-between" alignItems="center" mb={2}>
|
|
128
|
-
<Skeleton variant="text" width={150} height={32} />
|
|
129
|
-
<Skeleton variant="text" width={80} height={20} />
|
|
130
|
-
</Box>
|
|
131
|
-
<Stack direction="row" spacing={2} mb={3}>
|
|
132
|
-
<Skeleton variant="circular" width={48} height={48} sx={{ minWidth: '48px' }} />
|
|
133
|
-
<Box width="100%">
|
|
134
|
-
<Skeleton variant="text" width="80%" />
|
|
135
|
-
<Skeleton variant="text" width="60%" />
|
|
136
|
-
</Box>
|
|
137
|
-
</Stack>
|
|
138
|
-
<Stack spacing={1}>
|
|
139
|
-
{[...Array(7)].map((_, i) => (
|
|
140
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
141
|
-
<Stack key={i} direction="column" spacing={0.5}>
|
|
142
|
-
<Skeleton variant="text" width="30%" />
|
|
143
|
-
<Skeleton variant="text" width="70%" />
|
|
144
|
-
</Stack>
|
|
145
|
-
))}
|
|
146
|
-
</Stack>
|
|
139
|
+
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
147
140
|
</Box>
|
|
148
141
|
);
|
|
149
142
|
}
|
|
150
143
|
|
|
151
144
|
export default function CustomerHome() {
|
|
152
145
|
const { t } = useLocaleContext();
|
|
153
|
-
const { events
|
|
146
|
+
const { events } = useSessionContext();
|
|
154
147
|
const { settings } = usePaymentContext();
|
|
155
148
|
const [currency, setCurrency] = useState(settings?.baseCurrency);
|
|
156
149
|
const [subscriptionLoading, setSubscriptionLoading] = useState(false);
|
|
157
150
|
const currencies = flatten(
|
|
158
|
-
settings.paymentMethods
|
|
151
|
+
settings.paymentMethods?.map((method) =>
|
|
159
152
|
(method.payment_currencies || []).map((c) => ({
|
|
160
153
|
...c,
|
|
161
154
|
methodName: method.name,
|
|
@@ -171,8 +164,8 @@ export default function CustomerHome() {
|
|
|
171
164
|
setOverdraftProtection: false,
|
|
172
165
|
});
|
|
173
166
|
const navigate = useNavigate();
|
|
174
|
-
const { isMobile } = useMobile('lg');
|
|
175
167
|
const [subscriptionStatus, setSubscriptionStatus] = useState(false);
|
|
168
|
+
const [hasSubscriptions, setHasSubscriptions] = useState(true);
|
|
176
169
|
const { startTransition } = useTransitionContext();
|
|
177
170
|
const {
|
|
178
171
|
data,
|
|
@@ -184,11 +177,7 @@ export default function CustomerHome() {
|
|
|
184
177
|
});
|
|
185
178
|
|
|
186
179
|
const loadingCard = loading || !data;
|
|
187
|
-
|
|
188
|
-
const countryDetail = useMemo(() => {
|
|
189
|
-
const item = defaultCountries.find((v) => v[1] === data?.address?.country);
|
|
190
|
-
return item ? parseCountry(item) : { name: '' };
|
|
191
|
-
}, [data]);
|
|
180
|
+
const [showOverduePayment, setShowOverduePayment] = useState(false);
|
|
192
181
|
|
|
193
182
|
useEffect(() => {
|
|
194
183
|
runAsync();
|
|
@@ -221,20 +210,6 @@ export default function CustomerHome() {
|
|
|
221
210
|
);
|
|
222
211
|
}
|
|
223
212
|
|
|
224
|
-
const onUpdateInfo = async (updates: TCustomerExpanded) => {
|
|
225
|
-
try {
|
|
226
|
-
setState({ loading: true });
|
|
227
|
-
await api.put(`/api/customers/${data?.id}`, updates).then((res) => res.data);
|
|
228
|
-
Toast.success(t('common.saved'));
|
|
229
|
-
runAsync();
|
|
230
|
-
} catch (err) {
|
|
231
|
-
console.error(err);
|
|
232
|
-
Toast.error(formatError(err));
|
|
233
|
-
} finally {
|
|
234
|
-
setState({ loading: false });
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
213
|
const onToggleActive = (e: SelectChangeEvent) => {
|
|
239
214
|
setSubscriptionLoading(true);
|
|
240
215
|
setState({ onlyActive: e.target.value === 'active' });
|
|
@@ -247,58 +222,58 @@ export default function CustomerHome() {
|
|
|
247
222
|
setCurrency(newCurrency);
|
|
248
223
|
};
|
|
249
224
|
|
|
250
|
-
const SubscriptionCard =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
</
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
225
|
+
const SubscriptionCard =
|
|
226
|
+
loadingCard || !hasSubscriptions ? null : (
|
|
227
|
+
<Box className="base-card section section-subscription">
|
|
228
|
+
<Box className="section-header">
|
|
229
|
+
<Typography variant="h3">{t('admin.subscription.name')}</Typography>
|
|
230
|
+
{subscriptionStatus && (
|
|
231
|
+
<FormControl
|
|
232
|
+
sx={{
|
|
233
|
+
'.MuiInputBase-root': {
|
|
234
|
+
background: 'none',
|
|
235
|
+
border: 'none',
|
|
236
|
+
},
|
|
237
|
+
'.MuiOutlinedInput-notchedOutline': {
|
|
238
|
+
border: 'none',
|
|
239
|
+
},
|
|
240
|
+
}}>
|
|
241
|
+
<Select
|
|
242
|
+
value={state.onlyActive ? 'active' : ''}
|
|
243
|
+
onChange={onToggleActive}
|
|
244
|
+
displayEmpty
|
|
245
|
+
IconComponent={ExpandMore}
|
|
246
|
+
inputProps={{ 'aria-label': 'Without label' }}>
|
|
247
|
+
<MenuItem value="">All</MenuItem>
|
|
248
|
+
<MenuItem value="active">Active</MenuItem>
|
|
249
|
+
</Select>
|
|
250
|
+
</FormControl>
|
|
251
|
+
)}
|
|
252
|
+
</Box>
|
|
253
|
+
<Box className="section-body">
|
|
254
|
+
{subscriptionLoading ? (
|
|
255
|
+
<Box>{t('common.loading')}</Box>
|
|
256
|
+
) : (
|
|
257
|
+
<CurrentSubscriptions
|
|
258
|
+
id={data?.id}
|
|
259
|
+
onlyActive={state.onlyActive}
|
|
260
|
+
changeActive={(v) => setState({ onlyActive: v })}
|
|
261
|
+
status={state.onlyActive ? 'active,trialing,past_due' : 'active,trialing,paused,past_due,canceled'}
|
|
262
|
+
style={{
|
|
263
|
+
cursor: 'pointer',
|
|
264
|
+
}}
|
|
265
|
+
onClickSubscription={(subscription) => {
|
|
266
|
+
startTransition(() => {
|
|
267
|
+
navigate(`/customer/subscription/${subscription.id}`);
|
|
268
|
+
});
|
|
269
|
+
}}
|
|
270
|
+
setStatusState={setSubscriptionStatus}
|
|
271
|
+
setHasSubscriptions={setHasSubscriptions}
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
274
|
+
</Box>
|
|
299
275
|
</Box>
|
|
300
|
-
|
|
301
|
-
);
|
|
276
|
+
);
|
|
302
277
|
|
|
303
278
|
const SummaryCard = loadingCard ? (
|
|
304
279
|
<SummaryCardSkeleton />
|
|
@@ -345,11 +320,14 @@ export default function CustomerHome() {
|
|
|
345
320
|
</Box>
|
|
346
321
|
<Stack
|
|
347
322
|
className="section-body"
|
|
323
|
+
flexDirection="row"
|
|
348
324
|
sx={{
|
|
349
|
-
display: 'grid',
|
|
350
|
-
gridTemplateColumns: '1fr 1fr',
|
|
351
325
|
gap: 2,
|
|
352
326
|
mt: 1.5,
|
|
327
|
+
flexWrap: 'wrap',
|
|
328
|
+
'>div': {
|
|
329
|
+
flex: 1,
|
|
330
|
+
},
|
|
353
331
|
}}>
|
|
354
332
|
<CurrencyCard
|
|
355
333
|
label={t('admin.customer.summary.balance')}
|
|
@@ -359,30 +337,35 @@ export default function CustomerHome() {
|
|
|
359
337
|
background: 'var(--tags-tag-orange-bg, #B7FEE3)',
|
|
360
338
|
color: 'var(--tags-tag-orange-text, #007C52)',
|
|
361
339
|
}}
|
|
340
|
+
type="balance"
|
|
362
341
|
/>
|
|
363
342
|
<CurrencyCard
|
|
364
343
|
label={t('admin.customer.summary.spent')}
|
|
365
344
|
data={data?.summary?.paid || emptyObject}
|
|
366
345
|
currency={currency}
|
|
367
346
|
sx={{ background: 'var(--tags-tag-green-bg, #B7FEE3)', color: 'var(--tags-tag-green-text, #007C52)' }}
|
|
347
|
+
type="spent"
|
|
368
348
|
/>
|
|
369
349
|
<CurrencyCard
|
|
370
350
|
label={t('admin.customer.summary.stake')}
|
|
371
351
|
data={data?.summary?.stake || emptyObject}
|
|
372
352
|
currency={currency}
|
|
373
353
|
sx={{ background: 'var(--tags-tag-blue-bg, #D2ECFF)', color: 'var(--tags-tag-blue-text, #0051E9)' }}
|
|
354
|
+
type="stake"
|
|
374
355
|
/>
|
|
375
356
|
<CurrencyCard
|
|
376
357
|
label={t('admin.customer.summary.refund')}
|
|
377
358
|
data={data?.summary?.refund || emptyObject}
|
|
378
359
|
currency={currency}
|
|
379
360
|
sx={{ background: 'var(--tags-tag-purple-bg, #EFE9FF)', color: 'var(--tags-tag-purple-text, #007C52)' }}
|
|
361
|
+
type="refund"
|
|
380
362
|
/>
|
|
381
363
|
<CurrencyCard
|
|
382
364
|
label={t('admin.customer.summary.due')}
|
|
383
365
|
data={data?.summary?.due || emptyObject}
|
|
384
366
|
currency={currency}
|
|
385
367
|
sx={{ background: 'var(--tags-tag-red-bg, #FFE2E6)', color: 'var(--tags-tag-red-text, #E40031)' }}
|
|
368
|
+
type="due"
|
|
386
369
|
/>
|
|
387
370
|
</Stack>
|
|
388
371
|
</>
|
|
@@ -418,121 +401,6 @@ export default function CustomerHome() {
|
|
|
418
401
|
</Box>
|
|
419
402
|
);
|
|
420
403
|
|
|
421
|
-
const DetailCard = loadingCard ? (
|
|
422
|
-
<DetailCardSkeleton />
|
|
423
|
-
) : (
|
|
424
|
-
<Box className="base-card section section-detail">
|
|
425
|
-
<Box className="section-header" sx={{ mb: 2 }}>
|
|
426
|
-
<Typography variant="h3">{t('payment.customer.details')}</Typography>
|
|
427
|
-
<Button
|
|
428
|
-
variant="text"
|
|
429
|
-
sx={{ color: 'text.link' }}
|
|
430
|
-
disabled={state.editing || state.loading}
|
|
431
|
-
onClick={() => setState({ editing: true })}>
|
|
432
|
-
{t('common.edit')}
|
|
433
|
-
</Button>
|
|
434
|
-
</Box>
|
|
435
|
-
<Box display="flex" alignItems="center" gap={1} sx={{ mb: 3 }}>
|
|
436
|
-
<Avatar
|
|
437
|
-
title={data?.name}
|
|
438
|
-
src={getCustomerAvatar(data?.did, data?.updated_at ? new Date(data.updated_at).toISOString() : '', 48)}
|
|
439
|
-
variant="circular"
|
|
440
|
-
sx={{ width: 48, height: 48 }}
|
|
441
|
-
/>
|
|
442
|
-
<Box sx={{ minWidth: 0, flexGrow: 1 }}>
|
|
443
|
-
<Typography variant="h4" gutterBottom>
|
|
444
|
-
{data?.name || t('common.none')}
|
|
445
|
-
</Typography>
|
|
446
|
-
<Typography variant="body2" color="text.secondary">
|
|
447
|
-
{(data?.did || session.user?.did) && (
|
|
448
|
-
<DID
|
|
449
|
-
did={data?.did || session.user?.did || ''}
|
|
450
|
-
copyable
|
|
451
|
-
showQrcode
|
|
452
|
-
chainId={livemode ? 'main' : 'beta'}
|
|
453
|
-
/>
|
|
454
|
-
)}
|
|
455
|
-
</Typography>
|
|
456
|
-
</Box>
|
|
457
|
-
</Box>
|
|
458
|
-
<Stack>
|
|
459
|
-
<InfoRow
|
|
460
|
-
label={t('admin.customer.phone')}
|
|
461
|
-
value={data?.phone}
|
|
462
|
-
sizes={[1, 1]}
|
|
463
|
-
alignItems="normal"
|
|
464
|
-
direction="column"
|
|
465
|
-
/>
|
|
466
|
-
<InfoRow
|
|
467
|
-
label={t('admin.customer.email')}
|
|
468
|
-
value={data?.email}
|
|
469
|
-
sizes={[1, 1]}
|
|
470
|
-
alignItems="normal"
|
|
471
|
-
direction="column"
|
|
472
|
-
/>
|
|
473
|
-
<InfoRow
|
|
474
|
-
label={t('admin.customer.address.country')}
|
|
475
|
-
value={
|
|
476
|
-
data?.address?.country ? (
|
|
477
|
-
<Box display="flex" alignItems="center" flexWrap="nowrap" gap={0.5} sx={{ cursor: 'pointer' }}>
|
|
478
|
-
<FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
|
|
479
|
-
<Typography>{countryDetail?.name}</Typography>
|
|
480
|
-
</Box>
|
|
481
|
-
) : (
|
|
482
|
-
t('common.none')
|
|
483
|
-
)
|
|
484
|
-
}
|
|
485
|
-
sizes={[1, 1]}
|
|
486
|
-
alignItems="normal"
|
|
487
|
-
direction="column"
|
|
488
|
-
/>
|
|
489
|
-
<InfoRow
|
|
490
|
-
label={t('admin.customer.address.state')}
|
|
491
|
-
value={data?.address?.state}
|
|
492
|
-
sizes={[1, 1]}
|
|
493
|
-
alignItems="normal"
|
|
494
|
-
direction="column"
|
|
495
|
-
/>
|
|
496
|
-
<InfoRow
|
|
497
|
-
label={t('admin.customer.address.city')}
|
|
498
|
-
value={data?.address?.city && <TruncatedText text={data.address?.city} maxLength={280} useWidth />}
|
|
499
|
-
sizes={[1, 1]}
|
|
500
|
-
alignItems="normal"
|
|
501
|
-
direction="column"
|
|
502
|
-
/>
|
|
503
|
-
<InfoRow
|
|
504
|
-
label={t('admin.customer.address.line1')}
|
|
505
|
-
value={data?.address?.line1 && <TruncatedText text={data.address?.line1} maxLength={280} useWidth />}
|
|
506
|
-
sizes={[1, 1]}
|
|
507
|
-
alignItems="normal"
|
|
508
|
-
direction="column"
|
|
509
|
-
/>
|
|
510
|
-
<InfoRow
|
|
511
|
-
label={t('admin.customer.address.line2')}
|
|
512
|
-
value={data?.address?.line2 && <TruncatedText text={data.address?.line2} maxLength={280} useWidth />}
|
|
513
|
-
sizes={[1, 1]}
|
|
514
|
-
alignItems="normal"
|
|
515
|
-
direction="column"
|
|
516
|
-
/>
|
|
517
|
-
<InfoRow
|
|
518
|
-
label={t('admin.customer.address.postal_code')}
|
|
519
|
-
value={data?.address?.postal_code}
|
|
520
|
-
sizes={[1, 1]}
|
|
521
|
-
alignItems="normal"
|
|
522
|
-
direction="column"
|
|
523
|
-
/>
|
|
524
|
-
</Stack>
|
|
525
|
-
{state.editing && (
|
|
526
|
-
<EditCustomer
|
|
527
|
-
data={data}
|
|
528
|
-
loading={state.loading}
|
|
529
|
-
onSave={onUpdateInfo}
|
|
530
|
-
onCancel={() => setState({ editing: false })}
|
|
531
|
-
/>
|
|
532
|
-
)}
|
|
533
|
-
</Box>
|
|
534
|
-
);
|
|
535
|
-
|
|
536
404
|
const RevenueCard = loadingCard ? (
|
|
537
405
|
<CardSkeleton height={200} />
|
|
538
406
|
) : (
|
|
@@ -551,31 +419,57 @@ export default function CustomerHome() {
|
|
|
551
419
|
{data?.error}
|
|
552
420
|
</Alert>
|
|
553
421
|
)}
|
|
554
|
-
{
|
|
555
|
-
<
|
|
556
|
-
|
|
557
|
-
{
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
422
|
+
{isEmpty(data?.summary?.due) === false && (
|
|
423
|
+
<Alert
|
|
424
|
+
severity="error"
|
|
425
|
+
sx={{
|
|
426
|
+
mb: 2,
|
|
427
|
+
'.MuiAlert-action': {
|
|
428
|
+
alignItems: 'center',
|
|
429
|
+
},
|
|
430
|
+
}}
|
|
431
|
+
action={
|
|
432
|
+
<Button
|
|
433
|
+
color="inherit"
|
|
434
|
+
size="small"
|
|
435
|
+
variant="outlined"
|
|
436
|
+
onClick={() => setShowOverduePayment(true)}
|
|
437
|
+
sx={{ whiteSpace: 'nowrap' }}>
|
|
438
|
+
{t('customer.pastDue.payNow')}
|
|
439
|
+
</Button>
|
|
440
|
+
}>
|
|
441
|
+
{t('customer.pastDue.alert')}
|
|
442
|
+
</Alert>
|
|
443
|
+
)}
|
|
444
|
+
{showOverduePayment && data && (
|
|
445
|
+
<OverdueInvoicePayment
|
|
446
|
+
customerId={data.id}
|
|
447
|
+
onPaid={() => {
|
|
448
|
+
setShowOverduePayment(false);
|
|
449
|
+
runAsync();
|
|
450
|
+
}}
|
|
451
|
+
successToast={false}
|
|
452
|
+
detailLinkOptions={{
|
|
453
|
+
enabled: true,
|
|
454
|
+
onClick: () => {
|
|
455
|
+
setShowOverduePayment(false);
|
|
456
|
+
navigate('/customer/invoice/past-due');
|
|
457
|
+
},
|
|
458
|
+
}}
|
|
459
|
+
dialogProps={{
|
|
460
|
+
open: showOverduePayment,
|
|
461
|
+
onClose: () => setShowOverduePayment(false),
|
|
462
|
+
title: t('customer.pastDue.title'),
|
|
463
|
+
}}
|
|
464
|
+
/>
|
|
578
465
|
)}
|
|
466
|
+
<Root>
|
|
467
|
+
{SummaryCard}
|
|
468
|
+
{SubscriptionCard}
|
|
469
|
+
{InvoiceCard}
|
|
470
|
+
{RevenueCard}
|
|
471
|
+
{/* {DetailCard} */}
|
|
472
|
+
</Root>
|
|
579
473
|
</Content>
|
|
580
474
|
);
|
|
581
475
|
}
|
|
@@ -603,6 +497,7 @@ const Content = styled(Stack)`
|
|
|
603
497
|
`;
|
|
604
498
|
|
|
605
499
|
const Root = styled(Stack)`
|
|
500
|
+
gap: 24px;
|
|
606
501
|
@media (max-width: ${({ theme }) => theme.breakpoints.values.xl}px) {
|
|
607
502
|
padding: 0px;
|
|
608
503
|
gap: 0;
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from '@blocklet/payment-react';
|
|
18
18
|
import type { TCheckoutSession, TInvoiceExpanded, TPaymentLink } from '@blocklet/payment-types';
|
|
19
19
|
import { ArrowBackOutlined } from '@mui/icons-material';
|
|
20
|
-
import { Alert, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
|
|
20
|
+
import { Alert, Box, Button, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material';
|
|
21
21
|
import { styled } from '@mui/system';
|
|
22
22
|
import { useRequest, useSetState } from 'ahooks';
|
|
23
23
|
import { useEffect } from 'react';
|
|
@@ -204,9 +204,31 @@ export default function CustomerInvoiceDetail() {
|
|
|
204
204
|
}}>
|
|
205
205
|
<InfoMetric
|
|
206
206
|
label={t('common.status')}
|
|
207
|
-
value={
|
|
207
|
+
value={
|
|
208
|
+
<Tooltip
|
|
209
|
+
title={data.status === 'void' ? t('payment.customer.invoice.noPaymentRequired') : ''}
|
|
210
|
+
arrow
|
|
211
|
+
placement="top">
|
|
212
|
+
<span>
|
|
213
|
+
<Status label={data.status} color={getInvoiceStatusColor(data.status)} />
|
|
214
|
+
</span>
|
|
215
|
+
</Tooltip>
|
|
216
|
+
}
|
|
208
217
|
divider
|
|
209
218
|
/>
|
|
219
|
+
{data.subscription && (
|
|
220
|
+
<InfoMetric
|
|
221
|
+
label={t('admin.subscription.name')}
|
|
222
|
+
value={
|
|
223
|
+
<Link to={`/customer/subscription/${data.subscription.id}`}>
|
|
224
|
+
<Typography variant="body1" color="text.link">
|
|
225
|
+
{data.subscription.description || data.subscription.id}
|
|
226
|
+
</Typography>
|
|
227
|
+
</Link>
|
|
228
|
+
}
|
|
229
|
+
divider
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
210
232
|
<InfoMetric label={t('common.createdAt')} value={formatTime(data.created_at)} divider />
|
|
211
233
|
{data.period_start > 0 && data.period_end > 0 && (
|
|
212
234
|
<InfoMetric
|
|
@@ -301,20 +323,6 @@ export default function CustomerInvoiceDetail() {
|
|
|
301
323
|
alignItems={InfoAlignItems}
|
|
302
324
|
/>
|
|
303
325
|
)}
|
|
304
|
-
{data.subscription && (
|
|
305
|
-
<InfoRow
|
|
306
|
-
label={t('admin.subscription.name')}
|
|
307
|
-
value={
|
|
308
|
-
<Link to={`/customer/subscription/${data.subscription.id}`}>
|
|
309
|
-
<Typography variant="body1" color="text.link">
|
|
310
|
-
{data.subscription.description || data.subscription.id}
|
|
311
|
-
</Typography>
|
|
312
|
-
</Link>
|
|
313
|
-
}
|
|
314
|
-
direction={InfoDirection}
|
|
315
|
-
alignItems={InfoAlignItems}
|
|
316
|
-
/>
|
|
317
|
-
)}
|
|
318
326
|
{data?.relatedInvoice && (
|
|
319
327
|
<InfoRow
|
|
320
328
|
label={t('customer.invoice.relatedInvoice')}
|