payment-kit 1.18.11 → 1.18.13

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.
Files changed (37) hide show
  1. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +5 -3
  2. package/api/src/libs/notification/template/subscription-canceled.ts +3 -3
  3. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +4 -3
  4. package/api/src/libs/notification/template/subscription-renew-failed.ts +5 -4
  5. package/api/src/libs/notification/template/subscription-renewed.ts +2 -1
  6. package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +3 -4
  7. package/api/src/libs/notification/template/subscription-succeeded.ts +2 -1
  8. package/api/src/libs/notification/template/subscription-upgraded.ts +6 -4
  9. package/api/src/libs/notification/template/subscription-will-canceled.ts +6 -3
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  11. package/api/src/routes/connect/change-payment.ts +1 -0
  12. package/api/src/routes/connect/change-plan.ts +1 -0
  13. package/api/src/routes/connect/setup.ts +1 -0
  14. package/api/src/routes/connect/shared.ts +39 -33
  15. package/api/src/routes/connect/subscribe.ts +14 -8
  16. package/api/src/routes/customers.ts +79 -5
  17. package/api/src/routes/subscriptions.ts +13 -1
  18. package/api/src/store/models/invoice.ts +4 -2
  19. package/blocklet.yml +3 -3
  20. package/package.json +15 -15
  21. package/src/app.tsx +17 -17
  22. package/src/components/actions.tsx +32 -9
  23. package/src/components/copyable.tsx +2 -2
  24. package/src/components/layout/user.tsx +37 -0
  25. package/src/components/subscription/portal/actions.tsx +26 -5
  26. package/src/components/subscription/portal/list.tsx +24 -6
  27. package/src/components/subscription/status.tsx +2 -2
  28. package/src/libs/util.ts +15 -0
  29. package/src/pages/admin/payments/payouts/detail.tsx +6 -1
  30. package/src/pages/customer/index.tsx +247 -154
  31. package/src/pages/customer/invoice/detail.tsx +1 -1
  32. package/src/pages/customer/payout/detail.tsx +9 -2
  33. package/src/pages/customer/recharge.tsx +6 -2
  34. package/src/pages/customer/subscription/change-payment.tsx +1 -1
  35. package/src/pages/customer/subscription/change-plan.tsx +1 -1
  36. package/src/pages/customer/subscription/detail.tsx +8 -3
  37. package/src/pages/customer/subscription/embed.tsx +142 -84
@@ -14,7 +14,6 @@ import {
14
14
  import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
15
15
  import { ExpandMore } from '@mui/icons-material';
16
16
  import {
17
- Alert,
18
17
  Avatar,
19
18
  Box,
20
19
  Button,
@@ -22,9 +21,11 @@ import {
22
21
  Grid,
23
22
  MenuItem,
24
23
  Select,
24
+ Skeleton,
25
25
  Stack,
26
26
  Tooltip,
27
27
  Typography,
28
+ Alert,
28
29
  } from '@mui/material';
29
30
  import type { SelectChangeEvent } from '@mui/material/Select';
30
31
  import { styled, SxProps } from '@mui/system';
@@ -37,7 +38,7 @@ import { joinURL } from 'ufo';
37
38
 
38
39
  import EditCustomer from '../../components/customer/edit';
39
40
  import InfoRow from '../../components/info-row';
40
- import ProgressBar, { useTransitionContext } from '../../components/progress-bar';
41
+ import { useTransitionContext } from '../../components/progress-bar';
41
42
  import CurrentSubscriptions from '../../components/subscription/portal/list';
42
43
  import { useSessionContext } from '../../contexts/session';
43
44
  import api from '../../libs/api';
@@ -46,7 +47,7 @@ import CustomerRevenueList from '../../components/payouts/portal/list';
46
47
  type Result = TCustomerExpanded & { summary: { [key: string]: GroupedBN }; error?: string };
47
48
 
48
49
  const fetchData = (): Promise<Result> => {
49
- return api.get('/api/customers/me').then((res) => res.data);
50
+ return api.get('/api/customers/me?skipError=true&create=true').then((res) => res.data);
50
51
  };
51
52
 
52
53
  const emptyObject = {};
@@ -68,7 +69,8 @@ const CurrencyCard = memo(
68
69
  };
69
70
  sx: SxProps;
70
71
  }) => {
71
- const value = formatBNStr(data?.[currency?.id], currency.decimal, 6, false);
72
+ const safeData = data || {};
73
+ const value = formatBNStr(safeData[currency?.id], currency?.decimal, 6, false);
72
74
  return (
73
75
  <Box
74
76
  sx={{
@@ -87,11 +89,68 @@ const CurrencyCard = memo(
87
89
  }
88
90
  );
89
91
 
90
- // Remove the assignment of defaultProps
92
+ const CardSkeleton = memo(({ height = 100 }: { height: number }) => {
93
+ return (
94
+ <Box className="base-card section">
95
+ <Box className="section-header" display="flex" justifyContent="space-between" alignItems="center" mb={2}>
96
+ <Skeleton variant="text" width={150} height={32} />
97
+ </Box>
98
+ <Skeleton variant="rectangular" height={height} />
99
+ </Box>
100
+ );
101
+ });
102
+
103
+ function SummaryCardSkeleton() {
104
+ return (
105
+ <Box className="base-card section section-summary">
106
+ <Skeleton variant="text" width={150} height={32} sx={{ mb: 2 }} />
107
+ <Stack gap={1}>
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>
147
+ </Box>
148
+ );
149
+ }
91
150
 
92
151
  export default function CustomerHome() {
93
152
  const { t } = useLocaleContext();
94
- const { events } = useSessionContext();
153
+ const { events, session } = useSessionContext();
95
154
  const { settings } = usePaymentContext();
96
155
  const [currency, setCurrency] = useState(settings?.baseCurrency);
97
156
  const [subscriptionLoading, setSubscriptionLoading] = useState(false);
@@ -113,11 +172,19 @@ export default function CustomerHome() {
113
172
  });
114
173
  const navigate = useNavigate();
115
174
  const { isMobile } = useMobile('lg');
116
- const { isPending, startTransition } = useTransitionContext();
117
- const { data, error, loading, runAsync } = useRequest(fetchData, {
175
+ const [subscriptionStatus, setSubscriptionStatus] = useState(false);
176
+ const { startTransition } = useTransitionContext();
177
+ const {
178
+ data,
179
+ error,
180
+ runAsync,
181
+ loading = true,
182
+ } = useRequest(fetchData, {
118
183
  manual: true,
119
184
  });
120
185
 
186
+ const loadingCard = loading || !data;
187
+
121
188
  const countryDetail = useMemo(() => {
122
189
  const item = defaultCountries.find((v) => v[1] === data?.address?.country);
123
190
  return item ? parseCountry(item) : { name: '' };
@@ -131,39 +198,33 @@ export default function CustomerHome() {
131
198
  }, []);
132
199
 
133
200
  useEffect(() => {
134
- if (data && data.livemode !== livemode) {
201
+ if (data && !data.error && data.livemode !== livemode) {
135
202
  setLivemode(data.livemode);
136
203
  }
137
204
  }, [data]);
138
205
 
139
- if (loading) {
140
- return <ProgressBar pending />;
141
- }
142
-
143
206
  if (error) {
144
207
  return (
145
- <Alert sx={{ mt: 3 }} severity="error">
146
- {formatError(error)}
147
- </Alert>
148
- );
149
- }
150
-
151
- if (!data) {
152
- return null;
153
- }
154
-
155
- if (data?.error) {
156
- return (
157
- <Alert sx={{ mt: 3 }} severity="info">
158
- {t('payment.customer.empty')}
159
- </Alert>
208
+ <Box
209
+ sx={{
210
+ backgroundColor: 'error.light',
211
+ color: 'error.dark',
212
+ p: 1,
213
+ mb: 2,
214
+ borderRadius: 1,
215
+ display: 'flex',
216
+ alignItems: 'center',
217
+ justifyContent: 'center',
218
+ }}>
219
+ <Typography variant="body2">{formatError(error)}</Typography>
220
+ </Box>
160
221
  );
161
222
  }
162
223
 
163
224
  const onUpdateInfo = async (updates: TCustomerExpanded) => {
164
225
  try {
165
226
  setState({ loading: true });
166
- await api.put(`/api/customers/${data.id}`, updates).then((res) => res.data);
227
+ await api.put(`/api/customers/${data?.id}`, updates).then((res) => res.data);
167
228
  Toast.success(t('common.saved'));
168
229
  runAsync();
169
230
  } catch (err) {
@@ -181,43 +242,46 @@ export default function CustomerHome() {
181
242
  setSubscriptionLoading(false);
182
243
  }, 300);
183
244
  };
184
-
185
245
  const handleCurrencyChange = (e: SelectChangeEvent) => {
186
246
  const newCurrency = currencies.find((c) => c.id === e.target.value) || settings?.baseCurrency;
187
247
  setCurrency(newCurrency);
188
248
  };
189
249
 
190
- const SubscriptionCard = (
250
+ const SubscriptionCard = loadingCard ? (
251
+ <CardSkeleton height={200} />
252
+ ) : (
191
253
  <Box className="base-card section section-subscription">
192
254
  <Box className="section-header">
193
255
  <Typography variant="h3">{t('admin.subscription.name')}</Typography>
194
- <FormControl
195
- sx={{
196
- '.MuiInputBase-root': {
197
- background: 'none',
198
- border: 'none',
199
- },
200
- '.MuiOutlinedInput-notchedOutline': {
201
- border: 'none',
202
- },
203
- }}>
204
- <Select
205
- value={state.onlyActive ? 'active' : ''}
206
- onChange={onToggleActive}
207
- displayEmpty
208
- IconComponent={ExpandMore}
209
- inputProps={{ 'aria-label': 'Without label' }}>
210
- <MenuItem value="">All</MenuItem>
211
- <MenuItem value="active">Active</MenuItem>
212
- </Select>
213
- </FormControl>
256
+ {subscriptionStatus && (
257
+ <FormControl
258
+ sx={{
259
+ '.MuiInputBase-root': {
260
+ background: 'none',
261
+ border: 'none',
262
+ },
263
+ '.MuiOutlinedInput-notchedOutline': {
264
+ border: 'none',
265
+ },
266
+ }}>
267
+ <Select
268
+ value={state.onlyActive ? 'active' : ''}
269
+ onChange={onToggleActive}
270
+ displayEmpty
271
+ IconComponent={ExpandMore}
272
+ inputProps={{ 'aria-label': 'Without label' }}>
273
+ <MenuItem value="">All</MenuItem>
274
+ <MenuItem value="active">Active</MenuItem>
275
+ </Select>
276
+ </FormControl>
277
+ )}
214
278
  </Box>
215
279
  <Box className="section-body">
216
280
  {subscriptionLoading ? (
217
281
  <Box>{t('common.loading')}</Box>
218
282
  ) : (
219
283
  <CurrentSubscriptions
220
- id={data.id}
284
+ id={data?.id}
221
285
  onlyActive={state.onlyActive}
222
286
  changeActive={(v) => setState({ onlyActive: v })}
223
287
  status={state.onlyActive ? 'active,trialing,past_due' : 'active,trialing,paused,past_due,canceled'}
@@ -229,99 +293,110 @@ export default function CustomerHome() {
229
293
  navigate(`/customer/subscription/${subscription.id}`);
230
294
  });
231
295
  }}
296
+ setStatusState={setSubscriptionStatus}
232
297
  />
233
298
  )}
234
299
  </Box>
235
300
  </Box>
236
301
  );
237
- const SummaryCard = (
302
+
303
+ const SummaryCard = loadingCard ? (
304
+ <SummaryCardSkeleton />
305
+ ) : (
238
306
  <Box className="base-card section section-summary">
239
- <Box className="section-header">
240
- <Typography variant="h3">{t('admin.customer.summary.stats')}</Typography>
241
- <FormControl
242
- sx={{
243
- '.MuiInputBase-root': {
244
- background: 'none',
245
- border: 'none',
246
- },
247
- '.MuiOutlinedInput-notchedOutline': {
248
- border: 'none',
249
- },
250
- }}>
251
- <Select
252
- value={currency?.id}
253
- onChange={handleCurrencyChange}
254
- displayEmpty
255
- IconComponent={ExpandMore}
256
- inputProps={{ 'aria-label': 'Without label' }}>
257
- {currencies.map((c) => (
258
- <MenuItem key={c.id} value={c.id}>
259
- <Box alignItems="center" display="flex" gap={1}>
260
- <Avatar src={c?.logo} alt={c?.symbol} sx={{ width: 18, height: 18 }} />
261
- <Typography
262
- variant="h5"
263
- component="div"
264
- sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
265
- {c?.symbol}
266
- </Typography>
267
- <Typography sx={{ fontSize: 12 }} color="text.lighter">
268
- {c?.methodName}
269
- </Typography>
270
- </Box>
271
- </MenuItem>
272
- ))}
273
- </Select>
274
- </FormControl>
275
- </Box>
276
- <Stack
277
- className="section-body"
278
- sx={{
279
- display: 'grid',
280
- gridTemplateColumns: '1fr 1fr',
281
- gap: 2,
282
- mt: 1.5,
283
- }}>
284
- <CurrencyCard
285
- label={t('admin.customer.summary.balance')}
286
- data={data.summary.token || {}}
287
- currency={currency}
288
- sx={{
289
- background: 'var(--tags-tag-orange-bg, #B7FEE3)',
290
- color: 'var(--tags-tag-orange-text, #007C52)',
291
- }}
292
- />
293
- <CurrencyCard
294
- label={t('admin.customer.summary.spent')}
295
- data={data.summary.paid || {}}
296
- currency={currency}
297
- sx={{ background: 'var(--tags-tag-green-bg, #B7FEE3)', color: 'var(--tags-tag-green-text, #007C52)' }}
298
- />
299
- <CurrencyCard
300
- label={t('admin.customer.summary.stake')}
301
- data={data.summary.stake || {}}
302
- currency={currency}
303
- sx={{ background: 'var(--tags-tag-blue-bg, #D2ECFF)', color: 'var(--tags-tag-blue-text, #0051E9)' }}
304
- />
305
- <CurrencyCard
306
- label={t('admin.customer.summary.refund')}
307
- data={data.summary.refund || emptyObject}
308
- currency={currency}
309
- sx={{ background: 'var(--tags-tag-purple-bg, #EFE9FF)', color: 'var(--tags-tag-purple-text, #007C52)' }}
310
- />
311
- <CurrencyCard
312
- label={t('admin.customer.summary.due')}
313
- data={data.summary.due || emptyObject}
314
- currency={currency}
315
- sx={{ background: 'var(--tags-tag-red-bg, #FFE2E6)', color: 'var(--tags-tag-red-text, #E40031)' }}
316
- />
317
- </Stack>
307
+ {data?.summary && (
308
+ <>
309
+ <Box className="section-header">
310
+ <Typography variant="h3">{t('admin.customer.summary.stats')}</Typography>
311
+ <FormControl
312
+ sx={{
313
+ '.MuiInputBase-root': {
314
+ background: 'none',
315
+ border: 'none',
316
+ },
317
+ '.MuiOutlinedInput-notchedOutline': {
318
+ border: 'none',
319
+ },
320
+ }}>
321
+ <Select
322
+ value={currency?.id}
323
+ onChange={handleCurrencyChange}
324
+ displayEmpty
325
+ IconComponent={ExpandMore}
326
+ inputProps={{ 'aria-label': 'Without label' }}>
327
+ {currencies.map((c) => (
328
+ <MenuItem key={c.id} value={c.id}>
329
+ <Box alignItems="center" display="flex" gap={1}>
330
+ <Avatar src={c?.logo} alt={c?.symbol} sx={{ width: 18, height: 18 }} />
331
+ <Typography
332
+ variant="h5"
333
+ component="div"
334
+ sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
335
+ {c?.symbol}
336
+ </Typography>
337
+ <Typography sx={{ fontSize: 12 }} color="text.lighter">
338
+ {c?.methodName}
339
+ </Typography>
340
+ </Box>
341
+ </MenuItem>
342
+ ))}
343
+ </Select>
344
+ </FormControl>
345
+ </Box>
346
+ <Stack
347
+ className="section-body"
348
+ sx={{
349
+ display: 'grid',
350
+ gridTemplateColumns: '1fr 1fr',
351
+ gap: 2,
352
+ mt: 1.5,
353
+ }}>
354
+ <CurrencyCard
355
+ label={t('admin.customer.summary.balance')}
356
+ data={data?.summary?.token || emptyObject}
357
+ currency={currency}
358
+ sx={{
359
+ background: 'var(--tags-tag-orange-bg, #B7FEE3)',
360
+ color: 'var(--tags-tag-orange-text, #007C52)',
361
+ }}
362
+ />
363
+ <CurrencyCard
364
+ label={t('admin.customer.summary.spent')}
365
+ data={data?.summary?.paid || emptyObject}
366
+ currency={currency}
367
+ sx={{ background: 'var(--tags-tag-green-bg, #B7FEE3)', color: 'var(--tags-tag-green-text, #007C52)' }}
368
+ />
369
+ <CurrencyCard
370
+ label={t('admin.customer.summary.stake')}
371
+ data={data?.summary?.stake || emptyObject}
372
+ currency={currency}
373
+ sx={{ background: 'var(--tags-tag-blue-bg, #D2ECFF)', color: 'var(--tags-tag-blue-text, #0051E9)' }}
374
+ />
375
+ <CurrencyCard
376
+ label={t('admin.customer.summary.refund')}
377
+ data={data?.summary?.refund || emptyObject}
378
+ currency={currency}
379
+ sx={{ background: 'var(--tags-tag-purple-bg, #EFE9FF)', color: 'var(--tags-tag-purple-text, #007C52)' }}
380
+ />
381
+ <CurrencyCard
382
+ label={t('admin.customer.summary.due')}
383
+ data={data?.summary?.due || emptyObject}
384
+ currency={currency}
385
+ sx={{ background: 'var(--tags-tag-red-bg, #FFE2E6)', color: 'var(--tags-tag-red-text, #E40031)' }}
386
+ />
387
+ </Stack>
388
+ </>
389
+ )}
318
390
  </Box>
319
391
  );
320
- const InvoiceCard = (
321
- <Box className="base-card section section-invoice">
392
+
393
+ const InvoiceCard = loadingCard ? (
394
+ <CardSkeleton height={300} />
395
+ ) : (
396
+ <Box className="base-card section section-invoices">
322
397
  <Box className="section-header">
323
398
  <Typography variant="h3">{t('customer.invoiceHistory')}</Typography>
324
- {isEmpty(data.summary.due) === false && (
399
+ {isEmpty(data?.summary?.due) === false && (
325
400
  <Tooltip title={t('payment.customer.pastDue.warning')}>
326
401
  <Button
327
402
  variant="text"
@@ -338,12 +413,14 @@ export default function CustomerHome() {
338
413
  )}
339
414
  </Box>
340
415
  <Box className="section-body">
341
- <CustomerInvoiceList customer_id={data.id} type="table" include_staking />
416
+ <CustomerInvoiceList customer_id={data?.id} type="table" include_staking />
342
417
  </Box>
343
418
  </Box>
344
419
  );
345
420
 
346
- const DetailCard = (
421
+ const DetailCard = loadingCard ? (
422
+ <DetailCardSkeleton />
423
+ ) : (
347
424
  <Box className="base-card section section-detail">
348
425
  <Box className="section-header" sx={{ mb: 2 }}>
349
426
  <Typography variant="h3">{t('payment.customer.details')}</Typography>
@@ -355,33 +432,40 @@ export default function CustomerHome() {
355
432
  {t('common.edit')}
356
433
  </Button>
357
434
  </Box>
358
- <Box display="flex" alignItems="center" gap={1} flexWrap="wrap" sx={{ mb: 3 }}>
435
+ <Box display="flex" alignItems="center" gap={1} sx={{ mb: 3 }}>
359
436
  <Avatar
360
437
  title={data?.name}
361
438
  src={getCustomerAvatar(data?.did, data?.updated_at ? new Date(data.updated_at).toISOString() : '', 48)}
362
439
  variant="circular"
363
440
  sx={{ width: 48, height: 48 }}
364
441
  />
365
- <Box>
442
+ <Box sx={{ minWidth: 0, flexGrow: 1 }}>
366
443
  <Typography variant="h4" gutterBottom>
367
- {data.name}
444
+ {data?.name || t('common.none')}
368
445
  </Typography>
369
446
  <Typography variant="body2" color="text.secondary">
370
- <DID did={data.did} copyable showQrcode chainId={livemode ? 'main' : 'beta'} />
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
+ )}
371
455
  </Typography>
372
456
  </Box>
373
457
  </Box>
374
458
  <Stack>
375
459
  <InfoRow
376
460
  label={t('admin.customer.phone')}
377
- value={data.phone}
461
+ value={data?.phone}
378
462
  sizes={[1, 1]}
379
463
  alignItems="normal"
380
464
  direction="column"
381
465
  />
382
466
  <InfoRow
383
467
  label={t('admin.customer.email')}
384
- value={data.email}
468
+ value={data?.email}
385
469
  sizes={[1, 1]}
386
470
  alignItems="normal"
387
471
  direction="column"
@@ -389,13 +473,13 @@ export default function CustomerHome() {
389
473
  <InfoRow
390
474
  label={t('admin.customer.address.country')}
391
475
  value={
392
- data.address?.country ? (
476
+ data?.address?.country ? (
393
477
  <Box display="flex" alignItems="center" flexWrap="nowrap" gap={0.5} sx={{ cursor: 'pointer' }}>
394
478
  <FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
395
479
  <Typography>{countryDetail?.name}</Typography>
396
480
  </Box>
397
481
  ) : (
398
- ''
482
+ t('common.none')
399
483
  )
400
484
  }
401
485
  sizes={[1, 1]}
@@ -404,36 +488,35 @@ export default function CustomerHome() {
404
488
  />
405
489
  <InfoRow
406
490
  label={t('admin.customer.address.state')}
407
- value={data.address?.state}
491
+ value={data?.address?.state}
408
492
  sizes={[1, 1]}
409
493
  alignItems="normal"
410
494
  direction="column"
411
495
  />
412
496
  <InfoRow
413
497
  label={t('admin.customer.address.city')}
414
- value={data.address?.city && <TruncatedText text={data.address?.city} maxLength={280} useWidth />}
415
- // value={data.address?.city}
498
+ value={data?.address?.city && <TruncatedText text={data.address?.city} maxLength={280} useWidth />}
416
499
  sizes={[1, 1]}
417
500
  alignItems="normal"
418
501
  direction="column"
419
502
  />
420
503
  <InfoRow
421
504
  label={t('admin.customer.address.line1')}
422
- value={data.address?.line1 && <TruncatedText text={data.address?.line1} maxLength={280} useWidth />}
505
+ value={data?.address?.line1 && <TruncatedText text={data.address?.line1} maxLength={280} useWidth />}
423
506
  sizes={[1, 1]}
424
507
  alignItems="normal"
425
508
  direction="column"
426
509
  />
427
510
  <InfoRow
428
511
  label={t('admin.customer.address.line2')}
429
- value={data.address?.line2 && <TruncatedText text={data.address?.line2} maxLength={280} useWidth />}
512
+ value={data?.address?.line2 && <TruncatedText text={data.address?.line2} maxLength={280} useWidth />}
430
513
  sizes={[1, 1]}
431
514
  alignItems="normal"
432
515
  direction="column"
433
516
  />
434
517
  <InfoRow
435
518
  label={t('admin.customer.address.postal_code')}
436
- value={data.address?.postal_code}
519
+ value={data?.address?.postal_code}
437
520
  sizes={[1, 1]}
438
521
  alignItems="normal"
439
522
  direction="column"
@@ -450,7 +533,9 @@ export default function CustomerHome() {
450
533
  </Box>
451
534
  );
452
535
 
453
- const RevenueCard = (
536
+ const RevenueCard = loadingCard ? (
537
+ <CardSkeleton height={200} />
538
+ ) : (
454
539
  <Box className="base-card section section-revenue">
455
540
  <Box className="section-header">
456
541
  <Typography variant="h3">{t('customer.payout.title')}</Typography>
@@ -461,7 +546,11 @@ export default function CustomerHome() {
461
546
 
462
547
  return (
463
548
  <Content>
464
- <ProgressBar pending={isPending} />
549
+ {data?.error && (
550
+ <Alert severity="error" sx={{ mb: 2 }}>
551
+ {data?.error}
552
+ </Alert>
553
+ )}
465
554
  {isMobile ? (
466
555
  <Root>
467
556
  {SummaryCard}
@@ -492,7 +581,6 @@ export default function CustomerHome() {
492
581
  }
493
582
 
494
583
  const Content = styled(Stack)`
495
- padding: 20px;
496
584
  height: 100%;
497
585
  .section-header {
498
586
  display: flex;
@@ -521,9 +609,14 @@ const Root = styled(Stack)`
521
609
  .base-card {
522
610
  border: none;
523
611
  box-shadow: none;
612
+ padding-left: 0;
613
+ padding-right: 0;
524
614
  }
525
615
  .section-header h3 {
526
616
  font-size: 18px;
527
617
  }
618
+ .section-summary {
619
+ padding-top: 0;
620
+ }
528
621
  }
529
622
  `;
@@ -125,7 +125,7 @@ export default function CustomerInvoiceDetail() {
125
125
  const hidePayButton = data.billing_reason === 'overdraft_protection';
126
126
  const paymentDetails = data.paymentIntent?.payment_details || data.metadata?.payment_details;
127
127
  return (
128
- <InvoiceDetailRoot direction="column" spacing={3} sx={{ my: 2 }}>
128
+ <InvoiceDetailRoot direction="column" spacing={3}>
129
129
  <Stack direction="row" justifyContent="space-between">
130
130
  <Stack
131
131
  direction="row"