payment-kit 1.18.56 → 1.19.1

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 (214) hide show
  1. package/.eslintrc.js +6 -0
  2. package/api/src/crons/index.ts +8 -0
  3. package/api/src/index.ts +4 -0
  4. package/api/src/libs/credit-grant.ts +146 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +4 -3
  7. package/api/src/libs/notification/template/base.ts +388 -2
  8. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  9. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  10. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  11. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  12. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  13. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  14. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  15. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  16. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  17. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  18. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  19. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  20. package/api/src/libs/payment.ts +69 -0
  21. package/api/src/libs/queue/index.ts +3 -2
  22. package/api/src/libs/session.ts +8 -0
  23. package/api/src/libs/subscription.ts +74 -3
  24. package/api/src/libs/ws.ts +23 -1
  25. package/api/src/locales/en.ts +33 -0
  26. package/api/src/locales/zh.ts +31 -0
  27. package/api/src/queues/credit-consume.ts +715 -0
  28. package/api/src/queues/credit-grant.ts +572 -0
  29. package/api/src/queues/notification.ts +173 -128
  30. package/api/src/queues/payment.ts +210 -122
  31. package/api/src/queues/subscription.ts +179 -0
  32. package/api/src/routes/checkout-sessions.ts +157 -9
  33. package/api/src/routes/connect/shared.ts +3 -2
  34. package/api/src/routes/credit-grants.ts +241 -0
  35. package/api/src/routes/credit-transactions.ts +208 -0
  36. package/api/src/routes/index.ts +8 -0
  37. package/api/src/routes/meter-events.ts +347 -0
  38. package/api/src/routes/meters.ts +219 -0
  39. package/api/src/routes/payment-currencies.ts +14 -2
  40. package/api/src/routes/payment-links.ts +1 -1
  41. package/api/src/routes/payment-methods.ts +14 -2
  42. package/api/src/routes/prices.ts +43 -0
  43. package/api/src/routes/pricing-table.ts +13 -7
  44. package/api/src/routes/products.ts +63 -4
  45. package/api/src/routes/settings.ts +1 -1
  46. package/api/src/routes/subscriptions.ts +4 -0
  47. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  48. package/api/src/store/models/credit-grant.ts +486 -0
  49. package/api/src/store/models/credit-transaction.ts +268 -0
  50. package/api/src/store/models/customer.ts +8 -0
  51. package/api/src/store/models/index.ts +52 -1
  52. package/api/src/store/models/meter-event.ts +423 -0
  53. package/api/src/store/models/meter.ts +176 -0
  54. package/api/src/store/models/payment-currency.ts +66 -14
  55. package/api/src/store/models/price.ts +6 -0
  56. package/api/src/store/models/product.ts +2 -2
  57. package/api/src/store/models/subscription.ts +24 -0
  58. package/api/src/store/models/types.ts +28 -2
  59. package/api/tests/libs/subscription.spec.ts +53 -0
  60. package/blocklet.yml +9 -1
  61. package/package.json +57 -58
  62. package/scripts/sdk.js +233 -1
  63. package/src/app.tsx +10 -0
  64. package/src/components/actions.tsx +22 -9
  65. package/src/components/balance-list.tsx +40 -12
  66. package/src/components/collapse.tsx +33 -15
  67. package/src/components/copyable.tsx +8 -7
  68. package/src/components/currency.tsx +15 -7
  69. package/src/components/customer/actions.tsx +1 -5
  70. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  71. package/src/components/customer/credit-overview.tsx +233 -0
  72. package/src/components/customer/form.tsx +7 -2
  73. package/src/components/customer/link.tsx +4 -12
  74. package/src/components/customer/notification-preference.tsx +18 -9
  75. package/src/components/customer/overdraft-protection.tsx +112 -41
  76. package/src/components/drawer-form.tsx +42 -18
  77. package/src/components/error.tsx +1 -5
  78. package/src/components/event/list.tsx +9 -10
  79. package/src/components/filter-toolbar.tsx +20 -19
  80. package/src/components/info-card.tsx +32 -18
  81. package/src/components/info-metric.tsx +16 -6
  82. package/src/components/info-row-group.tsx +1 -7
  83. package/src/components/info-row.tsx +30 -24
  84. package/src/components/invoice/action.tsx +1 -7
  85. package/src/components/invoice/list.tsx +34 -26
  86. package/src/components/invoice/recharge.tsx +5 -7
  87. package/src/components/invoice/table.tsx +17 -12
  88. package/src/components/layout/user.tsx +1 -1
  89. package/src/components/metadata/form.tsx +290 -94
  90. package/src/components/metadata/list.tsx +11 -3
  91. package/src/components/meter/actions.tsx +101 -0
  92. package/src/components/meter/add-usage-dialog.tsx +239 -0
  93. package/src/components/meter/events-list.tsx +657 -0
  94. package/src/components/meter/form.tsx +245 -0
  95. package/src/components/meter/products.tsx +264 -0
  96. package/src/components/meter/usage-guide.tsx +174 -0
  97. package/src/components/passport/actions.tsx +9 -4
  98. package/src/components/payment-currency/add.tsx +16 -3
  99. package/src/components/payment-currency/form.tsx +14 -6
  100. package/src/components/payment-intent/actions.tsx +24 -16
  101. package/src/components/payment-intent/list.tsx +30 -9
  102. package/src/components/payment-link/actions.tsx +1 -5
  103. package/src/components/payment-link/after-pay.tsx +4 -2
  104. package/src/components/payment-link/before-pay.tsx +14 -4
  105. package/src/components/payment-link/item.tsx +27 -6
  106. package/src/components/payment-link/preview.tsx +9 -9
  107. package/src/components/payment-link/product-select.tsx +69 -15
  108. package/src/components/payment-method/arcblock.tsx +8 -1
  109. package/src/components/payment-method/base.tsx +8 -1
  110. package/src/components/payment-method/bitcoin.tsx +8 -1
  111. package/src/components/payment-method/ethereum.tsx +8 -1
  112. package/src/components/payment-method/evm-rpc-input.tsx +11 -7
  113. package/src/components/payment-method/form.tsx +2 -7
  114. package/src/components/payment-method/stripe.tsx +2 -0
  115. package/src/components/payouts/actions.tsx +1 -5
  116. package/src/components/payouts/list.tsx +30 -10
  117. package/src/components/payouts/portal/list.tsx +11 -9
  118. package/src/components/price/currency-select.tsx +63 -32
  119. package/src/components/price/form.tsx +895 -370
  120. package/src/components/price/upsell-select.tsx +10 -2
  121. package/src/components/price/upsell.tsx +7 -2
  122. package/src/components/pricing-table/actions.tsx +1 -5
  123. package/src/components/pricing-table/customer-settings.tsx +5 -1
  124. package/src/components/pricing-table/payment-settings.tsx +14 -4
  125. package/src/components/pricing-table/preview.tsx +9 -9
  126. package/src/components/pricing-table/price-item.tsx +6 -1
  127. package/src/components/pricing-table/product-item.tsx +6 -1
  128. package/src/components/pricing-table/product-settings.tsx +17 -4
  129. package/src/components/product/actions.tsx +1 -5
  130. package/src/components/product/add-price.tsx +9 -7
  131. package/src/components/product/create.tsx +8 -9
  132. package/src/components/product/cross-sell-select.tsx +5 -1
  133. package/src/components/product/cross-sell.tsx +7 -2
  134. package/src/components/product/edit-price.tsx +21 -12
  135. package/src/components/product/features.tsx +26 -6
  136. package/src/components/product/form.tsx +115 -72
  137. package/src/components/progress-bar.tsx +1 -1
  138. package/src/components/refund/actions.tsx +1 -7
  139. package/src/components/refund/list.tsx +31 -18
  140. package/src/components/section/header.tsx +12 -14
  141. package/src/components/subscription/actions/cancel.tsx +22 -5
  142. package/src/components/subscription/actions/index.tsx +9 -10
  143. package/src/components/subscription/actions/pause.tsx +32 -6
  144. package/src/components/subscription/actions/slash-stake.tsx +5 -3
  145. package/src/components/subscription/description.tsx +12 -8
  146. package/src/components/subscription/items/index.tsx +31 -16
  147. package/src/components/subscription/items/usage-records.tsx +19 -5
  148. package/src/components/subscription/list.tsx +5 -7
  149. package/src/components/subscription/metrics.tsx +62 -15
  150. package/src/components/subscription/portal/actions.tsx +78 -71
  151. package/src/components/subscription/portal/cancel.tsx +10 -3
  152. package/src/components/subscription/portal/list.tsx +48 -26
  153. package/src/components/uploader.tsx +5 -13
  154. package/src/components/webhook/attempts.tsx +51 -16
  155. package/src/components/webhook/request-info.tsx +8 -6
  156. package/src/contexts/products.tsx +27 -10
  157. package/src/hooks/subscription.ts +34 -0
  158. package/src/libs/meter-utils.ts +196 -0
  159. package/src/libs/util.ts +4 -0
  160. package/src/locales/en.tsx +385 -4
  161. package/src/locales/zh.tsx +364 -0
  162. package/src/pages/admin/billing/index.tsx +61 -33
  163. package/src/pages/admin/billing/invoices/detail.tsx +49 -13
  164. package/src/pages/admin/billing/meters/create.tsx +60 -0
  165. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  166. package/src/pages/admin/billing/meters/index.tsx +210 -0
  167. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  168. package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
  169. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  170. package/src/pages/admin/customers/customers/detail.tsx +67 -14
  171. package/src/pages/admin/customers/customers/index.tsx +6 -1
  172. package/src/pages/admin/customers/index.tsx +5 -0
  173. package/src/pages/admin/developers/events/detail.tsx +37 -11
  174. package/src/pages/admin/developers/index.tsx +1 -1
  175. package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
  176. package/src/pages/admin/index.tsx +15 -2
  177. package/src/pages/admin/overview.tsx +107 -19
  178. package/src/pages/admin/payments/intents/detail.tsx +58 -14
  179. package/src/pages/admin/payments/payouts/detail.tsx +63 -15
  180. package/src/pages/admin/payments/refunds/detail.tsx +58 -14
  181. package/src/pages/admin/products/index.tsx +11 -4
  182. package/src/pages/admin/products/links/create.tsx +22 -4
  183. package/src/pages/admin/products/links/detail.tsx +43 -14
  184. package/src/pages/admin/products/passports/index.tsx +23 -4
  185. package/src/pages/admin/products/prices/actions.tsx +16 -9
  186. package/src/pages/admin/products/prices/detail.tsx +73 -14
  187. package/src/pages/admin/products/prices/list.tsx +15 -3
  188. package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
  189. package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
  190. package/src/pages/admin/products/products/create.tsx +233 -54
  191. package/src/pages/admin/products/products/detail.tsx +74 -18
  192. package/src/pages/admin/settings/index.tsx +8 -1
  193. package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
  194. package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
  195. package/src/pages/admin/settings/vault-config/index.tsx +57 -10
  196. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  197. package/src/pages/customer/index.tsx +76 -17
  198. package/src/pages/customer/invoice/detail.tsx +63 -14
  199. package/src/pages/customer/invoice/past-due.tsx +11 -3
  200. package/src/pages/customer/payout/detail.tsx +56 -13
  201. package/src/pages/customer/recharge/account.tsx +78 -18
  202. package/src/pages/customer/recharge/subscription.tsx +86 -25
  203. package/src/pages/customer/refund/list.tsx +60 -24
  204. package/src/pages/customer/subscription/change-payment.tsx +17 -6
  205. package/src/pages/customer/subscription/change-plan.tsx +34 -7
  206. package/src/pages/customer/subscription/detail.tsx +134 -34
  207. package/src/pages/customer/subscription/embed.tsx +25 -5
  208. package/src/pages/home.tsx +26 -4
  209. package/src/pages/integrations/donations/edit-form.tsx +25 -9
  210. package/src/pages/integrations/donations/index.tsx +26 -9
  211. package/src/pages/integrations/donations/preview.tsx +59 -15
  212. package/src/pages/integrations/index.tsx +10 -1
  213. package/src/pages/integrations/overview.tsx +78 -17
  214. package/vite.config.ts +60 -30
@@ -1,30 +1,96 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
2
  import { FormInput } from '@blocklet/payment-react';
3
- import { AddOutlined, DeleteOutlineOutlined } from '@mui/icons-material';
4
- import { Box, Button, Divider, IconButton, Stack, Typography } from '@mui/material';
5
- import { useEffect, useRef } from 'react';
3
+ import { AddOutlined, Autorenew, DeleteOutlineOutlined, FormatAlignLeft } from '@mui/icons-material';
4
+ import { Box, Button, Divider, IconButton, Stack, TextField, InputAdornment, Tooltip, FormLabel } from '@mui/material';
5
+ import { useEffect, useRef, useState, useCallback } from 'react';
6
6
  import { useFieldArray, useFormContext } from 'react-hook-form';
7
+ import { isObject, debounce } from 'lodash';
8
+
9
+ import { isObjectContent } from '../../libs/util';
7
10
 
8
11
  export default function MetadataForm({
9
- title,
10
- actions,
12
+ title = undefined,
13
+ actions = null,
11
14
  minHeight = 'auto',
15
+ showModeToggle = true,
16
+ color = 'primary',
17
+ name = 'metadata',
12
18
  }: {
13
19
  title?: string;
14
20
  actions?: React.ReactNode;
15
21
  minHeight?: string;
22
+ showModeToggle?: boolean;
23
+ color?: 'primary' | 'inherit';
24
+ name?: string;
16
25
  }) {
17
26
  const { t } = useLocaleContext();
18
27
  const {
19
28
  control,
20
29
  formState: { errors },
30
+ setValue,
31
+ getValues,
21
32
  } = useFormContext();
22
33
 
23
- const metadata = useFieldArray({ control, name: 'metadata' });
34
+ const metadata = useFieldArray({ control, name });
24
35
  const lastItemRef = useRef<HTMLDivElement | null>(null);
25
36
  const errorRef = useRef<HTMLDivElement | null>(null);
26
37
  const initRef = useRef(false);
27
38
 
39
+ const [mode, setMode] = useState<'form' | 'json'>('form');
40
+ const [jsonValue, setJsonValue] = useState('');
41
+ const [formatError, setFormatError] = useState<string | null>(null);
42
+
43
+ // Initialize JSON value from form data
44
+ useEffect(() => {
45
+ const formData = getValues();
46
+ if (Array.isArray(formData.metadata)) {
47
+ const metadataObj = formData.metadata.reduce((acc: any, x: any) => {
48
+ if (x.key) {
49
+ acc[x.key] = isObjectContent(x.value) ? JSON.parse(x.value) : x.value;
50
+ }
51
+ return acc;
52
+ }, {});
53
+ setJsonValue(JSON.stringify(metadataObj, null, 2));
54
+ }
55
+ }, [getValues]);
56
+
57
+ // Debounced function to sync JSON changes to form
58
+ const debouncedSyncToForm = useCallback(
59
+ debounce((jsonString: string) => {
60
+ try {
61
+ const parsedData = JSON.parse(jsonString);
62
+ const formMetadata: Array<{ key: string; value: string }> = [];
63
+ const orderedKeys = Object.keys(JSON.parse(JSON.stringify(parsedData)));
64
+ orderedKeys.forEach((key) => {
65
+ formMetadata.push({
66
+ key,
67
+ value: isObject(parsedData[key]) ? JSON.stringify(parsedData[key]) : parsedData[key],
68
+ });
69
+ });
70
+
71
+ setValue('metadata', formMetadata.length > 0 ? formMetadata : [{ key: '', value: '' }]);
72
+ } catch (err) {
73
+ // eslint-disable-next-line no-console
74
+ console.log(err);
75
+ }
76
+ }, 500),
77
+ [setValue]
78
+ );
79
+
80
+ // Sync JSON changes to form when in JSON mode
81
+ useEffect(() => {
82
+ if (mode === 'json') {
83
+ debouncedSyncToForm(jsonValue);
84
+ }
85
+ }, [jsonValue, mode, debouncedSyncToForm]);
86
+
87
+ // Cleanup debounce on unmount
88
+ useEffect(() => {
89
+ return () => {
90
+ debouncedSyncToForm.cancel();
91
+ };
92
+ }, [debouncedSyncToForm]);
93
+
28
94
  useEffect(() => {
29
95
  if (!initRef.current) {
30
96
  initRef.current = true;
@@ -41,99 +107,229 @@ export default function MetadataForm({
41
107
  }
42
108
  }, [errors.metadata, errorRef]);
43
109
 
44
- return (
45
- <Box sx={{ width: 1, height: '100%', display: 'flex', flexDirection: 'column' }}>
46
- {!!title && <Typography>{title}</Typography>}
47
- <Stack
110
+ const handleModeChange = (newMode: 'form' | 'json') => {
111
+ if (newMode === 'json' && mode === 'form') {
112
+ // 从表单模式切换到JSON模式,同步数据
113
+ const formData = getValues();
114
+ if (Array.isArray(formData.metadata)) {
115
+ const metadataObj = formData.metadata.reduce((acc: any, x: any) => {
116
+ if (x.key) {
117
+ acc[x.key] = isObjectContent(x.value) ? JSON.parse(x.value) : x.value;
118
+ }
119
+ return acc;
120
+ }, {});
121
+ setJsonValue(JSON.stringify(metadataObj, null, 2));
122
+ }
123
+ }
124
+ setMode(newMode);
125
+ };
126
+
127
+ const handleJsonChange = (value: string) => {
128
+ setJsonValue(value);
129
+ // 清除格式化错误状态
130
+ if (formatError) {
131
+ setFormatError(null);
132
+ }
133
+ };
134
+
135
+ const handleFormatJson = () => {
136
+ try {
137
+ const parsed = JSON.parse(jsonValue);
138
+ const formatted = JSON.stringify(parsed, null, 2);
139
+ setJsonValue(formatted);
140
+ setFormatError(null);
141
+ } catch (err) {
142
+ setFormatError(err instanceof Error ? err.message : 'Invalid JSON format');
143
+ }
144
+ };
145
+
146
+ const toggleBtn = showModeToggle && (
147
+ <Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
148
+ <Button
149
+ size="small"
150
+ variant="text"
151
+ color="primary"
48
152
  sx={{
49
- maxHeight: {
50
- xs: 'calc(100vh - 130px)',
51
- md: 400,
52
- },
53
- minHeight,
54
- overflow: 'auto',
55
- pb: 1.5,
56
- mr: -1.5,
57
- pr: 1.5,
58
- flex: 1,
59
- }}>
60
- {metadata.fields.map((meta, index) => (
153
+ color: color === 'inherit' ? 'text.secondary' : 'primary.main',
154
+ }}
155
+ onClick={() => handleModeChange(mode === 'form' ? 'json' : 'form')}
156
+ startIcon={<Autorenew />}>
157
+ {mode === 'form' ? t('common.metadata.jsonMode') : t('common.metadata.formMode')}
158
+ </Button>
159
+ </Box>
160
+ );
161
+ return (
162
+ <Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
163
+ {title ? (
164
+ <Stack sx={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
165
+ <FormLabel sx={{ color: 'text.primary', mt: 0 }}>{title}</FormLabel>
166
+ {toggleBtn}
167
+ </Stack>
168
+ ) : (
169
+ toggleBtn
170
+ )}
171
+ {mode === 'form' ? (
172
+ <>
61
173
  <Stack
62
- key={meta.id}
63
- mt={2}
64
- spacing={2}
65
- direction="row"
66
- alignItems="flex-end"
67
- ref={index === metadata.fields.length - 1 ? lastItemRef : null}>
68
- <Stack direction="row" spacing={2} sx={{ flex: 1 }}>
69
- <FormInput
70
- sx={{ flex: 1 }}
71
- errorPosition="right"
72
- size="small"
73
- name={`metadata.${index}.key`}
74
- rules={{
75
- required: t('payment.checkout.required'),
76
- maxLength: {
77
- value: 64,
78
- message: t('common.maxLength', { len: 40 }),
79
- },
80
- }}
81
- placeholder="Key"
82
- label="Key"
83
- // @ts-ignore
84
- ref={errors?.metadata?.[index]?.key ? errorRef : null}
85
- inputProps={{
86
- maxLength: 40,
87
- }}
88
- />
89
- <FormInput
90
- sx={{ flex: 2 }}
91
- size="small"
92
- errorPosition="right"
93
- name={`metadata.${index}.value`}
94
- placeholder="Value"
95
- rules={{
96
- required: t('payment.checkout.required'),
97
- maxLength: {
98
- value: 256,
99
- message: t('common.maxLength', { len: 256 }),
100
- },
101
- }}
102
- label="Value"
103
- // @ts-ignore
104
- ref={errors?.metadata?.[index]?.value ? errorRef : null}
105
- inputProps={{
106
- maxLength: 256,
174
+ sx={{
175
+ maxHeight: {
176
+ xs: showModeToggle ? 'calc(100vh - 180px)' : 'calc(100vh - 130px)',
177
+ md: 400,
178
+ },
179
+ minHeight,
180
+ overflow: 'auto',
181
+ pb: 1.5,
182
+ mr: -1.5,
183
+ pr: 1.5,
184
+ flex: 1,
185
+ }}>
186
+ {metadata.fields.map((meta, index) => (
187
+ <Stack
188
+ key={meta.id}
189
+ ref={index === metadata.fields.length - 1 ? lastItemRef : null}
190
+ sx={{
191
+ mt: 2,
192
+ spacing: 2,
193
+
194
+ alignItems: 'flex-end',
107
195
  }}
108
- />
109
- </Stack>
110
- <IconButton
111
- onClick={() => metadata.remove(index)}
196
+ spacing={2}
197
+ direction="row">
198
+ <Stack direction="row" spacing={2} sx={{ flex: 1 }}>
199
+ <FormInput
200
+ sx={{ flex: 1 }}
201
+ errorPosition="right"
202
+ size="small"
203
+ name={`metadata.${index}.key`}
204
+ rules={{
205
+ required: t('payment.checkout.required'),
206
+ maxLength: {
207
+ value: 64,
208
+ message: t('common.maxLength', { len: 40 }),
209
+ },
210
+ }}
211
+ placeholder="Key"
212
+ label="Key"
213
+ // @ts-ignore
214
+ ref={errors?.metadata?.[index]?.key ? errorRef : null}
215
+ inputProps={{
216
+ maxLength: 40,
217
+ }}
218
+ />
219
+ <FormInput
220
+ sx={{ flex: 2 }}
221
+ size="small"
222
+ errorPosition="right"
223
+ name={`metadata.${index}.value`}
224
+ placeholder="Value"
225
+ rules={{
226
+ required: t('payment.checkout.required'),
227
+ maxLength: {
228
+ value: 256,
229
+ message: t('common.maxLength', { len: 256 }),
230
+ },
231
+ }}
232
+ label="Value"
233
+ // @ts-ignore
234
+ ref={errors?.metadata?.[index]?.value ? errorRef : null}
235
+ inputProps={{
236
+ maxLength: 256,
237
+ }}
238
+ />
239
+ </Stack>
240
+ <IconButton
241
+ onClick={() => metadata.remove(index)}
242
+ sx={{
243
+ border: '1px solid',
244
+ borderColor: 'grey.100',
245
+ borderRadius: 1,
246
+ padding: '8px',
247
+ }}>
248
+ <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
249
+ </IconButton>
250
+ </Stack>
251
+ ))}
252
+ </Stack>
253
+
254
+ <Divider />
255
+ <Stack
256
+ direction="row"
257
+ sx={{
258
+ mt: metadata.fields.length ? 2 : 1,
259
+ justifyContent: 'space-between',
260
+ }}>
261
+ <Button
262
+ size="small"
263
+ variant="outlined"
264
+ color="primary"
112
265
  sx={{
113
- border: '1px solid',
114
- borderColor: 'grey.100',
115
- borderRadius: 1,
116
- padding: '8px',
117
- }}>
118
- <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
119
- </IconButton>
266
+ color: color === 'inherit' ? 'text.primary' : 'primary.main',
267
+ }}
268
+ onClick={() => metadata.append({ key: '', value: '' })}>
269
+ <AddOutlined fontSize="small" /> {t('common.metadata.add')}
270
+ </Button>
271
+ {actions}
120
272
  </Stack>
121
- ))}
122
- </Stack>
123
-
124
- <Divider />
125
- <Stack mt={metadata.fields.length ? 2 : 1} direction="row" justifyContent="space-between">
126
- <Button size="small" variant="outlined" color="primary" onClick={() => metadata.append({ key: '', value: '' })}>
127
- <AddOutlined fontSize="small" /> {t('common.metadata.add')}
128
- </Button>
129
- {actions}
130
- </Stack>
273
+ </>
274
+ ) : (
275
+ <>
276
+ <TextField
277
+ multiline
278
+ fullWidth
279
+ rows={15}
280
+ value={jsonValue}
281
+ onChange={(e) => handleJsonChange(e.target.value)}
282
+ placeholder={t('common.metadata.jsonPlaceholder')}
283
+ InputProps={{
284
+ endAdornment: (
285
+ <InputAdornment position="end" sx={{ alignSelf: 'flex-start', mt: 1 }}>
286
+ <Tooltip
287
+ title={formatError || t('common.metadata.formatJson')}
288
+ placement="top"
289
+ componentsProps={{
290
+ tooltip: {
291
+ sx: formatError
292
+ ? {
293
+ maxWidth: 300,
294
+ backgroundColor: 'error.main',
295
+ opacity: 0.8,
296
+ }
297
+ : {},
298
+ },
299
+ }}>
300
+ <IconButton
301
+ onClick={handleFormatJson}
302
+ size="small"
303
+ sx={{
304
+ color: formatError ? 'error.main' : 'text.secondary',
305
+ }}>
306
+ <FormatAlignLeft fontSize="small" />
307
+ </IconButton>
308
+ </Tooltip>
309
+ </InputAdornment>
310
+ ),
311
+ }}
312
+ sx={{
313
+ mb: 2,
314
+ mt: 2,
315
+ flex: 1,
316
+ '& .MuiInputBase-input': {
317
+ fontFamily: 'monospace',
318
+ fontSize: '14px',
319
+ },
320
+ }}
321
+ />
322
+ <Divider />
323
+ <Stack
324
+ direction="row"
325
+ sx={{
326
+ mt: 2,
327
+ justifyContent: 'flex-end',
328
+ }}>
329
+ {actions}
330
+ </Stack>
331
+ </>
332
+ )}
131
333
  </Box>
132
334
  );
133
335
  }
134
-
135
- MetadataForm.defaultProps = {
136
- title: true,
137
- actions: null,
138
- minHeight: 'auto',
139
- };
@@ -11,17 +11,25 @@ export default function MetadataList({
11
11
  handleEditMetadata = () => {},
12
12
  }: {
13
13
  data: any;
14
- handleEditMetadata: () => void;
14
+ handleEditMetadata?: () => void;
15
15
  }) {
16
16
  const { t } = useLocaleContext();
17
17
 
18
18
  if (isEmpty(data)) {
19
19
  return (
20
20
  <Box sx={{ textAlign: 'center' }}>
21
- <Typography variant="subtitle1" color="text.primary">
21
+ <Typography
22
+ variant="subtitle1"
23
+ sx={{
24
+ color: 'text.primary',
25
+ }}>
22
26
  {t('common.metadata.empty')}
23
27
  </Typography>
24
- <Typography variant="body2" color="text.lighter">
28
+ <Typography
29
+ variant="body2"
30
+ sx={{
31
+ color: 'text.lighter',
32
+ }}>
25
33
  {t('common.metadata.emptyTip')}
26
34
  </Typography>
27
35
  <Button sx={{ color: 'text.link' }} onClick={handleEditMetadata}>
@@ -0,0 +1,101 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Toast from '@arcblock/ux/lib/Toast';
3
+ import { ConfirmDialog, api, formatError } from '@blocklet/payment-react';
4
+ import { useSetState } from 'ahooks';
5
+ import { useNavigate } from 'react-router-dom';
6
+ import type { LiteralUnion } from 'type-fest';
7
+ import type { TMeter } from '@blocklet/payment-types';
8
+ import Actions from '../actions';
9
+ import ClickBoundary from '../click-boundary';
10
+
11
+ interface MeterActionsProps {
12
+ data: TMeter;
13
+ variant?: LiteralUnion<'compact' | 'normal', string>;
14
+ onChange: (action?: string) => void;
15
+ }
16
+
17
+ export default function MeterActions({ data, variant = 'compact', onChange }: MeterActionsProps) {
18
+ const { t } = useLocaleContext();
19
+ const navigate = useNavigate();
20
+ const [state, setState] = useSetState({
21
+ action: '',
22
+ loading: false,
23
+ });
24
+
25
+ const createHandler = (action: string) => {
26
+ return async () => {
27
+ try {
28
+ setState({ loading: true });
29
+
30
+ if (action === 'delete') {
31
+ await api.delete(`/api/meters/${data.id}`);
32
+ } else {
33
+ await api.put(`/api/meters/${data.id}/${action}`);
34
+ }
35
+
36
+ if (action === 'activate') {
37
+ Toast.success(t('admin.meter.activated'));
38
+ } else if (action === 'deactivate') {
39
+ Toast.success(t('admin.meter.deactivated'));
40
+ } else if (action === 'delete') {
41
+ Toast.success(t('admin.meter.deleted'));
42
+ }
43
+
44
+ onChange(action);
45
+ } catch (err) {
46
+ console.error(err);
47
+ Toast.error(formatError(err));
48
+ } finally {
49
+ setState({ loading: false, action: '' });
50
+ }
51
+ };
52
+ };
53
+
54
+ const handleCancel = () => {
55
+ setState({ action: '' });
56
+ };
57
+
58
+ const actions = [
59
+ {
60
+ label: data.status === 'inactive' ? t('admin.meter.activate') : t('admin.meter.deactivate'),
61
+ handler: () => setState({ action: data.status === 'inactive' ? 'activate' : 'deactivate' }),
62
+ color: data.status === 'inactive' ? 'primary' : 'error',
63
+ },
64
+ ];
65
+
66
+ if (variant === 'compact') {
67
+ actions.push({
68
+ label: t('admin.meter.view'),
69
+ handler: () => navigate(`/admin/billing/meters/${data.id}`),
70
+ color: 'primary',
71
+ });
72
+ }
73
+
74
+ return (
75
+ <ClickBoundary>
76
+ <Actions variant={variant} actions={actions} />
77
+
78
+ {state.action === 'activate' && (
79
+ <ConfirmDialog
80
+ onConfirm={createHandler('activate')}
81
+ onCancel={handleCancel}
82
+ title={t('admin.meter.activate')}
83
+ message={t('admin.meter.activateConfirm')}
84
+ loading={state.loading}
85
+ color="primary"
86
+ />
87
+ )}
88
+
89
+ {state.action === 'deactivate' && (
90
+ <ConfirmDialog
91
+ onConfirm={createHandler('deactivate')}
92
+ onCancel={handleCancel}
93
+ title={t('admin.meter.deactivate')}
94
+ message={t('admin.meter.deactivateConfirm')}
95
+ loading={state.loading}
96
+ color="warning"
97
+ />
98
+ )}
99
+ </ClickBoundary>
100
+ );
101
+ }