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
@@ -3,9 +3,9 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
4
  import { api, findCurrency, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
5
5
  import { AddOutlined } from '@mui/icons-material';
6
- import { Box, Button, Divider, Typography } from '@mui/material';
6
+ import { Box, Button, Divider } from '@mui/material';
7
7
  import { cloneDeep } from 'lodash';
8
- import { Fragment } from 'react';
8
+ import { Fragment, useState, useEffect } from 'react';
9
9
  import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
10
10
  import { dispatch } from 'use-bus';
11
11
 
@@ -14,31 +14,145 @@ import DrawerForm from '../../../../components/drawer-form';
14
14
  import PriceActions from '../../../../components/price/actions';
15
15
  import PriceForm, { DEFAULT_PRICE } from '../../../../components/price/form';
16
16
  import ProductForm, { Product } from '../../../../components/product/form';
17
+ import { ProductsProvider } from '../../../../contexts/products';
17
18
 
18
- export default function ProductsCreate() {
19
+ type InitialPrice = {
20
+ currency_id?: string;
21
+ priceModel?: string;
22
+ name?: string;
23
+ metadata?: Record<string, any>;
24
+ };
25
+ type Props = {
26
+ open?: boolean;
27
+ onClose?: () => void;
28
+ onSubmit?: () => void;
29
+ mode?: 'standard' | 'credit';
30
+ meterId?: string;
31
+ initialPrice?: InitialPrice;
32
+ };
33
+
34
+ export default function ProductsCreate({
35
+ open = true,
36
+ onClose = () => {},
37
+ mode = 'standard',
38
+ meterId = '',
39
+ onSubmit: onSubmitCallback = () => {},
40
+ initialPrice = {} as InitialPrice,
41
+ }: Props) {
19
42
  const { t, locale } = useLocaleContext();
20
43
  const { settings } = usePaymentContext();
21
44
 
45
+ // 管理收折状态,Credit 模式下默认展开定价部分
46
+ const [expandedSection, setExpandedSection] = useState<'product' | 'pricing'>(
47
+ mode === 'credit' ? 'pricing' : 'product'
48
+ );
49
+
50
+ // Credit 模式下的默认价格配置
51
+ const getCreditDefaultPrice = () => ({
52
+ ...DEFAULT_PRICE,
53
+ currency_id: settings.baseCurrency.id,
54
+ type: 'one_time' as const,
55
+ nickname: t('admin.creditProduct.defaultNickname'),
56
+ metadata: {
57
+ ...(initialPrice?.metadata || {}),
58
+ ...(meterId ? { meter_id: meterId } : ({} as any)),
59
+ credit_config: {
60
+ priority: 50,
61
+ valid_duration_value: 0,
62
+ valid_duration_unit: 'days',
63
+ ...(initialPrice?.metadata?.credit_config || {}),
64
+ },
65
+ },
66
+ });
67
+
22
68
  const methods = useForm<Product>({
23
69
  mode: 'onChange',
24
70
  defaultValues: {
25
- type: 'service',
26
- name: '',
27
- description: '',
71
+ type: mode === 'credit' ? 'credit' : 'service',
72
+ name:
73
+ mode === 'credit'
74
+ ? t('admin.creditProduct.defaultName', {
75
+ name: initialPrice?.name || 'Credit',
76
+ })
77
+ : '',
78
+ description: mode === 'credit' ? t('admin.creditProduct.defaultDescription') : '',
28
79
  images: [],
29
80
  statement_descriptor: '',
30
- unit_label: '',
81
+ unit_label: mode === 'credit' ? t('admin.creditProduct.unitLabel') : '',
31
82
  features: [],
32
- prices: [{ ...DEFAULT_PRICE, currency_id: settings.baseCurrency.id }],
33
- metadata: [],
83
+ prices: [
84
+ mode === 'credit'
85
+ ? getCreditDefaultPrice()
86
+ : {
87
+ ...DEFAULT_PRICE,
88
+ recurring: {
89
+ ...DEFAULT_PRICE.recurring,
90
+ meter_id: meterId,
91
+ usage_type:
92
+ initialPrice?.priceModel === 'credit_metered' ? 'metered' : DEFAULT_PRICE.recurring?.usage_type,
93
+ },
94
+ unit_amount: '1',
95
+ model: initialPrice?.priceModel || 'standard',
96
+ currency_id: initialPrice?.currency_id || settings.baseCurrency.id,
97
+ },
98
+ ],
34
99
  },
35
100
  });
36
- const { control, handleSubmit, getValues, clearErrors } = methods;
101
+ const {
102
+ control,
103
+ handleSubmit,
104
+ getValues,
105
+ clearErrors,
106
+ formState: { errors },
107
+ } = methods;
37
108
 
38
109
  const prices = useFieldArray({ control, name: 'prices' });
39
110
  const getPrice = (index: number) => methods.getValues().prices[index];
40
111
 
112
+ useEffect(() => {
113
+ if (Object.keys(errors).length > 0) {
114
+ const hasProductErrors =
115
+ errors.name ||
116
+ errors.description ||
117
+ errors.images ||
118
+ errors.statement_descriptor ||
119
+ errors.unit_label ||
120
+ errors.features ||
121
+ errors.metadata;
122
+
123
+ const hasPriceErrors = errors.prices;
124
+
125
+ if (hasProductErrors) {
126
+ setExpandedSection('product');
127
+ } else if (hasPriceErrors) {
128
+ setExpandedSection('pricing');
129
+ }
130
+ }
131
+ }, [errors]);
132
+
41
133
  const onSubmit = (data: Product) => {
134
+ if (mode === 'credit') {
135
+ data.type = 'credit';
136
+ data.prices.forEach((price: any) => {
137
+ if (meterId) {
138
+ price.metadata = price.metadata || {};
139
+ const meterIdExists = price.metadata.meter_id;
140
+ if (!meterIdExists) {
141
+ price.metadata.meter_id = meterId;
142
+ }
143
+ }
144
+ });
145
+ } else {
146
+ data.prices.forEach((price: any) => {
147
+ if (price.billing_type === 'metered') {
148
+ price.type = 'recurring';
149
+ if (price.recurring) {
150
+ price.recurring.usage_type = 'metered';
151
+ }
152
+ }
153
+ });
154
+ }
155
+
42
156
  api
43
157
  .post('/api/products', data)
44
158
  .then(() => {
@@ -46,66 +160,131 @@ export default function ProductsCreate() {
46
160
  methods.reset();
47
161
  dispatch('drawer.submitted');
48
162
  dispatch('project.created');
163
+ if (onSubmitCallback) {
164
+ onSubmitCallback();
165
+ }
166
+ if (onClose) {
167
+ onClose();
168
+ }
49
169
  })
50
- .catch((err) => {
170
+ .catch((err: any) => {
51
171
  console.error(err);
52
172
  Toast.error(formatError(err));
53
173
  });
54
174
  };
55
175
 
176
+ const onSubmitError = (formErrors: any) => {
177
+ // 表单验证失败时,错误会通过 useEffect 自动处理展开逻辑
178
+ // eslint-disable-next-line no-console
179
+ console.log('Form validation errors:', formErrors);
180
+ };
181
+
182
+ const handleClose = () => {
183
+ clearErrors();
184
+ if (onClose) {
185
+ onClose();
186
+ }
187
+ };
188
+
189
+ const getDrawerTitle = () => {
190
+ if (mode === 'credit') {
191
+ return t('admin.creditProduct.create');
192
+ }
193
+ return t('admin.product.add');
194
+ };
195
+
56
196
  return (
57
197
  <DrawerForm
58
198
  icon={<AddOutlined />}
59
- text={t('admin.product.add')}
60
- onClose={() => clearErrors()}
199
+ text={getDrawerTitle()}
200
+ open={open}
201
+ onClose={handleClose}
61
202
  width={640}
62
203
  addons={
63
- <Button variant="contained" size="small" onClick={handleSubmit(onSubmit)}>
204
+ <Button variant="contained" size="small" onClick={handleSubmit(onSubmit, onSubmitError)}>
64
205
  {t('admin.product.save')}
65
206
  </Button>
66
207
  }>
67
208
  <FormProvider {...methods}>
68
- <Typography variant="h6" sx={{ mb: 3, fontWeight: 600 }}>
69
- {t('admin.product.info')}
70
- </Typography>
71
- <ProductForm />
72
- <Typography variant="h6" sx={{ mt: 5, mb: 3, fontWeight: 600 }}>
73
- {t('admin.price.info')}
74
- </Typography>
75
- <Box>
76
- {prices.fields.map((price, index) => (
77
- <Fragment key={price.id}>
78
- <Collapse
79
- expanded
80
- style={{ fontWeight: 'bold', width: '50%' }}
81
- addons={<PriceActions onDuplicate={() => prices.append(price)} onRemove={() => prices.remove(index)} />}
82
- trigger={(expanded: boolean) => {
83
- if (expanded) {
84
- return t('admin.price.detail');
85
- }
86
- const priceItem = getPrice(index);
87
- const currency =
88
- findCurrency(settings.paymentMethods, priceItem?.currency_id || '') || settings.baseCurrency;
89
-
90
- // @ts-ignore
91
- return formatPrice(priceItem as any, currency, getValues().unit_label, 1, false, locale);
209
+ <ProductsProvider>
210
+ <Collapse
211
+ expanded={expandedSection === 'product'}
212
+ value="product"
213
+ card
214
+ onChange={(_, expanded) => {
215
+ if (expanded) {
216
+ setExpandedSection('product');
217
+ }
218
+ }}
219
+ style={{ mb: 2 }}
220
+ trigger={t('admin.product.info')}>
221
+ <Box sx={{ p: 1, pl: 2 }}>
222
+ <ProductForm />
223
+ </Box>
224
+ </Collapse>
225
+
226
+ <Collapse
227
+ expanded={expandedSection === 'pricing'}
228
+ value="pricing"
229
+ onChange={(_, expanded) => {
230
+ if (expanded) {
231
+ setExpandedSection('pricing');
232
+ }
233
+ }}
234
+ card
235
+ trigger={
236
+ expandedSection === 'pricing'
237
+ ? t('admin.price.plan')
238
+ : `${t('admin.price.plan')} (${prices.fields.length} 个价格)`
239
+ }>
240
+ <Box sx={{ p: 1, pl: 2 }}>
241
+ {prices.fields.map((price, index) => (
242
+ <Fragment key={price.id}>
243
+ <Collapse
244
+ expanded
245
+ style={{ fontWeight: 'bold', width: '100%' }}
246
+ addons={
247
+ <PriceActions onDuplicate={() => prices.append(price)} onRemove={() => prices.remove(index)} />
248
+ }
249
+ trigger={(expanded: boolean) => {
250
+ if (expanded) {
251
+ return t('admin.price.order', {
252
+ order: index + 1,
253
+ });
254
+ }
255
+ const priceItem = getPrice(index);
256
+ const currency =
257
+ findCurrency(settings.paymentMethods, priceItem?.currency_id || '') || settings.baseCurrency;
258
+
259
+ // @ts-ignore
260
+ return formatPrice(priceItem as any, currency, getValues().unit_label, 1, false, locale);
261
+ }}>
262
+ <Box sx={{ width: '100%', p: 1 }}>
263
+ <PriceForm prefix={`prices.${index}`} meterId={meterId} />
264
+ </Box>
265
+ </Collapse>
266
+ <Divider sx={{ mt: 2, mb: 2 }} />
267
+ </Fragment>
268
+ ))}
269
+ <Button
270
+ variant="outlined"
271
+ color="primary"
272
+ sx={{
273
+ color: 'text.primary',
274
+ width: '100%',
275
+ }}
276
+ onClick={() => {
277
+ const newPrice =
278
+ mode === 'credit'
279
+ ? getCreditDefaultPrice()
280
+ : ({ ...cloneDeep(DEFAULT_PRICE), currency_id: settings.baseCurrency.id } as any);
281
+ prices.append(newPrice);
92
282
  }}>
93
- <PriceForm prefix={`prices.${index}`} />
94
- </Collapse>
95
- <Divider sx={{ mt: 2, mb: 4 }} />
96
- </Fragment>
97
- ))}
98
- <Box mt={1}>
99
- <Button
100
- size="small"
101
- variant="outlined"
102
- color="inherit"
103
- // @ts-ignore
104
- onClick={() => prices.append({ ...cloneDeep(DEFAULT_PRICE), currency_id: settings.baseCurrency.id })}>
105
- <AddOutlined fontSize="small" /> Add another price
106
- </Button>
107
- </Box>
108
- </Box>
283
+ <AddOutlined fontSize="small" /> {t('admin.price.addAnother')}
284
+ </Button>
285
+ </Box>
286
+ </Collapse>
287
+ </ProductsProvider>
109
288
  </FormProvider>
110
289
  </DrawerForm>
111
290
  );
@@ -134,12 +134,21 @@ export default function ProductDetail(props: { id: string }) {
134
134
  {t('admin.product.locked')}
135
135
  </Alert>
136
136
  )}
137
- <Stack className="page-header" direction="row" justifyContent="space-between" alignItems="center">
137
+ <Stack
138
+ className="page-header"
139
+ direction="row"
140
+ sx={{
141
+ justifyContent: 'space-between',
142
+ alignItems: 'center',
143
+ }}>
138
144
  <Stack
139
145
  direction="row"
140
- alignItems="center"
141
- sx={{ fontWeight: 'normal', cursor: 'pointer' }}
142
- onClick={() => goBackOrFallback('/admin/products')}>
146
+ onClick={() => goBackOrFallback('/admin/products')}
147
+ sx={{
148
+ alignItems: 'center',
149
+ fontWeight: 'normal',
150
+ cursor: 'pointer',
151
+ }}>
143
152
  <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
144
153
  <Typography variant="h6" sx={{ color: 'text.secondary', fontWeight: 'normal' }}>
145
154
  {t('admin.products')}
@@ -148,37 +157,64 @@ export default function ProductDetail(props: { id: string }) {
148
157
  <ProductActions data={data} onChange={onChange} variant="normal" />
149
158
  </Stack>
150
159
  <Box
151
- mt={4}
152
- mb={3}
153
160
  sx={{
161
+ mt: 4,
162
+ mb: 3,
154
163
  display: 'flex',
164
+
155
165
  gap: {
156
166
  xs: 2,
157
167
  sm: 2,
158
168
  md: 5,
159
169
  },
170
+
160
171
  flexWrap: 'wrap',
172
+
161
173
  flexDirection: {
162
174
  xs: 'column',
163
175
  sm: 'column',
164
176
  md: 'row',
165
177
  },
178
+
166
179
  alignItems: {
167
180
  xs: 'flex-start',
168
181
  sm: 'flex-start',
169
182
  md: 'center',
170
183
  },
171
184
  }}>
172
- <Stack direction="row" justifyContent="space-between" alignItems="center">
173
- <Stack direction="row" alignItems="center" spacing={1}>
185
+ <Stack
186
+ direction="row"
187
+ sx={{
188
+ justifyContent: 'space-between',
189
+ alignItems: 'center',
190
+ }}>
191
+ <Stack
192
+ direction="row"
193
+ spacing={1}
194
+ sx={{
195
+ alignItems: 'center',
196
+ }}>
174
197
  {data.images.length > 0 && (
175
198
  <Avatar src={data.images[0]} alt={data.name} variant="square" sx={{ width: '52px', height: '52px' }} />
176
199
  )}
177
- <Stack direction="column" alignItems="flex-start" justifyContent="space-around">
178
- <Typography variant="h2" color="text.primary">
200
+ <Stack
201
+ direction="column"
202
+ sx={{
203
+ alignItems: 'flex-start',
204
+ justifyContent: 'space-around',
205
+ }}>
206
+ <Typography
207
+ variant="h2"
208
+ sx={{
209
+ color: 'text.primary',
210
+ }}>
179
211
  {data.name}
180
212
  </Typography>
181
- <Typography variant="subtitle1" color="text.lighter">
213
+ <Typography
214
+ variant="subtitle1"
215
+ sx={{
216
+ color: 'text.lighter',
217
+ }}>
182
218
  {formatProductPrice(data as any, defaultCurrency, locale)}
183
219
  </Typography>
184
220
  </Stack>
@@ -186,18 +222,22 @@ export default function ProductDetail(props: { id: string }) {
186
222
  </Stack>
187
223
  <Stack
188
224
  className="section-body"
189
- justifyContent="flex-start"
190
- flexWrap="wrap"
191
225
  sx={{
226
+ justifyContent: 'flex-start',
227
+ flexWrap: 'wrap',
228
+
192
229
  'hr.MuiDivider-root:last-child': {
193
230
  display: 'none',
194
231
  },
232
+
195
233
  flexDirection: {
196
234
  xs: 'column',
197
235
  sm: 'column',
198
236
  md: 'row',
199
237
  },
238
+
200
239
  alignItems: 'flex-start',
240
+
201
241
  gap: {
202
242
  xs: 1,
203
243
  sm: 1,
@@ -210,7 +250,6 @@ export default function ProductDetail(props: { id: string }) {
210
250
  </Box>
211
251
  <Divider />
212
252
  </Box>
213
-
214
253
  <Stack
215
254
  sx={{
216
255
  flexDirection: {
@@ -239,7 +278,14 @@ export default function ProductDetail(props: { id: string }) {
239
278
  },
240
279
  },
241
280
  }}>
242
- <Box flex={1} className="payment-link-column-1" sx={{ gap: 2.5, display: 'flex', flexDirection: 'column' }}>
281
+ <Box
282
+ className="payment-link-column-1"
283
+ sx={{
284
+ flex: 1,
285
+ gap: 2.5,
286
+ display: 'flex',
287
+ flexDirection: 'column',
288
+ }}>
243
289
  <Box className="section" sx={{ containerType: 'inline-size' }}>
244
290
  <SectionHeader title={t('admin.details')}>
245
291
  <Button
@@ -295,7 +341,11 @@ export default function ProductDetail(props: { id: string }) {
295
341
  sx={{ width: '160px', height: '160px' }}
296
342
  />
297
343
  ) : (
298
- <Typography variant="body2" color="text.lighter">
344
+ <Typography
345
+ variant="body2"
346
+ sx={{
347
+ color: 'text.lighter',
348
+ }}>
299
349
  {t('empty.image')}
300
350
  </Typography>
301
351
  )
@@ -329,6 +379,7 @@ export default function ProductDetail(props: { id: string }) {
329
379
  <AddPrice
330
380
  loading={state.loading.price}
331
381
  onSave={onAddPrice}
382
+ productType={data.type}
332
383
  onCancel={() => setState((prev) => ({ adding: { ...prev.adding, price: false } }))}
333
384
  />
334
385
  )}
@@ -336,7 +387,7 @@ export default function ProductDetail(props: { id: string }) {
336
387
  </Box>
337
388
  <Divider />
338
389
  <Box className="section">
339
- <SectionHeader title={t('admin.events')} />
390
+ <SectionHeader title={t('admin.events.title')} />
340
391
  <Box className="section-body">
341
392
  <EventList
342
393
  features={{ toolbar: false }}
@@ -350,7 +401,12 @@ export default function ProductDetail(props: { id: string }) {
350
401
  <Box className="section">
351
402
  <SectionHeader
352
403
  title={
353
- <Stack direction="row" alignItems="center" spacing={1}>
404
+ <Stack
405
+ direction="row"
406
+ spacing={1}
407
+ sx={{
408
+ alignItems: 'center',
409
+ }}>
354
410
  <Typography
355
411
  variant="h3"
356
412
  sx={{
@@ -41,7 +41,14 @@ export default function SettingsIndex() {
41
41
 
42
42
  return (
43
43
  <>
44
- <Stack direction="row" alignItems="flex-start" justifyContent="end" flexWrap="wrap" spacing={1}>
44
+ <Stack
45
+ direction="row"
46
+ spacing={1}
47
+ sx={{
48
+ alignItems: 'flex-start',
49
+ justifyContent: 'end',
50
+ flexWrap: 'wrap',
51
+ }}>
45
52
  {/* @ts-ignore */}
46
53
  <Tabs
47
54
  // @ts-ignore