payment-kit 1.13.111 → 1.13.113

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 (126) hide show
  1. package/api/src/libs/notification/template/subscription-trial-will-end.ts +1 -1
  2. package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
  3. package/api/src/routes/products.ts +1 -0
  4. package/api/src/routes/subscriptions.ts +2 -0
  5. package/blocklet.yml +1 -1
  6. package/package.json +6 -6
  7. package/src/components/actions.tsx +1 -2
  8. package/src/components/blockchain/tx.tsx +1 -1
  9. package/src/components/click-boundary.tsx +1 -2
  10. package/src/components/customer/actions.tsx +2 -3
  11. package/src/components/customer/edit.tsx +3 -3
  12. package/src/components/customer/form.tsx +22 -24
  13. package/src/components/drawer-form.tsx +2 -4
  14. package/src/components/event/list.tsx +2 -3
  15. package/src/components/invoice/action.tsx +2 -3
  16. package/src/components/invoice/list.tsx +3 -4
  17. package/src/components/invoice/table.tsx +7 -7
  18. package/src/components/metadata/form.tsx +3 -4
  19. package/src/components/passport/actions.tsx +1 -2
  20. package/src/components/passport/assign.tsx +1 -1
  21. package/src/components/payment-intent/actions.tsx +2 -3
  22. package/src/components/payment-intent/list.tsx +3 -4
  23. package/src/components/payment-link/actions.tsx +2 -3
  24. package/src/components/payment-link/item.tsx +3 -5
  25. package/src/components/payment-link/product-select.tsx +3 -4
  26. package/src/components/payment-link/rename.tsx +3 -4
  27. package/src/components/payment-method/arcblock.tsx +1 -2
  28. package/src/components/payment-method/bitcoin.tsx +1 -2
  29. package/src/components/payment-method/ethereum.tsx +1 -2
  30. package/src/components/payment-method/stripe.tsx +1 -2
  31. package/src/components/portal/invoice/list.tsx +4 -5
  32. package/src/components/portal/subscription/actions.tsx +7 -8
  33. package/src/components/portal/subscription/cancel.tsx +13 -12
  34. package/src/components/portal/subscription/list.tsx +4 -10
  35. package/src/components/price/currency-select.tsx +2 -2
  36. package/src/components/price/form.tsx +3 -4
  37. package/src/components/price/upsell-select.tsx +2 -3
  38. package/src/components/price/upsell.tsx +3 -5
  39. package/src/components/pricing-table/actions.tsx +2 -3
  40. package/src/components/pricing-table/payment-settings.tsx +4 -4
  41. package/src/components/pricing-table/price-item.tsx +4 -7
  42. package/src/components/pricing-table/product-item.tsx +3 -3
  43. package/src/components/pricing-table/rename.tsx +3 -4
  44. package/src/components/product/actions.tsx +2 -3
  45. package/src/components/product/add-price.tsx +2 -2
  46. package/src/components/product/create.tsx +2 -4
  47. package/src/components/product/cross-sell-select.tsx +3 -3
  48. package/src/components/product/cross-sell.tsx +4 -5
  49. package/src/components/product/edit.tsx +1 -1
  50. package/src/components/product/form.tsx +6 -6
  51. package/src/components/relative-time.tsx +1 -2
  52. package/src/components/subscription/actions/cancel.tsx +2 -3
  53. package/src/components/subscription/actions/index.tsx +2 -3
  54. package/src/components/subscription/items/actions.tsx +1 -1
  55. package/src/components/subscription/items/index.tsx +2 -2
  56. package/src/components/subscription/items/usage-records.tsx +2 -3
  57. package/src/components/subscription/list.tsx +3 -4
  58. package/src/components/subscription/metrics.tsx +2 -2
  59. package/src/components/subscription/status.tsx +3 -3
  60. package/src/components/webhook/attempts.tsx +3 -3
  61. package/src/contexts/products.tsx +2 -3
  62. package/src/libs/api.ts +2 -19
  63. package/src/libs/dayjs.ts +1 -15
  64. package/src/libs/util.ts +24 -555
  65. package/src/locales/en.tsx +0 -139
  66. package/src/locales/index.tsx +5 -23
  67. package/src/locales/zh.tsx +0 -136
  68. package/src/pages/admin/billing/index.tsx +4 -2
  69. package/src/pages/admin/billing/invoices/detail.tsx +3 -4
  70. package/src/pages/admin/billing/subscriptions/detail.tsx +3 -3
  71. package/src/pages/admin/customers/customers/detail.tsx +3 -5
  72. package/src/pages/admin/customers/customers/index.tsx +2 -3
  73. package/src/pages/admin/customers/index.tsx +4 -2
  74. package/src/pages/admin/developers/events/detail.tsx +2 -3
  75. package/src/pages/admin/developers/index.tsx +4 -2
  76. package/src/pages/admin/developers/webhooks/detail.tsx +3 -4
  77. package/src/pages/admin/developers/webhooks/index.tsx +3 -4
  78. package/src/pages/admin/index.tsx +10 -7
  79. package/src/pages/admin/payments/index.tsx +4 -2
  80. package/src/pages/admin/payments/intents/detail.tsx +4 -6
  81. package/src/pages/admin/payments/links/create.tsx +3 -5
  82. package/src/pages/admin/payments/links/detail.tsx +4 -5
  83. package/src/pages/admin/payments/links/index.tsx +4 -6
  84. package/src/pages/admin/products/passports/index.tsx +1 -1
  85. package/src/pages/admin/products/prices/actions.tsx +2 -3
  86. package/src/pages/admin/products/prices/detail.tsx +2 -3
  87. package/src/pages/admin/products/prices/list.tsx +2 -4
  88. package/src/pages/admin/products/pricing-tables/create.tsx +2 -3
  89. package/src/pages/admin/products/pricing-tables/detail.tsx +4 -5
  90. package/src/pages/admin/products/pricing-tables/index.tsx +2 -4
  91. package/src/pages/admin/products/products/create.tsx +2 -4
  92. package/src/pages/admin/products/products/detail.tsx +4 -5
  93. package/src/pages/admin/products/products/index.tsx +4 -6
  94. package/src/pages/admin/settings/index.tsx +4 -2
  95. package/src/pages/admin/settings/payment-methods/create.tsx +3 -5
  96. package/src/pages/admin/settings/payment-methods/index.tsx +2 -3
  97. package/src/pages/checkout/index.tsx +6 -3
  98. package/src/pages/checkout/pay.tsx +5 -69
  99. package/src/pages/checkout/pricing-table.tsx +3 -7
  100. package/src/pages/customer/index.tsx +7 -7
  101. package/src/pages/customer/invoice.tsx +10 -11
  102. package/src/pages/customer/subscription/detail.tsx +4 -4
  103. package/src/pages/customer/subscription/update.tsx +19 -19
  104. package/src/components/checkout/amount.tsx +0 -24
  105. package/src/components/checkout/error.tsx +0 -30
  106. package/src/components/checkout/footer.tsx +0 -12
  107. package/src/components/checkout/form/address.tsx +0 -119
  108. package/src/components/checkout/form/index.tsx +0 -400
  109. package/src/components/checkout/form/phone.tsx +0 -103
  110. package/src/components/checkout/form/stripe.tsx +0 -195
  111. package/src/components/checkout/form/user-buttons.tsx +0 -24
  112. package/src/components/checkout/header.tsx +0 -40
  113. package/src/components/checkout/pay.tsx +0 -385
  114. package/src/components/checkout/pricing-table.tsx +0 -205
  115. package/src/components/checkout/product-card.tsx +0 -52
  116. package/src/components/checkout/product-item.tsx +0 -111
  117. package/src/components/checkout/skeleton/overview.tsx +0 -21
  118. package/src/components/checkout/skeleton/payment.tsx +0 -35
  119. package/src/components/checkout/success.tsx +0 -183
  120. package/src/components/checkout/summary.tsx +0 -198
  121. package/src/components/input.tsx +0 -58
  122. package/src/components/livemode.tsx +0 -23
  123. package/src/components/pricing-table/product-skeleton.tsx +0 -39
  124. package/src/components/status.tsx +0 -19
  125. package/src/components/switch.tsx +0 -48
  126. package/src/contexts/settings.tsx +0 -54
@@ -1,24 +0,0 @@
1
- import SessionManager from '@arcblock/did-connect/lib/SessionManager';
2
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import LocaleSelector from '@arcblock/ux/lib/Locale/selector';
4
- import { Stack, Tooltip } from '@mui/material';
5
-
6
- import { useSessionContext } from '../../../contexts/session';
7
-
8
- export default function UserButtons() {
9
- const { t } = useLocaleContext();
10
- const { session } = useSessionContext();
11
-
12
- return (
13
- <Stack direction="row" alignItems="center" justifyContent="space-between">
14
- <LocaleSelector showText={false} />
15
- {session.user ? (
16
- <SessionManager session={session} />
17
- ) : (
18
- <Tooltip title={t('checkout.login')} arrow>
19
- <SessionManager session={session} />
20
- </Tooltip>
21
- )}
22
- </Stack>
23
- );
24
- }
@@ -1,40 +0,0 @@
1
- import { useTheme } from '@arcblock/ux/lib/Theme';
2
- import type { TCheckoutSessionExpanded } from '@did-pay/types';
3
- import { Avatar, Stack, Typography } from '@mui/material';
4
- import { useCreation, useLocalStorageState, useSize } from 'ahooks';
5
-
6
- import { getStatementDescriptor } from '../../libs/util';
7
- import Livemode from '../livemode';
8
- import UserButtons from './form/user-buttons';
9
-
10
- type Props = {
11
- checkoutSession: TCheckoutSessionExpanded;
12
- };
13
-
14
- export default function PaymentHeader({ checkoutSession }: Props) {
15
- const [livemode] = useLocalStorageState('livemode', { defaultValue: true });
16
- const brand = getStatementDescriptor(checkoutSession.line_items);
17
- const theme = useTheme();
18
-
19
- const domSize = useSize(document.body);
20
-
21
- const isCloumnLayout = useCreation(() => {
22
- if (domSize) {
23
- if (domSize?.width <= theme.breakpoints.values.md) {
24
- return true;
25
- }
26
- }
27
- return false;
28
- }, [domSize, theme]);
29
-
30
- return (
31
- <Stack className="cko-header" direction="row" spacing={1} alignItems="center" justifyContent="space-between">
32
- <Stack direction="row" spacing={1} alignItems="center">
33
- <Avatar variant="square" src={window.blocklet.appLogo} sx={{ width: 32, height: 32 }} />
34
- <Typography sx={{ color: 'text.primary', fontWeight: 600 }}>{brand}</Typography>
35
- {!livemode && <Livemode />}
36
- </Stack>
37
- {isCloumnLayout ? <UserButtons /> : null}
38
- </Stack>
39
- );
40
- }
@@ -1,385 +0,0 @@
1
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import Toast from '@arcblock/ux/lib/Toast';
3
- import type {
4
- TCheckoutSessionExpanded,
5
- TCustomer,
6
- TPaymentCurrency,
7
- TPaymentIntent,
8
- TPaymentLink,
9
- TPaymentMethodExpanded,
10
- } from '@did-pay/types';
11
- import { Box, Fade, Stack } from '@mui/material';
12
- import { styled } from '@mui/system';
13
- import { useSetState } from 'ahooks';
14
- import { useEffect } from 'react';
15
- import { FormProvider, useForm } from 'react-hook-form';
16
-
17
- import { useSessionContext } from '../../contexts/session';
18
- import { useSettingsContext } from '../../contexts/settings';
19
- import api from '../../libs/api';
20
- import { findCurrency, formatError, getStatementDescriptor, isValidCountry } from '../../libs/util';
21
- import PaymentError from './error';
22
- import CheckoutFooter from './footer';
23
- import PaymentForm from './form';
24
- import PaymentHeader from './header';
25
- import OverviewSkeleton from './skeleton/overview';
26
- import PaymentSkeleton from './skeleton/payment';
27
- import PaymentSuccess from './success';
28
- import PaymentSummary from './summary';
29
-
30
- type Props = {
31
- onPaid: (res: any, paymentIntent: TPaymentIntent) => void;
32
- onError: (err: any) => void;
33
- checkoutSession?: TCheckoutSessionExpanded;
34
- paymentMethods?: TPaymentMethodExpanded[];
35
- paymentLink?: TPaymentLink;
36
- paymentIntent?: TPaymentIntent;
37
- customer?: TCustomer;
38
- completed?: boolean;
39
- error?: any;
40
- };
41
-
42
- CheckoutPay.defaultProps = {
43
- checkoutSession: null,
44
- paymentMethods: [],
45
- paymentLink: null,
46
- paymentIntent: null,
47
- customer: null,
48
- completed: false,
49
- error: null,
50
- };
51
-
52
- export default function CheckoutPay({
53
- checkoutSession,
54
- paymentMethods,
55
- paymentIntent,
56
- paymentLink,
57
- customer,
58
- completed,
59
- error,
60
- onPaid,
61
- onError,
62
- }: Props) {
63
- const { t } = useLocaleContext();
64
- const { refresh, livemode, setLivemode } = useSettingsContext();
65
-
66
- useEffect(() => {
67
- if (checkoutSession) {
68
- if (livemode !== checkoutSession.livemode) {
69
- setLivemode(checkoutSession.livemode);
70
- setTimeout(() => {
71
- refresh();
72
- }, 10);
73
- }
74
- }
75
- }, [checkoutSession, livemode, setLivemode, refresh]);
76
-
77
- if (error) {
78
- return <PaymentError title="Oops" description={formatError(error)} />;
79
- }
80
-
81
- if (!checkoutSession) {
82
- return (
83
- <PaymentRoot>
84
- <Stack className="cko-container">
85
- <Stack className="cko-overview">
86
- <OverviewSkeleton />
87
- </Stack>
88
- <Stack className="cko-payment">
89
- <PaymentSkeleton />
90
- </Stack>
91
- <CheckoutFooter className="cko-footer" />
92
- </Stack>
93
- </PaymentRoot>
94
- );
95
- }
96
-
97
- // expired session
98
- if (checkoutSession.expires_at <= Math.round(Date.now() / 1000)) {
99
- return <PaymentError title={t('checkout.expired.title')} description={t('checkout.expired.description')} />;
100
- }
101
-
102
- // completed session
103
- if (checkoutSession.status === 'complete') {
104
- return <PaymentError title={t('checkout.complete.title')} description={t('checkout.complete.description')} />;
105
- }
106
-
107
- return (
108
- <CheckoutPayMain
109
- checkoutSession={checkoutSession}
110
- paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
111
- paymentLink={paymentLink}
112
- paymentIntent={paymentIntent}
113
- completed={completed}
114
- customer={customer as TCustomer}
115
- onPaid={onPaid}
116
- onError={onError}
117
- />
118
- );
119
- }
120
-
121
- type MainProps = {
122
- onPaid: Function;
123
- onError: Function;
124
- checkoutSession: TCheckoutSessionExpanded;
125
- paymentMethods: TPaymentMethodExpanded[];
126
- paymentLink?: TPaymentLink;
127
- paymentIntent?: TPaymentIntent;
128
- customer: TCustomer;
129
- completed?: boolean;
130
- };
131
-
132
- CheckoutPayMain.defaultProps = {
133
- paymentLink: null,
134
- paymentIntent: null,
135
- completed: false,
136
- };
137
-
138
- export function CheckoutPayMain({
139
- checkoutSession,
140
- paymentMethods,
141
- paymentLink,
142
- paymentIntent,
143
- customer,
144
- completed,
145
- onPaid,
146
- onError,
147
- }: MainProps) {
148
- const { t } = useLocaleContext();
149
- const { session } = useSessionContext();
150
- const { settings } = useSettingsContext();
151
- const [state, setState] = useSetState({ checkoutSession });
152
-
153
- const defaultCurrencyId = state.checkoutSession.currency_id || state.checkoutSession.line_items[0]?.price.currency_id;
154
- const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
155
-
156
- const methods = useForm({
157
- defaultValues: {
158
- customer_name: customer?.name || session.user?.fullName || '',
159
- customer_email: customer?.email || session.user?.email || '',
160
- customer_phone: customer?.phone || session.user?.phone || '',
161
- payment_method: defaultMethodId,
162
- payment_currency: defaultCurrencyId,
163
- billing_address: Object.assign(
164
- {
165
- country: '',
166
- state: '',
167
- city: '',
168
- line1: '',
169
- line2: '',
170
- postal_code: '',
171
- },
172
- customer?.address || {},
173
- { country: isValidCountry(customer?.address?.country || '') ? customer?.address?.country : 'us' }
174
- ),
175
- },
176
- });
177
-
178
- const currencyId = methods.watch('payment_currency') as string;
179
- const currency =
180
- (findCurrency(paymentMethods as TPaymentMethodExpanded[], currencyId as string) as TPaymentCurrency) ||
181
- settings.baseCurrency;
182
-
183
- const onUpsell = async (from: string, to: string) => {
184
- try {
185
- const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/upsell`, { from, to });
186
- setState({ checkoutSession: data });
187
- } catch (err) {
188
- console.error(err);
189
- Toast.error(formatError(err));
190
- }
191
- };
192
-
193
- const onDownsell = async (from: string) => {
194
- try {
195
- const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/downsell`, { from });
196
- setState({ checkoutSession: data });
197
- } catch (err) {
198
- console.error(err);
199
- Toast.error(formatError(err));
200
- }
201
- };
202
-
203
- const onApplyCrossSell = async (to: string) => {
204
- try {
205
- const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`, { to });
206
- setState({ checkoutSession: data });
207
- } catch (err) {
208
- console.error(err);
209
- Toast.error(formatError(err));
210
- }
211
- };
212
-
213
- const onCancelCrossSell = async () => {
214
- try {
215
- const { data } = await api.delete(`/api/checkout-sessions/${state.checkoutSession.id}/cross-sell`);
216
- setState({ checkoutSession: data });
217
- } catch (err) {
218
- console.error(err);
219
- Toast.error(formatError(err));
220
- }
221
- };
222
-
223
- return (
224
- <FormProvider {...methods}>
225
- <PaymentRoot>
226
- <Stack className="cko-container">
227
- <Fade in>
228
- <Stack className="cko-overview" direction="column">
229
- <PaymentHeader checkoutSession={state.checkoutSession} />
230
- <PaymentSummary
231
- checkoutSession={state.checkoutSession}
232
- currency={currency}
233
- onUpsell={onUpsell}
234
- onDownsell={onDownsell}
235
- onApplyCrossSell={onApplyCrossSell}
236
- onCancelCrossSell={onCancelCrossSell}
237
- />
238
- </Stack>
239
- </Fade>
240
- <Stack className="cko-payment" direction="column" spacing={4}>
241
- {completed && (
242
- <PaymentSuccess
243
- payee={getStatementDescriptor(state.checkoutSession.line_items)}
244
- action={state.checkoutSession.mode}
245
- message={
246
- paymentLink?.after_completion?.hosted_confirmation?.custom_message ||
247
- t(`checkout.completed.${state.checkoutSession.mode}`)
248
- }
249
- />
250
- )}
251
- {!completed && (
252
- <PaymentForm
253
- checkoutSession={state.checkoutSession}
254
- paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
255
- paymentIntent={paymentIntent}
256
- customer={customer}
257
- onPaid={onPaid}
258
- onError={onError}
259
- />
260
- )}
261
- </Stack>
262
- <CheckoutFooter className="cko-footer" />
263
- </Stack>
264
- </PaymentRoot>
265
- </FormProvider>
266
- );
267
- }
268
-
269
- export const PaymentRoot = styled(Box)`
270
- box-sizing: border-box;
271
- display: flex;
272
- flex-direction: column;
273
- justify-content: center;
274
- align-items: center;
275
- min-height: 100vh;
276
- position: relative;
277
-
278
- &:before {
279
- animation-fill-mode: both;
280
- background: #ffffff;
281
- content: '';
282
- height: 100%;
283
- position: fixed;
284
- right: 0;
285
- top: 0;
286
- transform-origin: right;
287
- width: 50%;
288
- box-shadow: 15px 0 30px 0 rgba(0, 0, 0, 0.18);
289
- }
290
-
291
- .cko-container {
292
- width: 100%;
293
- max-width: 1000px;
294
- display: flex;
295
- flex-direction: row;
296
- justify-content: space-between;
297
- position: relative;
298
- padding: 0 16px;
299
- }
300
-
301
- .cko-overview {
302
- width: 400px;
303
- min-height: 540px;
304
- position: relative;
305
- }
306
- .cko-header {
307
- left: 0;
308
- margin-bottom: 0;
309
- position: absolute;
310
- right: 0;
311
- top: 0;
312
- transition: background-color 0.15s ease, box-shadow 0.15s ease-out;
313
- }
314
-
315
- .cko-payment {
316
- width: 400px;
317
- .MuiInputBase-root {
318
- border-radius: 0;
319
- }
320
- }
321
-
322
- .cko-payment-form {
323
- .MuiInputAdornment-positionStart {
324
- width: 50px;
325
- }
326
-
327
- .MuiBox-root {
328
- border: 1px solid #ccc;
329
- margin: -1px 0 0 -1px;
330
- }
331
-
332
- .MuiFormHelperText-root {
333
- margin-left: 14px;
334
- }
335
-
336
- .MuiOutlinedInput-notchedOutline {
337
- border: none;
338
- }
339
- }
340
-
341
- .cko-payment-methods {
342
- }
343
-
344
- .cko-payment-submit {
345
- .MuiButtonBase-root {
346
- border-radius: 0;
347
- font-size: 1.3rem;
348
- }
349
- }
350
-
351
- .cko-header {
352
- }
353
-
354
- .cko-footer {
355
- position: absolute;
356
- bottom: 0;
357
- left: 12px;
358
- margin: 12px 0;
359
- }
360
-
361
- @media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
362
- &:before {
363
- display: none;
364
- }
365
- .cko-container {
366
- flex-direction: column;
367
- align-items: center;
368
- gap: 24px;
369
- min-width: 350px;
370
- max-width: 400px;
371
- }
372
- .cko-overview {
373
- width: 100%;
374
- min-height: auto;
375
- }
376
- .cko-payment {
377
- width: 100%;
378
- }
379
-
380
- .cko-footer {
381
- position: static;
382
- margin-top: 0;
383
- }
384
- }
385
- `;
@@ -1,205 +0,0 @@
1
- /* eslint-disable no-nested-ternary */
2
- import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import Toast from '@arcblock/ux/lib/Toast';
4
- import type { PriceRecurring, TPricingTableExpanded, TPricingTableItem } from '@did-pay/types';
5
- import { CheckOutlined } from '@mui/icons-material';
6
- import { LoadingButton } from '@mui/lab';
7
- import {
8
- Box,
9
- Chip,
10
- Fade,
11
- List,
12
- ListItem,
13
- ListItemIcon,
14
- ListItemText,
15
- Stack,
16
- ToggleButton,
17
- ToggleButtonGroup,
18
- Typography,
19
- } from '@mui/material';
20
- import { useSetState } from 'ahooks';
21
- import { useEffect } from 'react';
22
-
23
- import { formatError, formatPriceAmount, formatRecurring } from '../../libs/util';
24
- import PaymentAmount from './amount';
25
-
26
- const groupItemsByRecurring = (items: TPricingTableItem[]) => {
27
- const grouped: { [key: string]: TPricingTableItem[] } = {};
28
- const recurring: { [key: string]: PriceRecurring } = {};
29
-
30
- items.forEach((x) => {
31
- const key = [x.price.recurring?.interval, x.price.recurring?.interval_count].join('-');
32
- recurring[key] = x.price.recurring as PriceRecurring;
33
-
34
- if (!grouped[key]) {
35
- grouped[key] = [];
36
- }
37
-
38
- // @ts-ignore
39
- grouped[key].push(x);
40
- });
41
-
42
- return { recurring, grouped };
43
- };
44
-
45
- type Props = {
46
- table: TPricingTableExpanded;
47
- onSelect: (priceId: string) => void;
48
- alignItems?: 'center' | 'left';
49
- mode?: 'checkout' | 'select';
50
- interval?: string;
51
- };
52
-
53
- PricingTable.defaultProps = {
54
- alignItems: 'center',
55
- mode: 'checkout',
56
- interval: '',
57
- };
58
-
59
- export default function PricingTable({ table, alignItems, interval, mode, onSelect }: Props) {
60
- const { t, locale } = useLocaleContext();
61
- const [state, setState] = useSetState({ interval, loading: '' });
62
- const { recurring, grouped } = groupItemsByRecurring(table.items);
63
-
64
- useEffect(() => {
65
- if (table) {
66
- if (!state.interval || !grouped[state.interval]) {
67
- const keys = Object.keys(recurring);
68
- if (keys[0]) {
69
- setState({ interval: keys[0] });
70
- }
71
- }
72
- }
73
- // eslint-disable-next-line react-hooks/exhaustive-deps
74
- }, [table]);
75
-
76
- const handleSelect = async (priceId: string) => {
77
- try {
78
- setState({ loading: priceId });
79
- await onSelect(priceId);
80
- } catch (err) {
81
- console.error(err);
82
- Toast.error(formatError(err));
83
- } finally {
84
- setState({ loading: '' });
85
- }
86
- };
87
-
88
- return (
89
- <Stack
90
- direction="column"
91
- alignItems={alignItems === 'center' ? 'center' : 'flex-start'}
92
- sx={{
93
- pt: {
94
- xs: 4,
95
- sm: 2,
96
- },
97
- gap: {
98
- xs: 3,
99
- sm: mode === 'select' ? 3 : 5,
100
- },
101
- }}>
102
- {Object.keys(recurring).length > 1 && (
103
- <ToggleButtonGroup
104
- color="primary"
105
- value={state.interval}
106
- onChange={(_, value) => {
107
- if (value !== null) {
108
- setState({ interval: value });
109
- }
110
- }}
111
- exclusive>
112
- {Object.keys(recurring).map((x) => (
113
- <ToggleButton key={x} value={x} sx={{ textTransform: 'capitalize' }}>
114
- {formatRecurring(recurring[x] as PriceRecurring, true, '', locale)}
115
- </ToggleButton>
116
- ))}
117
- </ToggleButtonGroup>
118
- )}
119
- <Stack
120
- flexWrap="wrap"
121
- direction="row"
122
- gap={{ xs: 3, sm: 5, md: mode === 'checkout' ? 10 : 5 }}
123
- justifyContent={alignItems === 'center' ? 'center' : 'flex-start'}>
124
- {grouped[state.interval as string]?.map((x: TPricingTableItem) => {
125
- let action = x.subscription_data?.trial_period_days ? t('checkout.try') : t('checkout.subscription');
126
- if (mode === 'select') {
127
- action = x.is_selected ? t('checkout.selected') : t('checkout.select');
128
- }
129
-
130
- return (
131
- <Fade key={x.price_id} in>
132
- <Stack
133
- padding={4}
134
- spacing={2}
135
- direction="column"
136
- alignItems="center"
137
- sx={{
138
- width: 320,
139
- cursor: 'pointer',
140
- borderWidth: '1px',
141
- borderStyle: 'solid',
142
- borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#eee',
143
- borderRadius: 1,
144
- transition: 'border-color 0.3s ease 0s, box-shadow 0.3s ease 0s',
145
- boxShadow: '0 4px 8px rgba(0, 0, 0, 20%)',
146
- '&:hover': {
147
- borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#ddd',
148
- boxShadow: '0 8px 16px rgba(0, 0, 0, 20%)',
149
- },
150
- }}>
151
- <Box textAlign="center">
152
- <Stack direction="row" alignItems="center" spacing={1}>
153
- <Typography variant="h5" color="text.primary" fontWeight={600}>
154
- {x.product.name}
155
- </Typography>
156
- {x.is_highlight && <Chip label={x.highlight_text} color="default" size="small" />}
157
- </Stack>
158
- <Typography color="text.secondary">{x.product.description}</Typography>
159
- </Box>
160
- <Stack direction="row" alignItems="center" spacing={1}>
161
- <PaymentAmount amount={formatPriceAmount(x.price, table.currency, x.product.unit_label)} />
162
- <Stack direction="column" alignItems="flex-start">
163
- <Typography component="span" color="text.secondary" fontSize="0.8rem">
164
- {t('checkout.per')}
165
- </Typography>
166
- <Typography component="span" color="text.secondary" fontSize="0.8rem">
167
- {formatRecurring(x.price.recurring as PriceRecurring, false, '', locale)}
168
- </Typography>
169
- </Stack>
170
- </Stack>
171
- <LoadingButton
172
- fullWidth
173
- size="large"
174
- loadingPosition="end"
175
- variant={x.is_highlight || x.is_selected ? 'contained' : 'outlined'}
176
- color={x.is_highlight || x.is_selected ? 'primary' : 'info'}
177
- sx={{ fontSize: '1.2rem' }}
178
- loading={state.loading === x.price_id}
179
- disabled={x.is_disabled}
180
- onClick={() => handleSelect(x.price_id)}>
181
- {action}
182
- </LoadingButton>
183
- {x.product.features.length > 0 && (
184
- <Box>
185
- <Typography>{t('checkout.include')}</Typography>
186
- <List dense>
187
- {x.product.features.map((f: any) => (
188
- <ListItem key={f.name} disableGutters disablePadding>
189
- <ListItemIcon sx={{ minWidth: 25 }}>
190
- <CheckOutlined color="success" fontSize="small" />
191
- </ListItemIcon>
192
- <ListItemText primary={f.name} />
193
- </ListItem>
194
- ))}
195
- </List>
196
- </Box>
197
- )}
198
- </Stack>
199
- </Fade>
200
- );
201
- })}
202
- </Stack>
203
- </Stack>
204
- );
205
- }
@@ -1,52 +0,0 @@
1
- import { Avatar, Stack, Typography } from '@mui/material';
2
- import type { LiteralUnion } from 'type-fest';
3
-
4
- type Props = {
5
- name: string;
6
- description?: string;
7
- logo?: string;
8
- size?: number;
9
- extra?: React.ReactNode;
10
- variant?: LiteralUnion<'square' | 'rounded' | 'circular', string>;
11
- };
12
-
13
- // FIXME: @wangshijun add image filter for logo
14
- export default function ProductCard({ size, variant, name, logo, description, extra }: Props) {
15
- const s = { width: size, height: size };
16
- return (
17
- <Stack direction="row" alignItems="flex-start" spacing={1} flex={2}>
18
- {logo ? (
19
- // @ts-ignore
20
- <Avatar src={logo} alt={name} variant={variant} sx={s} />
21
- ) : (
22
- // @ts-ignore
23
- <Avatar variant={variant} sx={s}>
24
- {name.slice(0, 1)}
25
- </Avatar>
26
- )}
27
- <Stack direction="column" alignItems="flex-start" justifyContent="space-around">
28
- <Typography variant="body1" sx={{ fontWeight: 500, mb: 0.5, lineHeight: 1 }} color="text.primary">
29
- {name}
30
- </Typography>
31
- {description && (
32
- <Typography variant="body1" sx={{ fontSize: '0.85rem', mb: 0.5, lineHeight: 1 }} color="text.secondary">
33
- {description}
34
- </Typography>
35
- )}
36
- {extra && (
37
- <Typography variant="body1" sx={{ fontSize: '0.85rem' }} color="text.secondary">
38
- {extra}
39
- </Typography>
40
- )}
41
- </Stack>
42
- </Stack>
43
- );
44
- }
45
-
46
- ProductCard.defaultProps = {
47
- logo: '',
48
- size: 48,
49
- description: '',
50
- variant: 'rounded',
51
- extra: undefined,
52
- };