payment-kit 1.18.18 → 1.18.20
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/libs/subscription.ts +116 -0
- package/api/src/routes/checkout-sessions.ts +28 -1
- package/api/src/routes/customers.ts +5 -1
- package/api/src/store/migrations/20250318-donate-invoice.ts +45 -0
- package/api/tests/libs/subscription.spec.ts +311 -0
- package/blocklet.yml +1 -1
- package/package.json +8 -8
- package/src/components/currency.tsx +11 -4
- package/src/components/customer/link.tsx +54 -14
- package/src/components/customer/overdraft-protection.tsx +36 -2
- package/src/components/info-card.tsx +55 -7
- package/src/components/info-row-group.tsx +122 -0
- package/src/components/info-row.tsx +14 -1
- package/src/components/payouts/portal/list.tsx +7 -2
- package/src/components/subscription/items/index.tsx +1 -1
- package/src/components/subscription/metrics.tsx +14 -6
- package/src/contexts/info-row.tsx +4 -0
- package/src/libs/dayjs.ts +4 -3
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
- package/src/pages/admin/billing/invoices/detail.tsx +54 -76
- package/src/pages/admin/billing/subscriptions/detail.tsx +34 -71
- package/src/pages/admin/customers/customers/detail.tsx +41 -64
- package/src/pages/admin/payments/intents/detail.tsx +28 -42
- package/src/pages/admin/payments/payouts/detail.tsx +27 -36
- package/src/pages/admin/payments/refunds/detail.tsx +27 -41
- package/src/pages/admin/products/links/detail.tsx +30 -55
- package/src/pages/admin/products/prices/detail.tsx +43 -50
- package/src/pages/admin/products/pricing-tables/detail.tsx +23 -25
- package/src/pages/admin/products/products/detail.tsx +52 -81
- package/src/pages/customer/index.tsx +189 -107
- package/src/pages/customer/invoice/detail.tsx +49 -50
- package/src/pages/customer/payout/detail.tsx +16 -22
- package/src/pages/customer/recharge/account.tsx +119 -34
- package/src/pages/customer/recharge/subscription.tsx +11 -1
- package/src/pages/customer/subscription/detail.tsx +176 -94
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
Button,
|
|
12
12
|
CircularProgress,
|
|
13
13
|
Divider,
|
|
14
|
-
Grid,
|
|
15
14
|
Stack,
|
|
16
15
|
Tooltip,
|
|
17
16
|
Typography,
|
|
@@ -25,6 +24,7 @@ import Copyable from '../../../../components/copyable';
|
|
|
25
24
|
import EventList from '../../../../components/event/list';
|
|
26
25
|
import InfoMetric from '../../../../components/info-metric';
|
|
27
26
|
import InfoRow from '../../../../components/info-row';
|
|
27
|
+
import InfoRowGroup from '../../../../components/info-row-group';
|
|
28
28
|
import MetadataEditor from '../../../../components/metadata/editor';
|
|
29
29
|
import MetadataList from '../../../../components/metadata/list';
|
|
30
30
|
import ProductActions from '../../../../components/product/actions';
|
|
@@ -39,9 +39,6 @@ const getProduct = (id: string): Promise<TProductExpanded> => {
|
|
|
39
39
|
return api.get(`/api/products/${id}`).then((res) => res.data);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const InfoDirection = 'column';
|
|
43
|
-
const InfoAlignItems = 'flex-start';
|
|
44
|
-
|
|
45
42
|
export default function ProductDetail(props: { id: string }) {
|
|
46
43
|
const { t, locale } = useLocaleContext();
|
|
47
44
|
const { isMobile } = useMobile();
|
|
@@ -212,6 +209,7 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
212
209
|
},
|
|
213
210
|
}}>
|
|
214
211
|
<InfoMetric label={t('common.id')} value={<Copyable text={props.id} style={{ marginLeft: 4 }} />} divider />
|
|
212
|
+
<InfoMetric label={t('common.createdAt')} value={formatTime(data.created_at)} divider />
|
|
215
213
|
</Stack>
|
|
216
214
|
</Box>
|
|
217
215
|
<Divider />
|
|
@@ -246,7 +244,7 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
246
244
|
},
|
|
247
245
|
}}>
|
|
248
246
|
<Box flex={1} className="payment-link-column-1" sx={{ gap: 2.5, display: 'flex', flexDirection: 'column' }}>
|
|
249
|
-
<Box className="section">
|
|
247
|
+
<Box className="section" sx={{ containerType: 'inline-size' }}>
|
|
250
248
|
<SectionHeader title={t('admin.details')}>
|
|
251
249
|
<Button
|
|
252
250
|
variant="text"
|
|
@@ -257,83 +255,56 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
257
255
|
{t('common.edit')}
|
|
258
256
|
</Button>
|
|
259
257
|
</SectionHeader>
|
|
260
|
-
<
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
258
|
+
<InfoRowGroup
|
|
259
|
+
sx={{
|
|
260
|
+
display: 'grid',
|
|
261
|
+
gridTemplateColumns: {
|
|
262
|
+
xs: 'repeat(1, 1fr)',
|
|
263
|
+
xl: 'repeat(2, 1fr)',
|
|
264
|
+
},
|
|
265
|
+
'@container (min-width: 1000px)': {
|
|
266
|
+
gridTemplateColumns: 'repeat(2, 1fr)',
|
|
267
|
+
},
|
|
268
|
+
'.info-row-wrapper': {
|
|
269
|
+
gap: 1,
|
|
270
|
+
flexDirection: {
|
|
271
|
+
xs: 'column',
|
|
272
|
+
xl: 'row',
|
|
271
273
|
},
|
|
272
|
-
|
|
273
|
-
xs:
|
|
274
|
-
|
|
274
|
+
alignItems: {
|
|
275
|
+
xs: 'flex-start',
|
|
276
|
+
xl: 'center',
|
|
275
277
|
},
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
/>
|
|
307
|
-
<InfoRow
|
|
308
|
-
label={t('common.createdAt')}
|
|
309
|
-
value={formatTime(data.created_at)}
|
|
310
|
-
direction={InfoDirection}
|
|
311
|
-
alignItems={InfoAlignItems}
|
|
312
|
-
/>
|
|
313
|
-
<InfoRow
|
|
314
|
-
label={t('common.updatedAt')}
|
|
315
|
-
value={formatTime(data.updated_at)}
|
|
316
|
-
direction={InfoDirection}
|
|
317
|
-
alignItems={InfoAlignItems}
|
|
318
|
-
/>
|
|
319
|
-
<InfoRow
|
|
320
|
-
label={t('admin.product.image.label')}
|
|
321
|
-
value={
|
|
322
|
-
data.images.length ? (
|
|
323
|
-
<Avatar
|
|
324
|
-
src={data.images[0]}
|
|
325
|
-
alt={data.name}
|
|
326
|
-
variant="square"
|
|
327
|
-
sx={{ width: '160px', height: '160px' }}
|
|
328
|
-
/>
|
|
329
|
-
) : (
|
|
330
|
-
t('empty.image')
|
|
331
|
-
)
|
|
332
|
-
}
|
|
333
|
-
direction={InfoDirection}
|
|
334
|
-
alignItems={InfoAlignItems}
|
|
335
|
-
/>
|
|
336
|
-
</Grid>
|
|
278
|
+
'@container (min-width: 1000px)': {
|
|
279
|
+
flexDirection: 'row',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
}}>
|
|
284
|
+
<InfoRow label={t('admin.product.name.label')} value={data.name} />
|
|
285
|
+
|
|
286
|
+
<InfoRow label={t('admin.product.description.label')} value={data.description} />
|
|
287
|
+
<InfoRow label={t('admin.product.statement_descriptor.label')} value={data.statement_descriptor} />
|
|
288
|
+
<InfoRow label={t('admin.product.unit_label.label')} value={data.unit_label} />
|
|
289
|
+
<InfoRow label={t('admin.product.features.label')} value={data.features.map((x) => x.name).join(',')} />
|
|
290
|
+
<InfoRow label={t('common.updatedAt')} value={formatTime(data.updated_at)} />
|
|
291
|
+
<InfoRow
|
|
292
|
+
label={t('admin.product.image.label')}
|
|
293
|
+
value={
|
|
294
|
+
data.images.length ? (
|
|
295
|
+
<Avatar
|
|
296
|
+
src={data.images[0]}
|
|
297
|
+
alt={data.name}
|
|
298
|
+
variant="square"
|
|
299
|
+
sx={{ width: '160px', height: '160px' }}
|
|
300
|
+
/>
|
|
301
|
+
) : (
|
|
302
|
+
<Typography variant="body2" color="text.lighter">
|
|
303
|
+
{t('empty.image')}
|
|
304
|
+
</Typography>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
/>
|
|
337
308
|
{state.editing.product && (
|
|
338
309
|
<EditProduct
|
|
339
310
|
product={data}
|
|
@@ -342,7 +313,7 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
342
313
|
onCancel={() => setState((prev) => ({ editing: { ...prev.editing, product: false } }))}
|
|
343
314
|
/>
|
|
344
315
|
)}
|
|
345
|
-
</
|
|
316
|
+
</InfoRowGroup>
|
|
346
317
|
</Box>
|
|
347
318
|
<Divider />
|
|
348
319
|
<Box className="section">
|
|
@@ -78,11 +78,7 @@ const CurrencyCard = memo(
|
|
|
78
78
|
const method = settings?.paymentMethods?.find((m) => m.id === currency.payment_method_id);
|
|
79
79
|
const { t } = useLocaleContext();
|
|
80
80
|
const safeData = data || {};
|
|
81
|
-
const value = formatBNStr(safeData[currency?.id], currency?.decimal, 6,
|
|
82
|
-
const hideTypes = ['stake', 'refund', 'due'];
|
|
83
|
-
if (hideTypes.includes(type) && (!safeData[currency?.id] || safeData[currency?.id] === '0')) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
81
|
+
const value = formatBNStr(safeData[currency?.id], currency?.decimal, 6, true);
|
|
86
82
|
|
|
87
83
|
const handleCardClick = () => {
|
|
88
84
|
if (type === 'balance' && currency?.id) {
|
|
@@ -95,7 +91,11 @@ const CurrencyCard = memo(
|
|
|
95
91
|
sx={{
|
|
96
92
|
transition: 'all 0.2s ease-in-out',
|
|
97
93
|
position: 'relative',
|
|
98
|
-
|
|
94
|
+
height: '100%',
|
|
95
|
+
display: 'flex',
|
|
96
|
+
flexDirection: 'column',
|
|
97
|
+
justifyContent: 'space-between',
|
|
98
|
+
containerType: 'inline-size',
|
|
99
99
|
}}>
|
|
100
100
|
<Typography
|
|
101
101
|
variant="body1"
|
|
@@ -126,8 +126,20 @@ const CurrencyCard = memo(
|
|
|
126
126
|
'&:hover': {
|
|
127
127
|
color: 'text.link',
|
|
128
128
|
},
|
|
129
|
+
'.recharge-title': {
|
|
130
|
+
'@container (max-width: 230px)': {
|
|
131
|
+
display: 'none',
|
|
132
|
+
},
|
|
133
|
+
'@container (min-width: 231px)': {
|
|
134
|
+
display: 'inline-block',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
129
137
|
}}>
|
|
130
138
|
<AddOutlined fontSize="small" />
|
|
139
|
+
|
|
140
|
+
<Typography variant="body2" className="recharge-title">
|
|
141
|
+
{t('customer.recharge.title')}
|
|
142
|
+
</Typography>
|
|
131
143
|
</Box>
|
|
132
144
|
</Tooltip>
|
|
133
145
|
)}
|
|
@@ -156,11 +168,80 @@ function SummaryCardSkeleton() {
|
|
|
156
168
|
);
|
|
157
169
|
}
|
|
158
170
|
|
|
171
|
+
function isSummaryEmpty(summary: any, id: string) {
|
|
172
|
+
const summaryKeys = ['balance', 'paid', 'stake', 'refund', 'due'];
|
|
173
|
+
return summaryKeys.every((key) => !summary?.[key]?.[id] || summary?.[key]?.[id] === '0');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 定义卡片类型
|
|
177
|
+
type CardType = 'balance' | 'spent' | 'stake' | 'refund' | 'due';
|
|
178
|
+
|
|
179
|
+
// 定义一个统一的卡片配置
|
|
180
|
+
const CARD_CONFIG: Record<CardType, { key: keyof typeof summaryKeyMap; alwaysShow?: boolean }> = {
|
|
181
|
+
balance: { key: 'token' },
|
|
182
|
+
spent: { key: 'paid', alwaysShow: true },
|
|
183
|
+
stake: { key: 'stake' },
|
|
184
|
+
refund: { key: 'refund' },
|
|
185
|
+
due: { key: 'due' },
|
|
186
|
+
} as const;
|
|
187
|
+
|
|
188
|
+
// 映射 summary 中的键名
|
|
189
|
+
const summaryKeyMap = {
|
|
190
|
+
token: 'token',
|
|
191
|
+
paid: 'paid',
|
|
192
|
+
stake: 'stake',
|
|
193
|
+
refund: 'refund',
|
|
194
|
+
due: 'due',
|
|
195
|
+
} as const;
|
|
196
|
+
|
|
197
|
+
const isCardVisible = (type: string, config: any, data: any, currency: any, method: any) => {
|
|
198
|
+
const summaryKey = config.key;
|
|
199
|
+
const hasSummaryValue =
|
|
200
|
+
data?.summary?.[summaryKey]?.[currency.id] && data?.summary?.[summaryKey]?.[currency.id] !== '0';
|
|
201
|
+
|
|
202
|
+
if (type === 'balance') {
|
|
203
|
+
return method?.type === 'arcblock' && (config.alwaysShow || hasSummaryValue);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return config.alwaysShow || hasSummaryValue;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const getCardVisibility = (currency: any, data: any, method: any) => {
|
|
210
|
+
return Object.entries(CARD_CONFIG).reduce(
|
|
211
|
+
(acc, [type, config]) => {
|
|
212
|
+
if (isCardVisible(type, config, data, currency, method)) {
|
|
213
|
+
acc.visibleCards.push(type as CardType);
|
|
214
|
+
}
|
|
215
|
+
return acc;
|
|
216
|
+
},
|
|
217
|
+
{ visibleCards: [] as CardType[] }
|
|
218
|
+
);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// 计算最大卡片数量
|
|
222
|
+
const calculateMaxCardCount = (currencies: any[], data: any, settings: any) => {
|
|
223
|
+
return currencies.reduce((maxCount, currency) => {
|
|
224
|
+
const method = settings?.paymentMethods?.find((m: any) => m.id === currency.payment_method_id);
|
|
225
|
+
const { visibleCards } = getCardVisibility(currency, data, method);
|
|
226
|
+
return Math.max(maxCount, visibleCards.length);
|
|
227
|
+
}, 0);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const getCardIcon = (type: CardType) => {
|
|
231
|
+
const iconMap = {
|
|
232
|
+
balance: <AccountBalanceWalletOutlined color="success" fontSize="small" />,
|
|
233
|
+
spent: <CreditCardOutlined color="warning" fontSize="small" />,
|
|
234
|
+
stake: <AccountBalanceOutlined fontSize="small" color="info" />,
|
|
235
|
+
refund: <AssignmentReturnOutlined fontSize="small" sx={{ color: 'var(--tags-tag-purple-icon, #7c3aed)' }} />,
|
|
236
|
+
due: <InfoOutlined fontSize="small" color="error" />,
|
|
237
|
+
};
|
|
238
|
+
return iconMap[type];
|
|
239
|
+
};
|
|
240
|
+
|
|
159
241
|
export default function CustomerHome() {
|
|
160
242
|
const { t } = useLocaleContext();
|
|
161
243
|
const { events } = useSessionContext();
|
|
162
244
|
const { settings } = usePaymentContext();
|
|
163
|
-
const [currency, setCurrency] = useState(settings?.baseCurrency);
|
|
164
245
|
const [subscriptionLoading, setSubscriptionLoading] = useState(false);
|
|
165
246
|
const currencies = flatten(
|
|
166
247
|
settings.paymentMethods?.map((method) =>
|
|
@@ -250,10 +331,6 @@ export default function CustomerHome() {
|
|
|
250
331
|
setSubscriptionLoading(false);
|
|
251
332
|
}, 300);
|
|
252
333
|
};
|
|
253
|
-
const handleCurrencyChange = (e: SelectChangeEvent) => {
|
|
254
|
-
const newCurrency = currencies.find((c) => c.id === e.target.value) || settings?.baseCurrency;
|
|
255
|
-
setCurrency(newCurrency);
|
|
256
|
-
};
|
|
257
334
|
|
|
258
335
|
const SubscriptionCard =
|
|
259
336
|
loadingCard || !hasSubscriptions ? null : (
|
|
@@ -308,6 +385,14 @@ export default function CustomerHome() {
|
|
|
308
385
|
</Box>
|
|
309
386
|
);
|
|
310
387
|
|
|
388
|
+
const maxCardCount = calculateMaxCardCount(currencies, data, settings);
|
|
389
|
+
|
|
390
|
+
const responsiveColumns = {
|
|
391
|
+
xs: Math.min(2, maxCardCount),
|
|
392
|
+
sm: Math.min(3, maxCardCount),
|
|
393
|
+
md: maxCardCount,
|
|
394
|
+
};
|
|
395
|
+
|
|
311
396
|
const SummaryCard = loadingCard ? (
|
|
312
397
|
<SummaryCardSkeleton />
|
|
313
398
|
) : (
|
|
@@ -316,104 +401,101 @@ export default function CustomerHome() {
|
|
|
316
401
|
<>
|
|
317
402
|
<Box className="section-header">
|
|
318
403
|
<Typography variant="h3">{t('admin.customer.summary.stats')}</Typography>
|
|
319
|
-
<FormControl
|
|
320
|
-
sx={{
|
|
321
|
-
'.MuiInputBase-root': {
|
|
322
|
-
background: 'none',
|
|
323
|
-
border: 'none',
|
|
324
|
-
},
|
|
325
|
-
'.MuiOutlinedInput-notchedOutline': {
|
|
326
|
-
border: 'none',
|
|
327
|
-
},
|
|
328
|
-
}}>
|
|
329
|
-
<Select
|
|
330
|
-
value={currency?.id}
|
|
331
|
-
onChange={handleCurrencyChange}
|
|
332
|
-
displayEmpty
|
|
333
|
-
IconComponent={ExpandMore}
|
|
334
|
-
inputProps={{ 'aria-label': 'Without label' }}>
|
|
335
|
-
{currencies.map((c) => (
|
|
336
|
-
<MenuItem key={c.id} value={c.id}>
|
|
337
|
-
<Box alignItems="center" display="flex" gap={1}>
|
|
338
|
-
<Avatar src={c?.logo} alt={c?.symbol} sx={{ width: 18, height: 18 }} />
|
|
339
|
-
<Typography
|
|
340
|
-
variant="h5"
|
|
341
|
-
component="div"
|
|
342
|
-
sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
|
|
343
|
-
{c?.symbol}
|
|
344
|
-
</Typography>
|
|
345
|
-
<Typography sx={{ fontSize: 12 }} color="text.lighter">
|
|
346
|
-
{c?.methodName}
|
|
347
|
-
</Typography>
|
|
348
|
-
</Box>
|
|
349
|
-
</MenuItem>
|
|
350
|
-
))}
|
|
351
|
-
</Select>
|
|
352
|
-
</FormControl>
|
|
353
404
|
</Box>
|
|
354
|
-
<Stack
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
},
|
|
379
|
-
}}>
|
|
380
|
-
<CurrencyCard
|
|
381
|
-
label={t('admin.customer.summary.balance')}
|
|
382
|
-
data={data?.summary?.token || emptyObject}
|
|
383
|
-
currency={currency}
|
|
384
|
-
type="balance"
|
|
385
|
-
icon={<AccountBalanceWalletOutlined color="success" fontSize="small" />}
|
|
386
|
-
/>
|
|
387
|
-
<CurrencyCard
|
|
388
|
-
label={t('admin.customer.summary.spent')}
|
|
389
|
-
data={data?.summary?.paid || emptyObject}
|
|
390
|
-
currency={currency}
|
|
391
|
-
type="spent"
|
|
392
|
-
icon={<CreditCardOutlined color="warning" fontSize="small" />}
|
|
393
|
-
/>
|
|
394
|
-
<CurrencyCard
|
|
395
|
-
label={t('admin.customer.summary.stake')}
|
|
396
|
-
data={data?.summary?.stake || emptyObject}
|
|
397
|
-
currency={currency}
|
|
398
|
-
type="stake"
|
|
399
|
-
icon={<AccountBalanceOutlined fontSize="small" color="info" />}
|
|
400
|
-
/>
|
|
401
|
-
<CurrencyCard
|
|
402
|
-
label={t('admin.customer.summary.refund')}
|
|
403
|
-
data={data?.summary?.refund || emptyObject}
|
|
404
|
-
currency={currency}
|
|
405
|
-
type="refund"
|
|
406
|
-
icon={
|
|
407
|
-
<AssignmentReturnOutlined fontSize="small" sx={{ color: 'var(--tags-tag-purple-icon, #7c3aed)' }} />
|
|
405
|
+
<Stack gap={2} mt={2}>
|
|
406
|
+
{currencies.map((c) => {
|
|
407
|
+
const method = settings?.paymentMethods?.find((m) => m.id === c.payment_method_id);
|
|
408
|
+
if (method?.type !== 'arcblock') {
|
|
409
|
+
return null;
|
|
408
410
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
411
|
+
return isSummaryEmpty(data?.summary, c.id) && c.id !== settings.baseCurrency.id ? null : (
|
|
412
|
+
<Stack
|
|
413
|
+
key={c.id}
|
|
414
|
+
sx={{
|
|
415
|
+
pb: 2,
|
|
416
|
+
borderBottom: '1px solid var(--stroke-border-base, #EFF1F5)',
|
|
417
|
+
'&:last-child': { borderBottom: 'none', pb: 0 },
|
|
418
|
+
}}>
|
|
419
|
+
<Box alignItems="center" display="flex" gap={1}>
|
|
420
|
+
<Avatar src={c?.logo} alt={c?.symbol} sx={{ width: 18, height: 18 }} />
|
|
421
|
+
<Typography
|
|
422
|
+
variant="h5"
|
|
423
|
+
component="div"
|
|
424
|
+
sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
|
|
425
|
+
{c?.symbol}
|
|
426
|
+
</Typography>
|
|
427
|
+
<Typography sx={{ fontSize: 12 }} color="text.lighter">
|
|
428
|
+
{c?.methodName}
|
|
429
|
+
</Typography>
|
|
430
|
+
</Box>
|
|
431
|
+
<Stack
|
|
432
|
+
className="section-body"
|
|
433
|
+
flexDirection="row"
|
|
434
|
+
sx={{
|
|
435
|
+
gap: 3,
|
|
436
|
+
mt: 1.5,
|
|
437
|
+
flexWrap: 'wrap',
|
|
438
|
+
position: 'relative',
|
|
439
|
+
display: 'grid',
|
|
440
|
+
gridTemplateColumns: {
|
|
441
|
+
xs: `repeat(${responsiveColumns.xs}, 1fr)`,
|
|
442
|
+
sm: `repeat(${responsiveColumns.sm}, 1fr)`,
|
|
443
|
+
md: `repeat(${responsiveColumns.md}, 1fr)`,
|
|
444
|
+
},
|
|
445
|
+
'&::after': {
|
|
446
|
+
content: '""',
|
|
447
|
+
position: 'absolute',
|
|
448
|
+
right: 0,
|
|
449
|
+
top: 0,
|
|
450
|
+
bottom: 0,
|
|
451
|
+
width: '1px',
|
|
452
|
+
backgroundColor: 'background.paper',
|
|
453
|
+
zIndex: 1,
|
|
454
|
+
},
|
|
455
|
+
}}>
|
|
456
|
+
{/* 使用配置渲染卡片 */}
|
|
457
|
+
{Object.entries(CARD_CONFIG).map(([type, config]) => {
|
|
458
|
+
if (!isCardVisible(type, config, data, c, method)) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<Box
|
|
464
|
+
key={`card-${c.id}-${type}`}
|
|
465
|
+
className="card-item"
|
|
466
|
+
sx={{
|
|
467
|
+
position: 'relative',
|
|
468
|
+
'&::after': {
|
|
469
|
+
content: '""',
|
|
470
|
+
position: 'absolute',
|
|
471
|
+
right: 0,
|
|
472
|
+
top: 0,
|
|
473
|
+
bottom: 0,
|
|
474
|
+
width: '1px',
|
|
475
|
+
backgroundColor: 'var(--stroke-border-base, #EFF1F5)',
|
|
476
|
+
zIndex: 1,
|
|
477
|
+
},
|
|
478
|
+
'&:has(+ .placeholder)::after': {
|
|
479
|
+
display: 'none',
|
|
480
|
+
},
|
|
481
|
+
'&:last-child::after': {
|
|
482
|
+
display: 'none',
|
|
483
|
+
},
|
|
484
|
+
}}>
|
|
485
|
+
<CurrencyCard
|
|
486
|
+
label={t(`admin.customer.summary.${type}`)}
|
|
487
|
+
data={data?.summary?.[config.key] || emptyObject}
|
|
488
|
+
currency={c}
|
|
489
|
+
type={type as CardType}
|
|
490
|
+
icon={getCardIcon(type as CardType)}
|
|
491
|
+
/>
|
|
492
|
+
</Box>
|
|
493
|
+
);
|
|
494
|
+
})}
|
|
495
|
+
</Stack>
|
|
496
|
+
</Stack>
|
|
497
|
+
);
|
|
498
|
+
})}
|
|
417
499
|
</Stack>
|
|
418
500
|
</>
|
|
419
501
|
)}
|