payment-kit 1.18.15 → 1.18.17
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/libs/notification/template/customer-reward-succeeded.ts +32 -14
- package/api/src/libs/session.ts +9 -1
- package/api/src/libs/util.ts +12 -4
- package/api/src/routes/checkout-sessions.ts +286 -120
- 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/pay.ts +1 -1
- 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 +175 -54
- package/api/src/routes/connect/subscribe.ts +11 -1
- package/api/src/routes/customers.ts +150 -7
- package/api/src/routes/donations.ts +1 -1
- package/api/src/routes/invoices.ts +47 -1
- package/api/src/routes/subscriptions.ts +0 -3
- package/blocklet.yml +2 -1
- package/package.json +16 -16
- package/src/app.tsx +11 -3
- package/src/components/info-card.tsx +6 -2
- 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/payments/payouts/detail.tsx +16 -5
- package/src/pages/customer/index.tsx +226 -284
- package/src/pages/customer/invoice/detail.tsx +24 -16
- package/src/pages/customer/invoice/past-due.tsx +46 -23
- package/src/pages/customer/payout/detail.tsx +16 -5
- package/src/pages/customer/recharge/account.tsx +513 -0
- package/src/pages/customer/{recharge.tsx → recharge/subscription.tsx} +22 -19
- package/src/pages/customer/subscription/embed.tsx +16 -1
|
@@ -1,24 +1,27 @@
|
|
|
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 {
|
|
11
|
+
import {
|
|
12
|
+
ExpandMore,
|
|
13
|
+
AccountBalanceWalletOutlined,
|
|
14
|
+
AddOutlined,
|
|
15
|
+
CreditCardOutlined,
|
|
16
|
+
AccountBalanceOutlined,
|
|
17
|
+
InfoOutlined,
|
|
18
|
+
AssignmentReturnOutlined,
|
|
19
|
+
} from '@mui/icons-material';
|
|
16
20
|
import {
|
|
17
21
|
Avatar,
|
|
18
22
|
Box,
|
|
19
23
|
Button,
|
|
20
24
|
FormControl,
|
|
21
|
-
Grid,
|
|
22
25
|
MenuItem,
|
|
23
26
|
Select,
|
|
24
27
|
Skeleton,
|
|
@@ -28,16 +31,13 @@ import {
|
|
|
28
31
|
Alert,
|
|
29
32
|
} from '@mui/material';
|
|
30
33
|
import type { SelectChangeEvent } from '@mui/material/Select';
|
|
31
|
-
import { styled
|
|
34
|
+
import { styled } from '@mui/system';
|
|
32
35
|
import { useRequest, useSetState } from 'ahooks';
|
|
33
36
|
import { flatten, isEmpty } from 'lodash';
|
|
34
|
-
import { memo, useEffect,
|
|
35
|
-
import { FlagEmoji, defaultCountries, parseCountry } from 'react-international-phone';
|
|
37
|
+
import { memo, useEffect, useState } from 'react';
|
|
36
38
|
import { useNavigate } from 'react-router-dom';
|
|
37
39
|
import { joinURL } from 'ufo';
|
|
38
40
|
|
|
39
|
-
import EditCustomer from '../../components/customer/edit';
|
|
40
|
-
import InfoRow from '../../components/info-row';
|
|
41
41
|
import { useTransitionContext } from '../../components/progress-bar';
|
|
42
42
|
import CurrentSubscriptions from '../../components/subscription/portal/list';
|
|
43
43
|
import { useSessionContext } from '../../contexts/session';
|
|
@@ -57,33 +57,80 @@ const CurrencyCard = memo(
|
|
|
57
57
|
label,
|
|
58
58
|
data,
|
|
59
59
|
currency,
|
|
60
|
-
|
|
60
|
+
type,
|
|
61
|
+
icon,
|
|
61
62
|
}: {
|
|
62
63
|
label: string;
|
|
63
64
|
data: {
|
|
64
65
|
[key: string]: string;
|
|
65
66
|
};
|
|
67
|
+
icon: any;
|
|
66
68
|
currency: {
|
|
67
69
|
id: string;
|
|
68
70
|
decimal: number;
|
|
71
|
+
payment_method_id: string;
|
|
72
|
+
symbol: string;
|
|
69
73
|
};
|
|
70
|
-
|
|
74
|
+
type: 'balance' | 'spent' | 'stake' | 'refund' | 'due';
|
|
71
75
|
}) => {
|
|
76
|
+
const navigate = useNavigate();
|
|
77
|
+
const { settings } = usePaymentContext();
|
|
78
|
+
const method = settings?.paymentMethods?.find((m) => m.id === currency.payment_method_id);
|
|
79
|
+
const { t } = useLocaleContext();
|
|
72
80
|
const safeData = data || {};
|
|
73
81
|
const value = formatBNStr(safeData[currency?.id], currency?.decimal, 6, false);
|
|
82
|
+
const hideTypes = ['stake', 'refund', 'due'];
|
|
83
|
+
if (hideTypes.includes(type) && (!safeData[currency?.id] || safeData[currency?.id] === '0')) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handleCardClick = () => {
|
|
88
|
+
if (type === 'balance' && currency?.id) {
|
|
89
|
+
navigate(`/customer/recharge/${currency.id}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
74
93
|
return (
|
|
75
94
|
<Box
|
|
76
95
|
sx={{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
transition: 'all 0.2s ease-in-out',
|
|
97
|
+
position: 'relative',
|
|
98
|
+
borderRight: '1px solid var(--stroke-border-base, #EFF1F5)',
|
|
80
99
|
}}>
|
|
81
|
-
<Typography
|
|
100
|
+
<Typography
|
|
101
|
+
variant="body1"
|
|
102
|
+
gutterBottom
|
|
103
|
+
sx={{ color: 'text.secondary', display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
104
|
+
{icon}
|
|
82
105
|
{label}
|
|
83
106
|
</Typography>
|
|
84
|
-
<Typography variant="h3" gutterBottom>
|
|
85
|
-
{value}
|
|
107
|
+
<Typography variant="h3" gutterBottom sx={{ color: 'text.primary', whiteSpace: 'nowrap' }}>
|
|
108
|
+
{value} {currency.symbol}
|
|
86
109
|
</Typography>
|
|
110
|
+
{type === 'balance' && method?.type === 'arcblock' && (
|
|
111
|
+
<Tooltip title={t('customer.recharge.rechargeTooltip')} arrow placement="top">
|
|
112
|
+
<Box
|
|
113
|
+
onClick={handleCardClick}
|
|
114
|
+
sx={{
|
|
115
|
+
position: 'absolute',
|
|
116
|
+
top: 0,
|
|
117
|
+
right: '12px',
|
|
118
|
+
display: 'flex',
|
|
119
|
+
alignItems: 'center',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
borderRadius: '50%',
|
|
122
|
+
padding: '4px',
|
|
123
|
+
zIndex: 1,
|
|
124
|
+
cursor: 'pointer',
|
|
125
|
+
color: 'text.secondary',
|
|
126
|
+
'&:hover': {
|
|
127
|
+
color: 'text.link',
|
|
128
|
+
},
|
|
129
|
+
}}>
|
|
130
|
+
<AddOutlined fontSize="small" />
|
|
131
|
+
</Box>
|
|
132
|
+
</Tooltip>
|
|
133
|
+
)}
|
|
87
134
|
</Box>
|
|
88
135
|
);
|
|
89
136
|
}
|
|
@@ -104,58 +151,19 @@ function SummaryCardSkeleton() {
|
|
|
104
151
|
return (
|
|
105
152
|
<Box className="base-card section section-summary">
|
|
106
153
|
<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>
|
|
154
|
+
<Skeleton variant="rectangular" height={88} width="100%" />
|
|
147
155
|
</Box>
|
|
148
156
|
);
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
export default function CustomerHome() {
|
|
152
160
|
const { t } = useLocaleContext();
|
|
153
|
-
const { events
|
|
161
|
+
const { events } = useSessionContext();
|
|
154
162
|
const { settings } = usePaymentContext();
|
|
155
163
|
const [currency, setCurrency] = useState(settings?.baseCurrency);
|
|
156
164
|
const [subscriptionLoading, setSubscriptionLoading] = useState(false);
|
|
157
165
|
const currencies = flatten(
|
|
158
|
-
settings.paymentMethods
|
|
166
|
+
settings.paymentMethods?.map((method) =>
|
|
159
167
|
(method.payment_currencies || []).map((c) => ({
|
|
160
168
|
...c,
|
|
161
169
|
methodName: method.name,
|
|
@@ -171,8 +179,8 @@ export default function CustomerHome() {
|
|
|
171
179
|
setOverdraftProtection: false,
|
|
172
180
|
});
|
|
173
181
|
const navigate = useNavigate();
|
|
174
|
-
const { isMobile } = useMobile('lg');
|
|
175
182
|
const [subscriptionStatus, setSubscriptionStatus] = useState(false);
|
|
183
|
+
const [hasSubscriptions, setHasSubscriptions] = useState(false);
|
|
176
184
|
const { startTransition } = useTransitionContext();
|
|
177
185
|
const {
|
|
178
186
|
data,
|
|
@@ -181,14 +189,28 @@ export default function CustomerHome() {
|
|
|
181
189
|
loading = true,
|
|
182
190
|
} = useRequest(fetchData, {
|
|
183
191
|
manual: true,
|
|
192
|
+
onSuccess: async (res) => {
|
|
193
|
+
if (!hasSubscriptions) {
|
|
194
|
+
const search = new URLSearchParams();
|
|
195
|
+
const searchParams = {
|
|
196
|
+
page: 1,
|
|
197
|
+
pageSize: 5,
|
|
198
|
+
showTotalCount: true,
|
|
199
|
+
customer_id: res.id,
|
|
200
|
+
};
|
|
201
|
+
Object.entries(searchParams).forEach(([key, value]) => {
|
|
202
|
+
search.set(key, String(value));
|
|
203
|
+
});
|
|
204
|
+
const { data: subscriptions } = await api.get(`/api/subscriptions?${search.toString()}`);
|
|
205
|
+
if (subscriptions.totalCount > 0 || subscriptions.count > 0) {
|
|
206
|
+
setHasSubscriptions(true);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
184
210
|
});
|
|
185
211
|
|
|
186
212
|
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]);
|
|
213
|
+
const [showOverduePayment, setShowOverduePayment] = useState(false);
|
|
192
214
|
|
|
193
215
|
useEffect(() => {
|
|
194
216
|
runAsync();
|
|
@@ -221,20 +243,6 @@ export default function CustomerHome() {
|
|
|
221
243
|
);
|
|
222
244
|
}
|
|
223
245
|
|
|
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
246
|
const onToggleActive = (e: SelectChangeEvent) => {
|
|
239
247
|
setSubscriptionLoading(true);
|
|
240
248
|
setState({ onlyActive: e.target.value === 'active' });
|
|
@@ -247,58 +255,58 @@ export default function CustomerHome() {
|
|
|
247
255
|
setCurrency(newCurrency);
|
|
248
256
|
};
|
|
249
257
|
|
|
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
|
-
|
|
258
|
+
const SubscriptionCard =
|
|
259
|
+
loadingCard || !hasSubscriptions ? null : (
|
|
260
|
+
<Box className="base-card section section-subscription">
|
|
261
|
+
<Box className="section-header">
|
|
262
|
+
<Typography variant="h3">{t('admin.subscription.name')}</Typography>
|
|
263
|
+
{subscriptionStatus && (
|
|
264
|
+
<FormControl
|
|
265
|
+
sx={{
|
|
266
|
+
'.MuiInputBase-root': {
|
|
267
|
+
background: 'none',
|
|
268
|
+
border: 'none',
|
|
269
|
+
},
|
|
270
|
+
'.MuiOutlinedInput-notchedOutline': {
|
|
271
|
+
border: 'none',
|
|
272
|
+
},
|
|
273
|
+
}}>
|
|
274
|
+
<Select
|
|
275
|
+
value={state.onlyActive ? 'active' : ''}
|
|
276
|
+
onChange={onToggleActive}
|
|
277
|
+
displayEmpty
|
|
278
|
+
IconComponent={ExpandMore}
|
|
279
|
+
inputProps={{ 'aria-label': 'Without label' }}>
|
|
280
|
+
<MenuItem value="">All</MenuItem>
|
|
281
|
+
<MenuItem value="active">Active</MenuItem>
|
|
282
|
+
</Select>
|
|
283
|
+
</FormControl>
|
|
284
|
+
)}
|
|
285
|
+
</Box>
|
|
286
|
+
<Box className="section-body">
|
|
287
|
+
{subscriptionLoading ? (
|
|
288
|
+
<Box>{t('common.loading')}</Box>
|
|
289
|
+
) : (
|
|
290
|
+
<CurrentSubscriptions
|
|
291
|
+
id={data?.id}
|
|
292
|
+
onlyActive={state.onlyActive}
|
|
293
|
+
changeActive={(v) => setState({ onlyActive: v })}
|
|
294
|
+
status={state.onlyActive ? 'active,trialing,past_due' : 'active,trialing,paused,past_due,canceled'}
|
|
295
|
+
style={{
|
|
296
|
+
cursor: 'pointer',
|
|
297
|
+
}}
|
|
298
|
+
onClickSubscription={(subscription) => {
|
|
299
|
+
startTransition(() => {
|
|
300
|
+
navigate(`/customer/subscription/${subscription.id}`);
|
|
301
|
+
});
|
|
302
|
+
}}
|
|
303
|
+
setStatusState={setSubscriptionStatus}
|
|
304
|
+
setHasSubscriptions={setHasSubscriptions}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
</Box>
|
|
299
308
|
</Box>
|
|
300
|
-
|
|
301
|
-
);
|
|
309
|
+
);
|
|
302
310
|
|
|
303
311
|
const SummaryCard = loadingCard ? (
|
|
304
312
|
<SummaryCardSkeleton />
|
|
@@ -345,44 +353,66 @@ export default function CustomerHome() {
|
|
|
345
353
|
</Box>
|
|
346
354
|
<Stack
|
|
347
355
|
className="section-body"
|
|
356
|
+
flexDirection="row"
|
|
348
357
|
sx={{
|
|
349
|
-
|
|
350
|
-
gridTemplateColumns: '1fr 1fr',
|
|
351
|
-
gap: 2,
|
|
358
|
+
gap: 3,
|
|
352
359
|
mt: 1.5,
|
|
360
|
+
flexWrap: 'wrap',
|
|
361
|
+
position: 'relative',
|
|
362
|
+
'>div': {
|
|
363
|
+
flex: '1',
|
|
364
|
+
'@media (max-width: 1100px)': {
|
|
365
|
+
flex: '0 0 calc(50% - 12px)',
|
|
366
|
+
maxWidth: 'calc(50% - 12px)',
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
'&::after': {
|
|
370
|
+
content: '""',
|
|
371
|
+
position: 'absolute',
|
|
372
|
+
right: 0,
|
|
373
|
+
top: 0,
|
|
374
|
+
bottom: 0,
|
|
375
|
+
width: '1px',
|
|
376
|
+
backgroundColor: 'background.paper',
|
|
377
|
+
zIndex: 1,
|
|
378
|
+
},
|
|
353
379
|
}}>
|
|
354
380
|
<CurrencyCard
|
|
355
381
|
label={t('admin.customer.summary.balance')}
|
|
356
382
|
data={data?.summary?.token || emptyObject}
|
|
357
383
|
currency={currency}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
color: 'var(--tags-tag-orange-text, #007C52)',
|
|
361
|
-
}}
|
|
384
|
+
type="balance"
|
|
385
|
+
icon={<AccountBalanceWalletOutlined color="success" fontSize="small" />}
|
|
362
386
|
/>
|
|
363
387
|
<CurrencyCard
|
|
364
388
|
label={t('admin.customer.summary.spent')}
|
|
365
389
|
data={data?.summary?.paid || emptyObject}
|
|
366
390
|
currency={currency}
|
|
367
|
-
|
|
391
|
+
type="spent"
|
|
392
|
+
icon={<CreditCardOutlined color="warning" fontSize="small" />}
|
|
368
393
|
/>
|
|
369
394
|
<CurrencyCard
|
|
370
395
|
label={t('admin.customer.summary.stake')}
|
|
371
396
|
data={data?.summary?.stake || emptyObject}
|
|
372
397
|
currency={currency}
|
|
373
|
-
|
|
398
|
+
type="stake"
|
|
399
|
+
icon={<AccountBalanceOutlined fontSize="small" color="info" />}
|
|
374
400
|
/>
|
|
375
401
|
<CurrencyCard
|
|
376
402
|
label={t('admin.customer.summary.refund')}
|
|
377
403
|
data={data?.summary?.refund || emptyObject}
|
|
378
404
|
currency={currency}
|
|
379
|
-
|
|
405
|
+
type="refund"
|
|
406
|
+
icon={
|
|
407
|
+
<AssignmentReturnOutlined fontSize="small" sx={{ color: 'var(--tags-tag-purple-icon, #7c3aed)' }} />
|
|
408
|
+
}
|
|
380
409
|
/>
|
|
381
410
|
<CurrencyCard
|
|
382
411
|
label={t('admin.customer.summary.due')}
|
|
383
412
|
data={data?.summary?.due || emptyObject}
|
|
384
413
|
currency={currency}
|
|
385
|
-
|
|
414
|
+
type="due"
|
|
415
|
+
icon={<InfoOutlined fontSize="small" color="error" />}
|
|
386
416
|
/>
|
|
387
417
|
</Stack>
|
|
388
418
|
</>
|
|
@@ -413,126 +443,11 @@ export default function CustomerHome() {
|
|
|
413
443
|
)}
|
|
414
444
|
</Box>
|
|
415
445
|
<Box className="section-body">
|
|
416
|
-
<CustomerInvoiceList customer_id={data?.id} type="table" include_staking />
|
|
446
|
+
<CustomerInvoiceList customer_id={data?.id} type="table" include_staking relatedSubscription />
|
|
417
447
|
</Box>
|
|
418
448
|
</Box>
|
|
419
449
|
);
|
|
420
450
|
|
|
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
451
|
const RevenueCard = loadingCard ? (
|
|
537
452
|
<CardSkeleton height={200} />
|
|
538
453
|
) : (
|
|
@@ -551,31 +466,57 @@ export default function CustomerHome() {
|
|
|
551
466
|
{data?.error}
|
|
552
467
|
</Alert>
|
|
553
468
|
)}
|
|
554
|
-
{
|
|
555
|
-
<
|
|
556
|
-
|
|
557
|
-
{
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
469
|
+
{isEmpty(data?.summary?.due) === false && (
|
|
470
|
+
<Alert
|
|
471
|
+
severity="error"
|
|
472
|
+
sx={{
|
|
473
|
+
mb: 2,
|
|
474
|
+
'.MuiAlert-action': {
|
|
475
|
+
alignItems: 'center',
|
|
476
|
+
},
|
|
477
|
+
}}
|
|
478
|
+
action={
|
|
479
|
+
<Button
|
|
480
|
+
color="inherit"
|
|
481
|
+
size="small"
|
|
482
|
+
variant="outlined"
|
|
483
|
+
onClick={() => setShowOverduePayment(true)}
|
|
484
|
+
sx={{ whiteSpace: 'nowrap' }}>
|
|
485
|
+
{t('customer.pastDue.payNow')}
|
|
486
|
+
</Button>
|
|
487
|
+
}>
|
|
488
|
+
{t('customer.pastDue.alert')}
|
|
489
|
+
</Alert>
|
|
490
|
+
)}
|
|
491
|
+
{showOverduePayment && data && (
|
|
492
|
+
<OverdueInvoicePayment
|
|
493
|
+
customerId={data.id}
|
|
494
|
+
onPaid={() => {
|
|
495
|
+
setShowOverduePayment(false);
|
|
496
|
+
runAsync();
|
|
497
|
+
}}
|
|
498
|
+
successToast={false}
|
|
499
|
+
detailLinkOptions={{
|
|
500
|
+
enabled: true,
|
|
501
|
+
onClick: () => {
|
|
502
|
+
setShowOverduePayment(false);
|
|
503
|
+
navigate('/customer/invoice/past-due');
|
|
504
|
+
},
|
|
505
|
+
}}
|
|
506
|
+
dialogProps={{
|
|
507
|
+
open: showOverduePayment,
|
|
508
|
+
onClose: () => setShowOverduePayment(false),
|
|
509
|
+
title: t('customer.pastDue.title'),
|
|
510
|
+
}}
|
|
511
|
+
/>
|
|
578
512
|
)}
|
|
513
|
+
<Root>
|
|
514
|
+
{SummaryCard}
|
|
515
|
+
{SubscriptionCard}
|
|
516
|
+
{InvoiceCard}
|
|
517
|
+
{RevenueCard}
|
|
518
|
+
{/* {DetailCard} */}
|
|
519
|
+
</Root>
|
|
579
520
|
</Content>
|
|
580
521
|
);
|
|
581
522
|
}
|
|
@@ -603,6 +544,7 @@ const Content = styled(Stack)`
|
|
|
603
544
|
`;
|
|
604
545
|
|
|
605
546
|
const Root = styled(Stack)`
|
|
547
|
+
gap: 24px;
|
|
606
548
|
@media (max-width: ${({ theme }) => theme.breakpoints.values.xl}px) {
|
|
607
549
|
padding: 0px;
|
|
608
550
|
gap: 0;
|