payment-kit 1.18.15 → 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/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/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
package/src/locales/en.tsx
CHANGED
|
@@ -796,6 +796,8 @@ export default flat({
|
|
|
796
796
|
estimatedDuration: '{duration} {unit} est.',
|
|
797
797
|
intervals: 'intervals',
|
|
798
798
|
history: 'Fund History',
|
|
799
|
+
relatedSubscriptions: 'Related Subscriptions',
|
|
800
|
+
rechargeTooltip: 'Click to recharge balance',
|
|
799
801
|
},
|
|
800
802
|
delegation: {
|
|
801
803
|
title:
|
|
@@ -855,6 +857,13 @@ export default flat({
|
|
|
855
857
|
viewReference: 'View Reference',
|
|
856
858
|
title: 'Revenue',
|
|
857
859
|
},
|
|
860
|
+
pastDue: {
|
|
861
|
+
warning: 'You have due invoices, please pay them to keep your services active',
|
|
862
|
+
invoices: 'Due Invoices',
|
|
863
|
+
payNow: 'Pay Now',
|
|
864
|
+
alert: 'You have due invoices. Please pay them promptly to avoid service interruption.',
|
|
865
|
+
title: 'Settle Due Invoices',
|
|
866
|
+
},
|
|
858
867
|
},
|
|
859
868
|
integrations: {
|
|
860
869
|
basicFeatures: 'Basic Features',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -774,6 +774,8 @@ export default flat({
|
|
|
774
774
|
custom: '自定义',
|
|
775
775
|
intervals: '个周期',
|
|
776
776
|
history: '充值记录',
|
|
777
|
+
relatedSubscriptions: '关联订阅',
|
|
778
|
+
rechargeTooltip: '点击充值余额',
|
|
777
779
|
},
|
|
778
780
|
delegation: {
|
|
779
781
|
title: '检测到你未完成账户的授权,为了不影响订阅的扣费,请尽快完成授权',
|
|
@@ -828,6 +830,13 @@ export default flat({
|
|
|
828
830
|
viewReference: '查看打赏原文',
|
|
829
831
|
title: '收款记录',
|
|
830
832
|
},
|
|
833
|
+
pastDue: {
|
|
834
|
+
warning: '您有欠费账单,请及时付款以保持服务正常运行',
|
|
835
|
+
invoices: '欠费账单',
|
|
836
|
+
payNow: '立即付款',
|
|
837
|
+
alert: '您有欠费账单需要处理,请及时付款以避免服务中断',
|
|
838
|
+
title: '结清欠费账单',
|
|
839
|
+
},
|
|
831
840
|
},
|
|
832
841
|
integrations: {
|
|
833
842
|
basicFeatures: '基础功能',
|
|
@@ -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;
|