payment-kit 1.13.15

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 (222) hide show
  1. package/.eslintrc.js +15 -0
  2. package/README.md +3 -0
  3. package/api/dev.ts +6 -0
  4. package/api/hooks/pre-start.js +12 -0
  5. package/api/src/hooks/pre-start.ts +21 -0
  6. package/api/src/index.ts +92 -0
  7. package/api/src/jobs/event.ts +72 -0
  8. package/api/src/jobs/invoice.ts +148 -0
  9. package/api/src/jobs/payment.ts +208 -0
  10. package/api/src/jobs/subscription.ts +301 -0
  11. package/api/src/jobs/webhook.ts +113 -0
  12. package/api/src/libs/audit.ts +73 -0
  13. package/api/src/libs/auth.ts +40 -0
  14. package/api/src/libs/chain/arcblock.ts +13 -0
  15. package/api/src/libs/dayjs.ts +17 -0
  16. package/api/src/libs/env.ts +5 -0
  17. package/api/src/libs/hooks.ts +42 -0
  18. package/api/src/libs/logger.ts +27 -0
  19. package/api/src/libs/middleware.ts +12 -0
  20. package/api/src/libs/payment.ts +53 -0
  21. package/api/src/libs/queue/index.ts +263 -0
  22. package/api/src/libs/queue/store.ts +47 -0
  23. package/api/src/libs/security.ts +95 -0
  24. package/api/src/libs/session.ts +164 -0
  25. package/api/src/libs/util.ts +93 -0
  26. package/api/src/locales/en.ts +3 -0
  27. package/api/src/locales/index.ts +37 -0
  28. package/api/src/locales/zh.ts +3 -0
  29. package/api/src/routes/checkout-sessions.ts +536 -0
  30. package/api/src/routes/connect/collect.ts +109 -0
  31. package/api/src/routes/connect/pay.ts +116 -0
  32. package/api/src/routes/connect/setup.ts +121 -0
  33. package/api/src/routes/connect/shared.ts +410 -0
  34. package/api/src/routes/connect/subscribe.ts +128 -0
  35. package/api/src/routes/customers.ts +70 -0
  36. package/api/src/routes/events.ts +76 -0
  37. package/api/src/routes/index.ts +59 -0
  38. package/api/src/routes/invoices.ts +126 -0
  39. package/api/src/routes/payment-currencies.ts +38 -0
  40. package/api/src/routes/payment-intents.ts +122 -0
  41. package/api/src/routes/payment-links.ts +221 -0
  42. package/api/src/routes/payment-methods.ts +39 -0
  43. package/api/src/routes/prices.ts +134 -0
  44. package/api/src/routes/products.ts +191 -0
  45. package/api/src/routes/settings.ts +33 -0
  46. package/api/src/routes/subscription-items.ts +148 -0
  47. package/api/src/routes/subscriptions.ts +254 -0
  48. package/api/src/routes/usage-records.ts +120 -0
  49. package/api/src/routes/webhook-attempts.ts +57 -0
  50. package/api/src/routes/webhook-endpoints.ts +105 -0
  51. package/api/src/store/migrate.ts +16 -0
  52. package/api/src/store/migrations/20230905-genesis.ts +52 -0
  53. package/api/src/store/migrations/20230911-seeding.ts +145 -0
  54. package/api/src/store/models/checkout-session.ts +395 -0
  55. package/api/src/store/models/coupon.ts +137 -0
  56. package/api/src/store/models/customer.ts +199 -0
  57. package/api/src/store/models/discount.ts +116 -0
  58. package/api/src/store/models/event.ts +111 -0
  59. package/api/src/store/models/index.ts +165 -0
  60. package/api/src/store/models/invoice-item.ts +185 -0
  61. package/api/src/store/models/invoice.ts +492 -0
  62. package/api/src/store/models/job.ts +75 -0
  63. package/api/src/store/models/payment-currency.ts +139 -0
  64. package/api/src/store/models/payment-intent.ts +282 -0
  65. package/api/src/store/models/payment-link.ts +219 -0
  66. package/api/src/store/models/payment-method.ts +169 -0
  67. package/api/src/store/models/price.ts +266 -0
  68. package/api/src/store/models/product.ts +162 -0
  69. package/api/src/store/models/promotion-code.ts +112 -0
  70. package/api/src/store/models/setup-intent.ts +206 -0
  71. package/api/src/store/models/subscription-item.ts +103 -0
  72. package/api/src/store/models/subscription-schedule.ts +157 -0
  73. package/api/src/store/models/subscription.ts +307 -0
  74. package/api/src/store/models/types.ts +406 -0
  75. package/api/src/store/models/usage-record.ts +132 -0
  76. package/api/src/store/models/webhook-attempt.ts +96 -0
  77. package/api/src/store/models/webhook-endpoint.ts +96 -0
  78. package/api/src/store/sequelize.ts +15 -0
  79. package/api/third.d.ts +28 -0
  80. package/blocklet.md +3 -0
  81. package/blocklet.yml +89 -0
  82. package/index.html +14 -0
  83. package/logo.png +0 -0
  84. package/package.json +133 -0
  85. package/public/.gitkeep +0 -0
  86. package/screenshots/.gitkeep +0 -0
  87. package/screenshots/1-subscription.png +0 -0
  88. package/screenshots/2-customer-1.png +0 -0
  89. package/screenshots/3-customer-2.png +0 -0
  90. package/screenshots/4-admin-3.png +0 -0
  91. package/screenshots/5-admin-4.png +0 -0
  92. package/scripts/build-clean.js +6 -0
  93. package/scripts/bump-version.mjs +35 -0
  94. package/src/app.tsx +68 -0
  95. package/src/components/actions.tsx +85 -0
  96. package/src/components/blockchain/tx.tsx +29 -0
  97. package/src/components/checkout/amount.tsx +24 -0
  98. package/src/components/checkout/error.tsx +30 -0
  99. package/src/components/checkout/footer.tsx +12 -0
  100. package/src/components/checkout/form/address.tsx +38 -0
  101. package/src/components/checkout/form/index.tsx +295 -0
  102. package/src/components/checkout/header.tsx +23 -0
  103. package/src/components/checkout/pay.tsx +222 -0
  104. package/src/components/checkout/product-card.tsx +56 -0
  105. package/src/components/checkout/product-item.tsx +37 -0
  106. package/src/components/checkout/skeleton/overview.tsx +21 -0
  107. package/src/components/checkout/skeleton/payment.tsx +35 -0
  108. package/src/components/checkout/success.tsx +183 -0
  109. package/src/components/checkout/summary.tsx +34 -0
  110. package/src/components/collapse.tsx +50 -0
  111. package/src/components/confirm.tsx +55 -0
  112. package/src/components/copyable.tsx +38 -0
  113. package/src/components/currency.tsx +15 -0
  114. package/src/components/customer/actions.tsx +73 -0
  115. package/src/components/data.tsx +20 -0
  116. package/src/components/drawer-form.tsx +77 -0
  117. package/src/components/error-fallback.tsx +7 -0
  118. package/src/components/error.tsx +39 -0
  119. package/src/components/event/list.tsx +217 -0
  120. package/src/components/info-card.tsx +40 -0
  121. package/src/components/info-metric.tsx +35 -0
  122. package/src/components/info-row.tsx +28 -0
  123. package/src/components/input.tsx +40 -0
  124. package/src/components/invoice/action.tsx +94 -0
  125. package/src/components/invoice/list.tsx +225 -0
  126. package/src/components/invoice/table.tsx +110 -0
  127. package/src/components/layout.tsx +70 -0
  128. package/src/components/livemode.tsx +23 -0
  129. package/src/components/metadata/editor.tsx +57 -0
  130. package/src/components/metadata/form.tsx +45 -0
  131. package/src/components/payment-intent/actions.tsx +81 -0
  132. package/src/components/payment-intent/list.tsx +204 -0
  133. package/src/components/payment-link/actions.tsx +114 -0
  134. package/src/components/payment-link/after-pay.tsx +87 -0
  135. package/src/components/payment-link/before-pay.tsx +175 -0
  136. package/src/components/payment-link/item.tsx +135 -0
  137. package/src/components/payment-link/product-select.tsx +66 -0
  138. package/src/components/payment-link/rename.tsx +64 -0
  139. package/src/components/portal/invoice/list.tsx +110 -0
  140. package/src/components/portal/subscription/cancel.tsx +83 -0
  141. package/src/components/portal/subscription/list.tsx +232 -0
  142. package/src/components/price/actions.tsx +21 -0
  143. package/src/components/price/form.tsx +292 -0
  144. package/src/components/product/actions.tsx +125 -0
  145. package/src/components/product/add-price.tsx +59 -0
  146. package/src/components/product/create.tsx +97 -0
  147. package/src/components/product/edit-price.tsx +75 -0
  148. package/src/components/product/edit.tsx +67 -0
  149. package/src/components/product/features.tsx +32 -0
  150. package/src/components/product/form.tsx +76 -0
  151. package/src/components/relative-time.tsx +41 -0
  152. package/src/components/section/header.tsx +29 -0
  153. package/src/components/status.tsx +12 -0
  154. package/src/components/subscription/actions/cancel.tsx +66 -0
  155. package/src/components/subscription/actions/index.tsx +172 -0
  156. package/src/components/subscription/actions/pause.tsx +83 -0
  157. package/src/components/subscription/items/actions.tsx +31 -0
  158. package/src/components/subscription/items/index.tsx +107 -0
  159. package/src/components/subscription/list.tsx +200 -0
  160. package/src/components/switch.tsx +48 -0
  161. package/src/components/table.tsx +66 -0
  162. package/src/components/uploader.tsx +81 -0
  163. package/src/components/webhook/attempts.tsx +149 -0
  164. package/src/contexts/products.tsx +42 -0
  165. package/src/contexts/session.ts +10 -0
  166. package/src/contexts/settings.tsx +54 -0
  167. package/src/env.d.ts +17 -0
  168. package/src/global.css +97 -0
  169. package/src/hooks/mobile.ts +15 -0
  170. package/src/index.tsx +6 -0
  171. package/src/libs/api.ts +19 -0
  172. package/src/libs/dayjs.ts +17 -0
  173. package/src/libs/util.ts +474 -0
  174. package/src/locales/en.tsx +395 -0
  175. package/src/locales/index.tsx +8 -0
  176. package/src/locales/zh.tsx +389 -0
  177. package/src/pages/admin/billing/index.tsx +56 -0
  178. package/src/pages/admin/billing/invoices/detail.tsx +215 -0
  179. package/src/pages/admin/billing/invoices/index.tsx +5 -0
  180. package/src/pages/admin/billing/subscriptions/detail.tsx +237 -0
  181. package/src/pages/admin/billing/subscriptions/index.tsx +5 -0
  182. package/src/pages/admin/customers/customers/detail.tsx +209 -0
  183. package/src/pages/admin/customers/customers/index.tsx +109 -0
  184. package/src/pages/admin/customers/index.tsx +47 -0
  185. package/src/pages/admin/developers/events/detail.tsx +77 -0
  186. package/src/pages/admin/developers/events/index.tsx +5 -0
  187. package/src/pages/admin/developers/index.tsx +60 -0
  188. package/src/pages/admin/developers/logs.tsx +3 -0
  189. package/src/pages/admin/developers/overview.tsx +3 -0
  190. package/src/pages/admin/developers/webhooks/detail.tsx +109 -0
  191. package/src/pages/admin/developers/webhooks/index.tsx +102 -0
  192. package/src/pages/admin/index.tsx +120 -0
  193. package/src/pages/admin/overview.tsx +3 -0
  194. package/src/pages/admin/payments/index.tsx +65 -0
  195. package/src/pages/admin/payments/intents/detail.tsx +205 -0
  196. package/src/pages/admin/payments/intents/index.tsx +5 -0
  197. package/src/pages/admin/payments/links/create.tsx +141 -0
  198. package/src/pages/admin/payments/links/detail.tsx +318 -0
  199. package/src/pages/admin/payments/links/index.tsx +167 -0
  200. package/src/pages/admin/products/coupons/index.tsx +3 -0
  201. package/src/pages/admin/products/index.tsx +81 -0
  202. package/src/pages/admin/products/prices/actions.tsx +151 -0
  203. package/src/pages/admin/products/prices/detail.tsx +203 -0
  204. package/src/pages/admin/products/prices/list.tsx +95 -0
  205. package/src/pages/admin/products/pricing-tables.tsx +3 -0
  206. package/src/pages/admin/products/products/create.tsx +105 -0
  207. package/src/pages/admin/products/products/detail.tsx +246 -0
  208. package/src/pages/admin/products/products/index.tsx +154 -0
  209. package/src/pages/admin/settings/branding.tsx +3 -0
  210. package/src/pages/admin/settings/business.tsx +3 -0
  211. package/src/pages/admin/settings/index.tsx +47 -0
  212. package/src/pages/admin/settings/payment-methods.tsx +80 -0
  213. package/src/pages/checkout/index.tsx +38 -0
  214. package/src/pages/checkout/pay.tsx +89 -0
  215. package/src/pages/customer/index.tsx +93 -0
  216. package/src/pages/customer/invoice.tsx +147 -0
  217. package/src/pages/home.tsx +9 -0
  218. package/tsconfig.api.json +9 -0
  219. package/tsconfig.eslint.json +7 -0
  220. package/tsconfig.json +99 -0
  221. package/tsconfig.types.json +11 -0
  222. package/vite.config.ts +19 -0
@@ -0,0 +1,38 @@
1
+ import { CopyButton } from '@arcblock/ux/lib/ClickToCopy';
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { Stack, Typography } from '@mui/material';
4
+
5
+ export default function Copyable({ text, children, style }: { text: string; children?: React.ReactNode; style?: any }) {
6
+ const { locale } = useLocaleContext();
7
+ return (
8
+ <CopyButton
9
+ content={text}
10
+ locale={locale}
11
+ style={style}
12
+ render={({ copyButton, containerRef }: any) => (
13
+ <Stack ref={containerRef} direction="row" alignItems="center" color="text.secondary">
14
+ {children || (
15
+ <Typography
16
+ component="span"
17
+ sx={{
18
+ mr: 0.5,
19
+ maxWidth: 480,
20
+ overflow: 'hidden',
21
+ fontSize: 'inherit',
22
+ textOverflow: 'ellipsis',
23
+ whiteSpace: 'nowrap',
24
+ }}>
25
+ {text}
26
+ </Typography>
27
+ )}
28
+ {copyButton}
29
+ </Stack>
30
+ )}
31
+ />
32
+ );
33
+ }
34
+
35
+ Copyable.defaultProps = {
36
+ style: {},
37
+ children: null,
38
+ };
@@ -0,0 +1,15 @@
1
+ import { Avatar, Stack, Typography } from '@mui/material';
2
+
3
+ type Props = {
4
+ logo: string;
5
+ name: string;
6
+ };
7
+
8
+ export default function Currency({ logo, name }: Props) {
9
+ return (
10
+ <Stack direction="row" alignItems="center" spacing={0.5}>
11
+ <Avatar src={logo} alt={name} sx={{ width: 20, height: 20 }} />
12
+ <Typography color="text.primary">{name}</Typography>
13
+ </Stack>
14
+ );
15
+ }
@@ -0,0 +1,73 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Toast from '@arcblock/ux/lib/Toast';
3
+ import type { TCustomerExpanded } from '@did-pay/types';
4
+ import { useSetState } from 'ahooks';
5
+ import type { LiteralUnion } from 'type-fest';
6
+
7
+ import api from '../../libs/api';
8
+ import { formatError } from '../../libs/util';
9
+ import Actions from '../actions';
10
+ import ConfirmDialog from '../confirm';
11
+
12
+ type Props = {
13
+ data: TCustomerExpanded;
14
+ onChange: (action: string) => void;
15
+ variant?: LiteralUnion<'compact' | 'normal', string>;
16
+ };
17
+
18
+ CustomerActions.defaultProps = {
19
+ variant: 'compact',
20
+ };
21
+
22
+ export default function CustomerActions({ data, onChange, variant }: Props) {
23
+ const { t } = useLocaleContext();
24
+ const [state, setState] = useSetState({
25
+ action: '',
26
+ loading: false,
27
+ });
28
+
29
+ const onRefund = async () => {
30
+ try {
31
+ setState({ loading: true });
32
+ await api.put(`/api/customers/${data.id}/archive`).then((res) => res.data);
33
+ Toast.success(t('common.saved'));
34
+ onChange(state.action);
35
+ } catch (err) {
36
+ console.error(err);
37
+ Toast.error(formatError(err));
38
+ } finally {
39
+ setState({ loading: false, action: '' });
40
+ }
41
+ };
42
+
43
+ return (
44
+ <>
45
+ <Actions
46
+ variant={variant}
47
+ actions={[
48
+ {
49
+ label: t('admin.payments'),
50
+ handler: () => setState({ action: 'refund' }),
51
+ color: 'primary',
52
+ disabled: true,
53
+ },
54
+ {
55
+ label: t('admin.customer.edit'),
56
+ handler: () => setState({ action: 'edit' }),
57
+ color: 'primary',
58
+ disabled: true,
59
+ },
60
+ ]}
61
+ />
62
+ {state.action === 'refund' && (
63
+ <ConfirmDialog
64
+ onConfirm={onRefund}
65
+ onCancel={() => setState({ action: '' })}
66
+ title={t('admin.customer.refund')}
67
+ message={t('admin.customer.refundTip')}
68
+ loading={state.loading}
69
+ />
70
+ )}
71
+ </>
72
+ );
73
+ }
@@ -0,0 +1,20 @@
1
+ import { Alert, CircularProgress } from '@mui/material';
2
+ import { useRequest } from 'ahooks';
3
+
4
+ type Props = {
5
+ dataFn: () => Promise<any>;
6
+ children: (data: any) => React.ReactNode;
7
+ };
8
+
9
+ export default function DataDrivenPage(props: Props) {
10
+ const { loading, error, data } = useRequest(props.dataFn);
11
+
12
+ if (loading) {
13
+ return <CircularProgress />;
14
+ }
15
+ if (error) {
16
+ return <Alert severity="error">{error.message}</Alert>;
17
+ }
18
+
19
+ return props.children(data);
20
+ }
@@ -0,0 +1,77 @@
1
+ import { Close } from '@mui/icons-material';
2
+ import { Box, Button, Divider, Drawer, Stack, Typography } from '@mui/material';
3
+ import { styled } from '@mui/system';
4
+ import React, { useState } from 'react';
5
+ import useBus from 'use-bus';
6
+
7
+ import { useSettingsContext } from '../contexts/settings';
8
+ import Livemode from './livemode';
9
+
10
+ type Props = {
11
+ icon: React.ReactNode;
12
+ text: string;
13
+ addons: React.ReactNode;
14
+ children: React.ReactNode;
15
+ maxWidth?: number;
16
+ style?: Record<string, any>;
17
+ };
18
+
19
+ DrawerForm.defaultProps = {
20
+ style: {},
21
+ maxWidth: 960,
22
+ };
23
+
24
+ export default function DrawerForm(props: Props) {
25
+ const [open, setOpen] = useState(false);
26
+ const settings = useSettingsContext();
27
+
28
+ useBus('drawer.submitted', () => setOpen(false), []);
29
+
30
+ return (
31
+ <>
32
+ <Button variant="contained" size="small" color="primary" onClick={() => setOpen(true)}>
33
+ {props.icon}
34
+ {props.text}
35
+ </Button>
36
+ <Container
37
+ anchor="right"
38
+ open={open}
39
+ onClose={() => setOpen(false)}
40
+ sx={props.style || {}}
41
+ maxWidth={props.maxWidth}
42
+ disableEscapeKeyDown>
43
+ <Stack
44
+ direction="row"
45
+ alignItems="center"
46
+ justifyContent="space-between"
47
+ sx={{ pl: 3, pr: 3, pb: 2, mt: 2, borderBottom: '1px solid #eee' }}>
48
+ <Stack direction="row" alignItems="center">
49
+ <Typography className="drawer-form-header" variant="h6" sx={{ fontWeight: 600 }}>
50
+ {props.text}
51
+ </Typography>
52
+ {!settings.livemode && <Livemode />}
53
+ </Stack>
54
+ <Stack direction="row" alignItems="center" justifyContent="space-between">
55
+ <Close
56
+ sx={{ mr: props.addons ? 1 : 0, color: 'text.secondary', cursor: 'pointer' }}
57
+ onClick={() => setOpen(false)}
58
+ />
59
+ {props.addons && <Divider orientation="vertical" flexItem sx={{ mr: 2 }} />}
60
+ {props.addons}
61
+ </Stack>
62
+ </Stack>
63
+ <Box className="drawer-form-body" sx={{ mx: 3, my: 2 }}>
64
+ {props.children}
65
+ </Box>
66
+ </Container>
67
+ </>
68
+ );
69
+ }
70
+
71
+ const Container = styled<any>(Drawer)`
72
+ .MuiPaper-root {
73
+ width: 80%;
74
+ min-width: 360px;
75
+ max-width: ${(props) => props.maxWidth}px;
76
+ }
77
+ `;
@@ -0,0 +1,7 @@
1
+ import Error from './error';
2
+
3
+ function ErrorFallback({ error }: { error: Error }) {
4
+ return <Error message={error?.message} />;
5
+ }
6
+
7
+ export default ErrorFallback;
@@ -0,0 +1,39 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Box from '@mui/material/Box';
3
+ import { styled } from '@mui/system';
4
+
5
+ const Container = styled(Box)`
6
+ width: 100%;
7
+ height: 100vh;
8
+ display: flex;
9
+ flex-direction: column;
10
+ text-align: center;
11
+ max-width: 800px;
12
+ margin: 0 auto;
13
+ `;
14
+
15
+ const Content = styled(Box)`
16
+ flex: 1;
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: center;
20
+ font-family: 'Lexend';
21
+ font-style: normal;
22
+ font-weight: 400;
23
+ line-height: 20px;
24
+ text-align: center;
25
+ `;
26
+
27
+ export default function Error({ message }: { message?: string }) {
28
+ const { t } = useLocaleContext();
29
+
30
+ return (
31
+ <Container>
32
+ <Content>{message || t('common.error')}</Content>
33
+ </Container>
34
+ );
35
+ }
36
+
37
+ Error.defaultProps = {
38
+ message: '',
39
+ };
@@ -0,0 +1,217 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import type { TEventExpanded } from '@did-pay/types';
5
+ import { Alert, CircularProgress, Typography } from '@mui/material';
6
+ import { useRequest } from 'ahooks';
7
+ import { useEffect, useState } from 'react';
8
+ import { useNavigate } from 'react-router-dom';
9
+
10
+ import api from '../../libs/api';
11
+ import { formatTime } from '../../libs/util';
12
+ import Table from '../table';
13
+
14
+ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TEventExpanded[]; count: number }> => {
15
+ const search = new URLSearchParams();
16
+ Object.keys(params).forEach((key) => {
17
+ search.set(key, String(params[key]));
18
+ });
19
+ return api.get(`/api/events?${search.toString()}`).then((res) => res.data);
20
+ };
21
+
22
+ type SearchProps = {
23
+ pageSize: number;
24
+ page: number;
25
+ type?: string;
26
+ object_id?: string;
27
+ };
28
+
29
+ type ListProps = {
30
+ features?: {
31
+ toolbar?: boolean;
32
+ };
33
+ type?: string;
34
+ object_id?: string;
35
+ };
36
+
37
+ const getListKey = (props: ListProps) => {
38
+ if (props.type) {
39
+ return `events-${props.type}`;
40
+ }
41
+ if (props.object_id) {
42
+ return `events-${props.object_id}`;
43
+ }
44
+
45
+ return 'events';
46
+ };
47
+
48
+ const getEventDescription = (event: TEventExpanded) => {
49
+ switch (event.type) {
50
+ case 'product.created':
51
+ return `A new product ${event.object_id} was created`;
52
+ case 'product.updated':
53
+ return `The product with ID ${event.object_id} was updated`;
54
+ case 'product.deleted':
55
+ return `The product with ID ${event.object_id} was deleted`;
56
+ case 'price.created':
57
+ return `A new price ${event.object_id} was created`;
58
+ case 'price.updated':
59
+ return `The price with ID ${event.object_id} was updated`;
60
+ case 'price.deleted':
61
+ return `The price with ID ${event.object_id} was deleted`;
62
+ case 'payment_link.created':
63
+ return `A new payment link ${event.object_id} was created`;
64
+ case 'payment_link.updated':
65
+ return `The payment link with ID ${event.object_id} was updated`;
66
+ case 'payment_link.deleted':
67
+ return `The payment link with ID ${event.object_id} was deleted`;
68
+ case 'payment_intent.created':
69
+ return `A new payment intent ${event.object_id} was created`;
70
+ case 'payment_intent.updated':
71
+ return `The payment intent with ID ${event.object_id} was updated`;
72
+ case 'payment_intent.canceled':
73
+ return `The payment intent with ID ${event.object_id} was canceled`;
74
+ case 'payment_intent.succeeded':
75
+ return `The payment intent with ID ${event.object_id} succeeded`;
76
+ case 'payment_intent.processing':
77
+ return `The payment intent with ID ${event.object_id} is processing`;
78
+ case 'payment_intent.requires_action':
79
+ return `The payment intent with ID ${event.object_id} requires user action`;
80
+ case 'payment_intent.payment_failed':
81
+ return `The payment intent with ID ${event.object_id} failed to pay`;
82
+ case 'payment_intent.deleted':
83
+ return `The payment intent with ID ${event.object_id} was deleted`;
84
+ case 'customer.created':
85
+ return `A new customer ${event.object_id} was created`;
86
+ case 'customer.updated':
87
+ return `The customer with ID ${event.object_id} was updated`;
88
+ case 'customer.deleted':
89
+ return `The customer with ID ${event.object_id} was deleted`;
90
+ case 'invoice.created':
91
+ return `A new invoice ${event.object_id} was created`;
92
+ case 'invoice.finalized':
93
+ return `The invoice with ID ${event.object_id} was finalized`;
94
+ case 'invoice.updated':
95
+ return `The invoice with ID ${event.object_id} was updated`;
96
+ case 'invoice.voided':
97
+ return `The invoice with ID ${event.object_id} was marked as void`;
98
+ case 'invoice.paid':
99
+ return `The invoice with ID ${event.object_id} was marked as paid`;
100
+ case 'invoice.marked_uncollectible':
101
+ return `The invoice with ID ${event.object_id} was marked as uncollectible`;
102
+ case 'invoice.deleted':
103
+ return `The invoice with ID ${event.object_id} was deleted`;
104
+ case 'checkout.session.completed':
105
+ return `The checkout session ${event.object_id} completed`;
106
+ case 'checkout.session.expired':
107
+ return `The checkout session ${event.object_id} expired`;
108
+ case 'customer.subscription.created':
109
+ return `A new subscription ${event.object_id} was created`;
110
+ case 'customer.subscription.updated':
111
+ return `The subscription with ID ${event.object_id} was updated`;
112
+ case 'customer.subscription.canceled':
113
+ return `The subscription with ID ${event.object_id} was canceled`;
114
+ case 'customer.subscription.resumed':
115
+ return `The subscription with ID ${event.object_id} was resumed`;
116
+ case 'customer.subscription.paused':
117
+ return `The subscription with ID ${event.object_id} was paused`;
118
+ case 'customer.subscription.deleted':
119
+ return `The subscription with ID ${event.object_id} was deleted`;
120
+ default:
121
+ return `${event.type}: ${event.object_id}`;
122
+ }
123
+ };
124
+
125
+ EventList.defaultProps = {
126
+ features: {
127
+ toolbar: true,
128
+ },
129
+ type: '',
130
+ object_id: '',
131
+ };
132
+
133
+ export default function EventList({ type, object_id, features }: ListProps) {
134
+ const listKey = getListKey({ type, object_id });
135
+ const persisted = getDurableData(listKey);
136
+
137
+ const { t } = useLocaleContext();
138
+ const navigate = useNavigate();
139
+ const [search, setSearch] = useState<SearchProps>({
140
+ type,
141
+ object_id,
142
+ pageSize: persisted.rowsPerPage || 50,
143
+ page: persisted.page ? persisted.page + 1 : 1,
144
+ });
145
+
146
+ const { loading, error, data, refresh } = useRequest(() => fetchData(search));
147
+ useEffect(() => {
148
+ refresh();
149
+ }, [search, refresh]);
150
+
151
+ if (error) {
152
+ return <Alert severity="error">{error.message}</Alert>;
153
+ }
154
+
155
+ if (loading || !data) {
156
+ return <CircularProgress />;
157
+ }
158
+
159
+ if (data && data.list.length === 0) {
160
+ return <Typography color="text.secondary">{t('admin.event.empty')}</Typography>;
161
+ }
162
+
163
+ const columns = [
164
+ {
165
+ label: t('common.description'),
166
+ name: 'type',
167
+ options: {
168
+ customBodyRenderLite: (_: string, index: number) => {
169
+ const item = data.list[index] as TEventExpanded;
170
+ return getEventDescription(item);
171
+ },
172
+ },
173
+ },
174
+ {
175
+ label: t('common.id'),
176
+ name: 'id',
177
+ },
178
+ {
179
+ label: t('common.createdAt'),
180
+ name: 'created_at',
181
+ options: {
182
+ customBodyRender: (e: string) => {
183
+ return formatTime(e);
184
+ },
185
+ },
186
+ },
187
+ ].filter(Boolean);
188
+
189
+ const onTableChange = ({ page, rowsPerPage }: any) => {
190
+ if (search.pageSize !== rowsPerPage) {
191
+ setSearch((x) => ({ ...x, pageSize: rowsPerPage, page: 1 }));
192
+ } else if (search.page !== page + 1) {
193
+ setSearch((x) => ({ ...x, page: page + 1 }));
194
+ }
195
+ };
196
+
197
+ return (
198
+ <Table
199
+ durable={listKey}
200
+ durableKeys={['page', 'rowsPerPage']}
201
+ data={data.list}
202
+ columns={columns}
203
+ loading={loading}
204
+ onChange={onTableChange}
205
+ options={{
206
+ count: data.count,
207
+ page: search.page - 1,
208
+ rowsPerPage: search.pageSize,
209
+ onRowClick: (_: any, { dataIndex }: any) => {
210
+ const item = data.list[dataIndex] as TEventExpanded;
211
+ navigate(`/admin/developers/${item.id}`);
212
+ },
213
+ }}
214
+ toolbar={features?.toolbar}
215
+ />
216
+ );
217
+ }
@@ -0,0 +1,40 @@
1
+ import { Avatar, Stack, Typography } from '@mui/material';
2
+ import type { LiteralUnion } from 'type-fest';
3
+
4
+ type Props = {
5
+ logo?: string;
6
+ name: string;
7
+ description: string;
8
+ variant?: LiteralUnion<'square' | 'rounded' | 'circular', string>;
9
+ };
10
+
11
+ // FIXME: @wangshijun add image filter for logo
12
+ export default function InfoCard(props: Props) {
13
+ const size = { width: 40, height: 40 };
14
+ return (
15
+ <Stack direction="row" alignItems="center" spacing={1}>
16
+ {props.logo ? (
17
+ // @ts-ignore
18
+ <Avatar src={props.logo} alt={props.name} variant={props.variant} sx={size} />
19
+ ) : (
20
+ // @ts-ignore
21
+ <Avatar variant={props.variant} sx={size}>
22
+ {props.name.slice(0, 1)}
23
+ </Avatar>
24
+ )}
25
+ <Stack direction="column" alignItems="flex-start" justifyContent="space-around">
26
+ <Typography variant="body1" color="text.primary">
27
+ {props.name}
28
+ </Typography>
29
+ <Typography variant="subtitle1" color="text.secondary">
30
+ {props.description}
31
+ </Typography>
32
+ </Stack>
33
+ </Stack>
34
+ );
35
+ }
36
+
37
+ InfoCard.defaultProps = {
38
+ logo: '',
39
+ variant: 'rounded',
40
+ };
@@ -0,0 +1,35 @@
1
+ import { InfoOutlined } from '@mui/icons-material';
2
+ import { Divider, Stack, Tooltip, Typography } from '@mui/material';
3
+
4
+ type Props = {
5
+ label: string | React.ReactNode;
6
+ value: string | React.ReactNode;
7
+ tip?: string | React.ReactNode;
8
+ divider?: boolean;
9
+ };
10
+
11
+ export default function InfoMetric(props: Props) {
12
+ return (
13
+ <>
14
+ <Stack direction="column" alignItems="flex-start">
15
+ <Typography variant="body1" mb={1} color="text.secondary">
16
+ {props.label}
17
+ {!!props.tip && (
18
+ <Tooltip title={props.tip}>
19
+ <InfoOutlined fontSize="small" />
20
+ </Tooltip>
21
+ )}
22
+ </Typography>
23
+ <Typography variant="body1" color="text.primary">
24
+ {props.value}
25
+ </Typography>
26
+ </Stack>
27
+ {props.divider && <Divider orientation="vertical" flexItem />}
28
+ </>
29
+ );
30
+ }
31
+
32
+ InfoMetric.defaultProps = {
33
+ tip: '',
34
+ divider: false,
35
+ };
@@ -0,0 +1,28 @@
1
+ import { Box, Stack } from '@mui/material';
2
+ import type { ReactNode } from 'react';
3
+
4
+ type Props = {
5
+ label: string | ReactNode;
6
+ value?: string | ReactNode;
7
+ sizes?: [number, number];
8
+ };
9
+
10
+ InfoRow.defaultProps = {
11
+ value: undefined,
12
+ sizes: [1, 3],
13
+ };
14
+
15
+ export default function InfoRow(props: Props) {
16
+ const isNone = props.value === '' || typeof props.value === 'undefined';
17
+ const sizes = props.sizes || [1, 3];
18
+ return (
19
+ <Stack direction="row" alignItems="flex-start" justifyContent="space-between" flexWrap="wrap" sx={{ mb: 1 }}>
20
+ <Box flex={sizes[0]} color="text.secondary">
21
+ {props.label}
22
+ </Box>
23
+ <Box flex={sizes[1]} color={isNone ? 'text.disabled' : 'color.primary'}>
24
+ {isNone ? 'None' : props.value}
25
+ </Box>
26
+ </Stack>
27
+ );
28
+ }
@@ -0,0 +1,40 @@
1
+ import { Box, FormLabel, TextField, TextFieldProps } from '@mui/material';
2
+ import { Controller, RegisterOptions, useFormContext } from 'react-hook-form';
3
+
4
+ type InputProps = TextFieldProps & {
5
+ name: string;
6
+ label?: string;
7
+ placeholder?: string;
8
+ rules?: RegisterOptions;
9
+ };
10
+
11
+ FormInput.defaultProps = {
12
+ label: '',
13
+ placeholder: '',
14
+ rules: {},
15
+ };
16
+
17
+ export default function FormInput({ name, label, placeholder, rules, ...rest }: InputProps) {
18
+ const { control, formState } = useFormContext();
19
+ return (
20
+ <Controller
21
+ name={name}
22
+ control={control}
23
+ rules={rules}
24
+ render={({ field }) => (
25
+ <Box sx={{ width: '100%' }}>
26
+ {!!label && <FormLabel>{label}</FormLabel>}
27
+ <TextField
28
+ fullWidth
29
+ error={!!formState.errors[name]}
30
+ helperText={formState.errors[name]?.message as string}
31
+ placeholder={placeholder}
32
+ size="small"
33
+ {...field}
34
+ {...rest}
35
+ />
36
+ </Box>
37
+ )}
38
+ />
39
+ );
40
+ }