@blocklet/payment-react 1.13.287 → 1.13.289
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.
- package/es/components/pricing-table.js +146 -130
- package/es/locales/en.js +2 -1
- package/es/locales/zh.js +2 -1
- package/lib/components/pricing-table.js +23 -11
- package/lib/locales/en.js +2 -1
- package/lib/locales/zh.js +2 -1
- package/package.json +3 -3
- package/src/components/pricing-table.tsx +139 -125
- package/src/locales/en.tsx +1 -0
- package/src/locales/zh.tsx +1 -0
|
@@ -20,15 +20,24 @@ import {
|
|
|
20
20
|
import { styled } from "@mui/system";
|
|
21
21
|
import { useSetState } from "ahooks";
|
|
22
22
|
import { useEffect, useMemo, useState } from "react";
|
|
23
|
+
import { BN } from "@ocap/util";
|
|
23
24
|
import { usePaymentContext } from "../contexts/payment.js";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
formatError,
|
|
27
|
+
formatPriceAmount,
|
|
28
|
+
formatRecurring,
|
|
29
|
+
getPriceCurrencyOptions,
|
|
30
|
+
getPriceUintAmountByCurrency
|
|
31
|
+
} from "../libs/util.js";
|
|
25
32
|
import Amount from "../payment/amount.js";
|
|
26
|
-
const groupItemsByRecurring = (items) => {
|
|
33
|
+
const groupItemsByRecurring = (items, currency) => {
|
|
27
34
|
const grouped = {};
|
|
28
35
|
const recurring = {};
|
|
29
36
|
items.forEach((x) => {
|
|
30
37
|
const key = [x.price.recurring?.interval, x.price.recurring?.interval_count].join("-");
|
|
31
|
-
|
|
38
|
+
if (x.price.currency_options.find((c) => c.currency_id === currency.id)) {
|
|
39
|
+
recurring[key] = x.price.recurring;
|
|
40
|
+
}
|
|
32
41
|
if (!grouped[key]) {
|
|
33
42
|
grouped[key] = [];
|
|
34
43
|
}
|
|
@@ -47,7 +56,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
47
56
|
settings: { paymentMethods = [] }
|
|
48
57
|
} = usePaymentContext();
|
|
49
58
|
const [currency, setCurrency] = useState(table.currency || {});
|
|
50
|
-
const { recurring, grouped } = groupItemsByRecurring(table.items);
|
|
59
|
+
const { recurring, grouped } = useMemo(() => groupItemsByRecurring(table.items, currency), [table.items, currency]);
|
|
51
60
|
const [state, setState] = useSetState({ interval });
|
|
52
61
|
const currencyMap = useMemo(() => {
|
|
53
62
|
if (!paymentMethods || paymentMethods.length === 0) {
|
|
@@ -76,6 +85,15 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
76
85
|
});
|
|
77
86
|
return Object.keys(visited).map((x) => currencyMap[x]).filter((v) => v);
|
|
78
87
|
}, [currencyMap, grouped, state.interval]);
|
|
88
|
+
const productList = useMemo(() => {
|
|
89
|
+
return (grouped[state.interval] || []).filter((x) => {
|
|
90
|
+
const price = getPriceUintAmountByCurrency(x.price, currency);
|
|
91
|
+
if (new BN(price).isZero() || !price) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
}, [grouped, state.interval, currency]);
|
|
79
97
|
useEffect(() => {
|
|
80
98
|
if (table) {
|
|
81
99
|
if (!state.interval || !grouped[state.interval]) {
|
|
@@ -128,7 +146,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
128
146
|
},
|
|
129
147
|
children: [
|
|
130
148
|
/* @__PURE__ */ jsxs("div", { className: "btn-row", children: [
|
|
131
|
-
Object.keys(recurring).length >
|
|
149
|
+
Object.keys(recurring).length > 0 && /* @__PURE__ */ jsx(
|
|
132
150
|
ToggleButtonGroup,
|
|
133
151
|
{
|
|
134
152
|
size: "small",
|
|
@@ -169,7 +187,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
169
187
|
))
|
|
170
188
|
}
|
|
171
189
|
),
|
|
172
|
-
currencyList.length >
|
|
190
|
+
currencyList.length > 0 && /* @__PURE__ */ jsx(
|
|
173
191
|
Select,
|
|
174
192
|
{
|
|
175
193
|
value: currency?.id,
|
|
@@ -188,147 +206,145 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
188
206
|
gap: "20px",
|
|
189
207
|
justifyContent: alignItems === "center" ? "center" : "flex-start",
|
|
190
208
|
className: "price-table-wrap",
|
|
191
|
-
children:
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
boxShadow: "0 8px 16px rgba(0, 0, 0, 20%)"
|
|
217
|
-
},
|
|
218
|
-
width: "320px",
|
|
219
|
-
maxWidth: "360px",
|
|
220
|
-
padding: "20px",
|
|
221
|
-
position: "relative"
|
|
209
|
+
children: productList?.map((x) => {
|
|
210
|
+
let action = x.subscription_data?.trial_period_days ? t("payment.checkout.try") : t("payment.checkout.subscription");
|
|
211
|
+
if (mode === "select") {
|
|
212
|
+
action = x.is_selected ? t("payment.checkout.selected") : t("payment.checkout.select");
|
|
213
|
+
}
|
|
214
|
+
return /* @__PURE__ */ jsxs(
|
|
215
|
+
Stack,
|
|
216
|
+
{
|
|
217
|
+
padding: 4,
|
|
218
|
+
spacing: 2,
|
|
219
|
+
direction: "column",
|
|
220
|
+
alignItems: "flex-start",
|
|
221
|
+
className: "price-table-item",
|
|
222
|
+
justifyContent: "flex-start",
|
|
223
|
+
sx: {
|
|
224
|
+
cursor: "pointer",
|
|
225
|
+
borderWidth: "1px",
|
|
226
|
+
borderStyle: "solid",
|
|
227
|
+
borderColor: mode === "select" && x.is_selected ? "primary.main" : "#eee",
|
|
228
|
+
borderRadius: 2,
|
|
229
|
+
transition: "border-color 0.3s ease 0s, box-shadow 0.3s ease 0s",
|
|
230
|
+
boxShadow: "0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 1px 2px -1px rgba(3, 7, 18, 0.08), 0px 2px 4px rgba(3, 7, 18, 0.04)",
|
|
231
|
+
"&:hover": {
|
|
232
|
+
borderColor: mode === "select" && x.is_selected ? "primary.main" : "#ddd",
|
|
233
|
+
boxShadow: "0 8px 16px rgba(0, 0, 0, 20%)"
|
|
222
234
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
sx: {
|
|
240
|
-
color: "#4B5563",
|
|
241
|
-
fontSize: "18px !important",
|
|
242
|
-
fontWeight: "600"
|
|
243
|
-
},
|
|
244
|
-
children: x.product.name
|
|
245
|
-
}
|
|
246
|
-
),
|
|
247
|
-
x.is_highlight && /* @__PURE__ */ jsx(
|
|
248
|
-
Chip,
|
|
249
|
-
{
|
|
250
|
-
label: x.highlight_text,
|
|
251
|
-
color: "default",
|
|
252
|
-
size: "small",
|
|
253
|
-
sx: {
|
|
254
|
-
position: "absolute",
|
|
255
|
-
top: "20px",
|
|
256
|
-
right: "20px"
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
)
|
|
260
|
-
] }),
|
|
235
|
+
width: "320px",
|
|
236
|
+
maxWidth: "360px",
|
|
237
|
+
padding: "20px",
|
|
238
|
+
position: "relative"
|
|
239
|
+
},
|
|
240
|
+
children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Box, { textAlign: "center", children: /* @__PURE__ */ jsxs(
|
|
242
|
+
Stack,
|
|
243
|
+
{
|
|
244
|
+
direction: "column",
|
|
245
|
+
justifyContent: "center",
|
|
246
|
+
alignItems: "flex-start",
|
|
247
|
+
spacing: 1,
|
|
248
|
+
sx: { gap: "12px" },
|
|
249
|
+
children: [
|
|
250
|
+
/* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
|
|
261
251
|
/* @__PURE__ */ jsx(
|
|
262
|
-
|
|
252
|
+
Typography,
|
|
263
253
|
{
|
|
264
|
-
|
|
265
|
-
|
|
254
|
+
color: "text.primary",
|
|
255
|
+
fontWeight: 600,
|
|
256
|
+
sx: {
|
|
257
|
+
color: "#4B5563",
|
|
258
|
+
fontSize: "18px !important",
|
|
259
|
+
fontWeight: "600"
|
|
260
|
+
},
|
|
261
|
+
children: x.product.name
|
|
266
262
|
}
|
|
267
263
|
),
|
|
268
|
-
/* @__PURE__ */ jsx(
|
|
269
|
-
|
|
264
|
+
x.is_highlight && /* @__PURE__ */ jsx(
|
|
265
|
+
Chip,
|
|
270
266
|
{
|
|
271
|
-
|
|
267
|
+
label: x.highlight_text,
|
|
268
|
+
color: "default",
|
|
269
|
+
size: "small",
|
|
272
270
|
sx: {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
textAlign: "left"
|
|
278
|
-
},
|
|
279
|
-
children: x.product.description
|
|
271
|
+
position: "absolute",
|
|
272
|
+
top: "20px",
|
|
273
|
+
right: "20px"
|
|
274
|
+
}
|
|
280
275
|
}
|
|
281
276
|
)
|
|
282
|
-
]
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
x.product.features.length > 0 && /* @__PURE__ */ jsx(Box, { sx: { width: "100%" }, children: /* @__PURE__ */ jsxs(List, { dense: true, sx: { display: "flex", flexDirection: "column", gap: "16px", padding: "0px" }, children: [
|
|
286
|
-
/* @__PURE__ */ jsx(
|
|
287
|
-
Box,
|
|
288
|
-
{
|
|
289
|
-
sx: {
|
|
290
|
-
width: "100%",
|
|
291
|
-
position: "relative",
|
|
292
|
-
borderTop: "1px solid #e5e7eb",
|
|
293
|
-
boxSizing: "border-box",
|
|
294
|
-
height: "1px"
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
),
|
|
298
|
-
x.product.features.map((f) => /* @__PURE__ */ jsxs(ListItem, { disableGutters: true, disablePadding: true, sx: { fontSize: "16px !important" }, children: [
|
|
299
|
-
/* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 25, color: "#059669", fontSize: "64px" }, children: /* @__PURE__ */ jsx(
|
|
300
|
-
CheckOutlined,
|
|
277
|
+
] }),
|
|
278
|
+
/* @__PURE__ */ jsx(
|
|
279
|
+
Amount,
|
|
301
280
|
{
|
|
302
|
-
|
|
303
|
-
fontSize: "
|
|
304
|
-
sx: {
|
|
305
|
-
color: "#059669",
|
|
306
|
-
fontSize: "18px"
|
|
307
|
-
}
|
|
281
|
+
amount: formatPriceAmount(x.price, currency, x.product.unit_label),
|
|
282
|
+
sx: { my: 0, marginTop: "0px !important", fontSize: "48px", fontWeight: "bold" }
|
|
308
283
|
}
|
|
309
|
-
)
|
|
284
|
+
),
|
|
310
285
|
/* @__PURE__ */ jsx(
|
|
311
|
-
|
|
286
|
+
Typography,
|
|
312
287
|
{
|
|
288
|
+
color: "text.secondary",
|
|
313
289
|
sx: {
|
|
314
|
-
"
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
290
|
+
marginTop: "0px !important",
|
|
291
|
+
color: "#4b5563",
|
|
292
|
+
fontWeight: "400",
|
|
293
|
+
fontSize: "16px",
|
|
294
|
+
textAlign: "left"
|
|
319
295
|
},
|
|
320
|
-
|
|
296
|
+
children: x.product.description
|
|
321
297
|
}
|
|
322
298
|
)
|
|
323
|
-
]
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
) }),
|
|
302
|
+
x.product.features.length > 0 && /* @__PURE__ */ jsx(Box, { sx: { width: "100%" }, children: /* @__PURE__ */ jsxs(List, { dense: true, sx: { display: "flex", flexDirection: "column", gap: "16px", padding: "0px" }, children: [
|
|
303
|
+
/* @__PURE__ */ jsx(
|
|
304
|
+
Box,
|
|
305
|
+
{
|
|
306
|
+
sx: {
|
|
307
|
+
width: "100%",
|
|
308
|
+
position: "relative",
|
|
309
|
+
borderTop: "1px solid #e5e7eb",
|
|
310
|
+
boxSizing: "border-box",
|
|
311
|
+
height: "1px"
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
x.product.features.map((f) => /* @__PURE__ */ jsxs(ListItem, { disableGutters: true, disablePadding: true, sx: { fontSize: "16px !important" }, children: [
|
|
316
|
+
/* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 25, color: "#059669", fontSize: "64px" }, children: /* @__PURE__ */ jsx(
|
|
317
|
+
CheckOutlined,
|
|
318
|
+
{
|
|
319
|
+
color: "success",
|
|
320
|
+
fontSize: "small",
|
|
321
|
+
sx: {
|
|
322
|
+
color: "#059669",
|
|
323
|
+
fontSize: "18px"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
) }),
|
|
327
|
+
/* @__PURE__ */ jsx(
|
|
328
|
+
ListItemText,
|
|
329
|
+
{
|
|
330
|
+
sx: {
|
|
331
|
+
".MuiListItemText-primary": {
|
|
332
|
+
fontSize: "16px",
|
|
333
|
+
color: "#030712",
|
|
334
|
+
fontWeight: "500"
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
primary: f.name
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
] }, f.name))
|
|
341
|
+
] }) }),
|
|
342
|
+
/* @__PURE__ */ jsx(Subscribe, { x, action, onSelect, currencyId: currency?.id })
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
x?.price_id
|
|
346
|
+
);
|
|
347
|
+
})
|
|
332
348
|
}
|
|
333
349
|
)
|
|
334
350
|
]
|
package/es/locales/en.js
CHANGED
package/es/locales/zh.js
CHANGED
|
@@ -13,16 +13,19 @@ var _material = require("@mui/material");
|
|
|
13
13
|
var _system = require("@mui/system");
|
|
14
14
|
var _ahooks = require("ahooks");
|
|
15
15
|
var _react = require("react");
|
|
16
|
+
var _util = require("@ocap/util");
|
|
16
17
|
var _payment = require("../contexts/payment");
|
|
17
|
-
var
|
|
18
|
+
var _util2 = require("../libs/util");
|
|
18
19
|
var _amount = _interopRequireDefault(require("../payment/amount"));
|
|
19
20
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
-
const groupItemsByRecurring = items => {
|
|
21
|
+
const groupItemsByRecurring = (items, currency) => {
|
|
21
22
|
const grouped = {};
|
|
22
23
|
const recurring = {};
|
|
23
24
|
items.forEach(x => {
|
|
24
25
|
const key = [x.price.recurring?.interval, x.price.recurring?.interval_count].join("-");
|
|
25
|
-
|
|
26
|
+
if (x.price.currency_options.find(c => c.currency_id === currency.id)) {
|
|
27
|
+
recurring[key] = x.price.recurring;
|
|
28
|
+
}
|
|
26
29
|
if (!grouped[key]) {
|
|
27
30
|
grouped[key] = [];
|
|
28
31
|
}
|
|
@@ -58,7 +61,7 @@ function PricingTable({
|
|
|
58
61
|
const {
|
|
59
62
|
recurring,
|
|
60
63
|
grouped
|
|
61
|
-
} = groupItemsByRecurring(table.items);
|
|
64
|
+
} = (0, _react.useMemo)(() => groupItemsByRecurring(table.items, currency), [table.items, currency]);
|
|
62
65
|
const [state, setState] = (0, _ahooks.useSetState)({
|
|
63
66
|
interval
|
|
64
67
|
});
|
|
@@ -85,12 +88,21 @@ function PricingTable({
|
|
|
85
88
|
return [];
|
|
86
89
|
}
|
|
87
90
|
grouped[state.interval].forEach(x => {
|
|
88
|
-
(0,
|
|
91
|
+
(0, _util2.getPriceCurrencyOptions)(x.price).forEach(c => {
|
|
89
92
|
visited[c?.currency_id] = true;
|
|
90
93
|
});
|
|
91
94
|
});
|
|
92
95
|
return Object.keys(visited).map(x => currencyMap[x]).filter(v => v);
|
|
93
96
|
}, [currencyMap, grouped, state.interval]);
|
|
97
|
+
const productList = (0, _react.useMemo)(() => {
|
|
98
|
+
return (grouped[state.interval] || []).filter(x => {
|
|
99
|
+
const price = (0, _util2.getPriceUintAmountByCurrency)(x.price, currency);
|
|
100
|
+
if (new _util.BN(price).isZero() || !price) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
}, [grouped, state.interval, currency]);
|
|
94
106
|
(0, _react.useEffect)(() => {
|
|
95
107
|
if (table) {
|
|
96
108
|
if (!state.interval || !grouped[state.interval]) {
|
|
@@ -148,7 +160,7 @@ function PricingTable({
|
|
|
148
160
|
},
|
|
149
161
|
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)("div", {
|
|
150
162
|
className: "btn-row",
|
|
151
|
-
children: [Object.keys(recurring).length >
|
|
163
|
+
children: [Object.keys(recurring).length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.ToggleButtonGroup, {
|
|
152
164
|
size: "small",
|
|
153
165
|
value: state.interval,
|
|
154
166
|
sx: {
|
|
@@ -181,9 +193,9 @@ function PricingTable({
|
|
|
181
193
|
border: "1px solid #e5e7eb"
|
|
182
194
|
}
|
|
183
195
|
},
|
|
184
|
-
children: (0,
|
|
196
|
+
children: (0, _util2.formatRecurring)(recurring[x], true, "", locale)
|
|
185
197
|
}, x))
|
|
186
|
-
}), currencyList.length >
|
|
198
|
+
}), currencyList.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Select, {
|
|
187
199
|
value: currency?.id,
|
|
188
200
|
onChange: e => setCurrency(currencyList.find(v => v?.id === e.target.value)),
|
|
189
201
|
size: "small",
|
|
@@ -205,7 +217,7 @@ function PricingTable({
|
|
|
205
217
|
gap: "20px",
|
|
206
218
|
justifyContent: alignItems === "center" ? "center" : "flex-start",
|
|
207
219
|
className: "price-table-wrap",
|
|
208
|
-
children:
|
|
220
|
+
children: productList?.map(x => {
|
|
209
221
|
let action = x.subscription_data?.trial_period_days ? t("payment.checkout.try") : t("payment.checkout.subscription");
|
|
210
222
|
if (mode === "select") {
|
|
211
223
|
action = x.is_selected ? t("payment.checkout.selected") : t("payment.checkout.select");
|
|
@@ -270,7 +282,7 @@ function PricingTable({
|
|
|
270
282
|
}
|
|
271
283
|
})]
|
|
272
284
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_amount.default, {
|
|
273
|
-
amount: (0,
|
|
285
|
+
amount: (0, _util2.formatPriceAmount)(x.price, currency, x.product.unit_label),
|
|
274
286
|
sx: {
|
|
275
287
|
my: 0,
|
|
276
288
|
marginTop: "0px !important",
|
|
@@ -372,7 +384,7 @@ function Subscribe({
|
|
|
372
384
|
await onSelect(priceId, currencyId);
|
|
373
385
|
} catch (err) {
|
|
374
386
|
console.error(err);
|
|
375
|
-
_Toast.default.error((0,
|
|
387
|
+
_Toast.default.error((0, _util2.formatError)(err));
|
|
376
388
|
}
|
|
377
389
|
};
|
|
378
390
|
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_lab.LoadingButton, {
|
package/lib/locales/en.js
CHANGED
package/lib/locales/zh.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.289",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"@babel/core": "^7.24.7",
|
|
92
92
|
"@babel/preset-env": "^7.24.7",
|
|
93
93
|
"@babel/preset-react": "^7.24.7",
|
|
94
|
-
"@blocklet/payment-types": "1.13.
|
|
94
|
+
"@blocklet/payment-types": "1.13.289",
|
|
95
95
|
"@storybook/addon-essentials": "^7.6.19",
|
|
96
96
|
"@storybook/addon-interactions": "^7.6.19",
|
|
97
97
|
"@storybook/addon-links": "^7.6.19",
|
|
@@ -120,5 +120,5 @@
|
|
|
120
120
|
"vite-plugin-babel": "^1.2.0",
|
|
121
121
|
"vite-plugin-node-polyfills": "^0.21.0"
|
|
122
122
|
},
|
|
123
|
-
"gitHead": "
|
|
123
|
+
"gitHead": "23a533f8752c9ad24d199562ccbae166e992a713"
|
|
124
124
|
}
|
|
@@ -22,18 +22,26 @@ import { styled } from '@mui/system';
|
|
|
22
22
|
import { useSetState } from 'ahooks';
|
|
23
23
|
import { useEffect, useMemo, useState } from 'react';
|
|
24
24
|
|
|
25
|
+
import { BN } from '@ocap/util';
|
|
25
26
|
import { usePaymentContext } from '../contexts/payment';
|
|
26
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
formatError,
|
|
29
|
+
formatPriceAmount,
|
|
30
|
+
formatRecurring,
|
|
31
|
+
getPriceCurrencyOptions,
|
|
32
|
+
getPriceUintAmountByCurrency,
|
|
33
|
+
} from '../libs/util';
|
|
27
34
|
import Amount from '../payment/amount';
|
|
28
35
|
|
|
29
|
-
const groupItemsByRecurring = (items: TPricingTableItem[]) => {
|
|
36
|
+
const groupItemsByRecurring = (items: TPricingTableItem[], currency: { id: string; symbol: string }) => {
|
|
30
37
|
const grouped: { [key: string]: TPricingTableItem[] } = {};
|
|
31
38
|
const recurring: { [key: string]: PriceRecurring } = {};
|
|
32
39
|
|
|
33
40
|
items.forEach((x) => {
|
|
34
41
|
const key = [x.price.recurring?.interval, x.price.recurring?.interval_count].join('-');
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
if (x.price.currency_options.find((c: PriceCurrency) => c.currency_id === currency.id)) {
|
|
43
|
+
recurring[key] = x.price.recurring as PriceRecurring;
|
|
44
|
+
}
|
|
37
45
|
if (!grouped[key]) {
|
|
38
46
|
grouped[key] = [];
|
|
39
47
|
}
|
|
@@ -65,7 +73,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
65
73
|
settings: { paymentMethods = [] },
|
|
66
74
|
} = usePaymentContext();
|
|
67
75
|
const [currency, setCurrency] = useState(table.currency || {});
|
|
68
|
-
const { recurring, grouped } = groupItemsByRecurring(table.items);
|
|
76
|
+
const { recurring, grouped } = useMemo(() => groupItemsByRecurring(table.items, currency), [table.items, currency]);
|
|
69
77
|
const [state, setState] = useSetState({ interval });
|
|
70
78
|
const currencyMap = useMemo(() => {
|
|
71
79
|
if (!paymentMethods || paymentMethods.length === 0) {
|
|
@@ -97,6 +105,15 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
97
105
|
.map((x) => currencyMap[x])
|
|
98
106
|
.filter((v) => v);
|
|
99
107
|
}, [currencyMap, grouped, state.interval]);
|
|
108
|
+
const productList = useMemo(() => {
|
|
109
|
+
return (grouped[state.interval as string] || []).filter((x: TPricingTableItem) => {
|
|
110
|
+
const price = getPriceUintAmountByCurrency(x.price, currency);
|
|
111
|
+
if (new BN(price).isZero() || !price) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
}, [grouped, state.interval, currency]);
|
|
100
117
|
|
|
101
118
|
useEffect(() => {
|
|
102
119
|
if (table) {
|
|
@@ -151,7 +168,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
151
168
|
},
|
|
152
169
|
}}>
|
|
153
170
|
<div className="btn-row">
|
|
154
|
-
{Object.keys(recurring).length >
|
|
171
|
+
{Object.keys(recurring).length > 0 && (
|
|
155
172
|
<ToggleButtonGroup
|
|
156
173
|
size="small"
|
|
157
174
|
value={state.interval}
|
|
@@ -190,7 +207,7 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
190
207
|
))}
|
|
191
208
|
</ToggleButtonGroup>
|
|
192
209
|
)}
|
|
193
|
-
{currencyList.length >
|
|
210
|
+
{currencyList.length > 0 && (
|
|
194
211
|
<Select
|
|
195
212
|
value={currency?.id}
|
|
196
213
|
onChange={(e) => setCurrency(currencyList.find((v) => v?.id === e.target.value))}
|
|
@@ -212,135 +229,132 @@ export default function PricingTable({ table, alignItems, interval, mode, onSele
|
|
|
212
229
|
gap="20px"
|
|
213
230
|
justifyContent={alignItems === 'center' ? 'center' : 'flex-start'}
|
|
214
231
|
className="price-table-wrap">
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
'
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
width: '320px',
|
|
247
|
-
maxWidth: '360px',
|
|
232
|
+
{productList?.map((x: TPricingTableItem & { is_selected?: boolean; is_disabled?: boolean }) => {
|
|
233
|
+
let action: string = x.subscription_data?.trial_period_days
|
|
234
|
+
? t('payment.checkout.try')
|
|
235
|
+
: t('payment.checkout.subscription');
|
|
236
|
+
if (mode === 'select') {
|
|
237
|
+
action = x.is_selected ? t('payment.checkout.selected') : t('payment.checkout.select');
|
|
238
|
+
}
|
|
239
|
+
return (
|
|
240
|
+
<Stack
|
|
241
|
+
key={x?.price_id}
|
|
242
|
+
padding={4}
|
|
243
|
+
spacing={2}
|
|
244
|
+
direction="column"
|
|
245
|
+
alignItems="flex-start"
|
|
246
|
+
className="price-table-item"
|
|
247
|
+
justifyContent="flex-start"
|
|
248
|
+
sx={{
|
|
249
|
+
cursor: 'pointer',
|
|
250
|
+
borderWidth: '1px',
|
|
251
|
+
borderStyle: 'solid',
|
|
252
|
+
borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#eee',
|
|
253
|
+
borderRadius: 2,
|
|
254
|
+
transition: 'border-color 0.3s ease 0s, box-shadow 0.3s ease 0s',
|
|
255
|
+
boxShadow:
|
|
256
|
+
'0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 1px 2px -1px rgba(3, 7, 18, 0.08), 0px 2px 4px rgba(3, 7, 18, 0.04)',
|
|
257
|
+
'&:hover': {
|
|
258
|
+
borderColor: mode === 'select' && x.is_selected ? 'primary.main' : '#ddd',
|
|
259
|
+
boxShadow: '0 8px 16px rgba(0, 0, 0, 20%)',
|
|
260
|
+
},
|
|
261
|
+
width: '320px',
|
|
262
|
+
maxWidth: '360px',
|
|
248
263
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<Typography
|
|
261
|
-
color="text.primary"
|
|
262
|
-
fontWeight={600}
|
|
263
|
-
sx={{
|
|
264
|
-
color: '#4B5563',
|
|
265
|
-
fontSize: '18px !important',
|
|
266
|
-
fontWeight: '600',
|
|
267
|
-
}}>
|
|
268
|
-
{x.product.name}
|
|
269
|
-
</Typography>
|
|
270
|
-
{x.is_highlight && (
|
|
271
|
-
<Chip
|
|
272
|
-
label={x.highlight_text}
|
|
273
|
-
color="default"
|
|
274
|
-
size="small"
|
|
275
|
-
sx={{
|
|
276
|
-
position: 'absolute',
|
|
277
|
-
top: '20px',
|
|
278
|
-
right: '20px',
|
|
279
|
-
}}
|
|
280
|
-
/>
|
|
281
|
-
)}
|
|
282
|
-
</Box>
|
|
283
|
-
<Amount
|
|
284
|
-
amount={formatPriceAmount(x.price, currency, x.product.unit_label)}
|
|
285
|
-
sx={{ my: 0, marginTop: '0px !important', fontSize: '48px', fontWeight: 'bold' }}
|
|
286
|
-
/>
|
|
264
|
+
padding: '20px',
|
|
265
|
+
position: 'relative',
|
|
266
|
+
}}>
|
|
267
|
+
<Box textAlign="center">
|
|
268
|
+
<Stack
|
|
269
|
+
direction="column"
|
|
270
|
+
justifyContent="center"
|
|
271
|
+
alignItems="flex-start"
|
|
272
|
+
spacing={1}
|
|
273
|
+
sx={{ gap: '12px' }}>
|
|
274
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
287
275
|
<Typography
|
|
288
|
-
color="text.
|
|
276
|
+
color="text.primary"
|
|
277
|
+
fontWeight={600}
|
|
289
278
|
sx={{
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
fontWeight: '
|
|
293
|
-
fontSize: '16px',
|
|
294
|
-
textAlign: 'left',
|
|
279
|
+
color: '#4B5563',
|
|
280
|
+
fontSize: '18px !important',
|
|
281
|
+
fontWeight: '600',
|
|
295
282
|
}}>
|
|
296
|
-
{x.product.
|
|
283
|
+
{x.product.name}
|
|
297
284
|
</Typography>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
<Box
|
|
285
|
+
{x.is_highlight && (
|
|
286
|
+
<Chip
|
|
287
|
+
label={x.highlight_text}
|
|
288
|
+
color="default"
|
|
289
|
+
size="small"
|
|
304
290
|
sx={{
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
boxSizing: 'border-box',
|
|
309
|
-
height: '1px',
|
|
291
|
+
position: 'absolute',
|
|
292
|
+
top: '20px',
|
|
293
|
+
right: '20px',
|
|
310
294
|
}}
|
|
311
295
|
/>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
296
|
+
)}
|
|
297
|
+
</Box>
|
|
298
|
+
<Amount
|
|
299
|
+
amount={formatPriceAmount(x.price, currency, x.product.unit_label)}
|
|
300
|
+
sx={{ my: 0, marginTop: '0px !important', fontSize: '48px', fontWeight: 'bold' }}
|
|
301
|
+
/>
|
|
302
|
+
<Typography
|
|
303
|
+
color="text.secondary"
|
|
304
|
+
sx={{
|
|
305
|
+
marginTop: '0px !important',
|
|
306
|
+
color: '#4b5563',
|
|
307
|
+
fontWeight: '400',
|
|
308
|
+
fontSize: '16px',
|
|
309
|
+
textAlign: 'left',
|
|
310
|
+
}}>
|
|
311
|
+
{x.product.description}
|
|
312
|
+
</Typography>
|
|
313
|
+
</Stack>
|
|
314
|
+
</Box>
|
|
315
|
+
{x.product.features.length > 0 && (
|
|
316
|
+
<Box sx={{ width: '100%' }}>
|
|
317
|
+
<List dense sx={{ display: 'flex', flexDirection: 'column', gap: '16px', padding: '0px' }}>
|
|
318
|
+
<Box
|
|
319
|
+
sx={{
|
|
320
|
+
width: '100%',
|
|
321
|
+
position: 'relative',
|
|
322
|
+
borderTop: '1px solid #e5e7eb',
|
|
323
|
+
boxSizing: 'border-box',
|
|
324
|
+
height: '1px',
|
|
325
|
+
}}
|
|
326
|
+
/>
|
|
327
|
+
{x.product.features.map((f: any) => (
|
|
328
|
+
<ListItem key={f.name} disableGutters disablePadding sx={{ fontSize: '16px !important' }}>
|
|
329
|
+
<ListItemIcon sx={{ minWidth: 25, color: '#059669', fontSize: '64px' }}>
|
|
330
|
+
<CheckOutlined
|
|
331
|
+
color="success"
|
|
332
|
+
fontSize="small"
|
|
325
333
|
sx={{
|
|
326
|
-
'
|
|
327
|
-
|
|
328
|
-
color: '#030712',
|
|
329
|
-
fontWeight: '500',
|
|
330
|
-
},
|
|
334
|
+
color: '#059669',
|
|
335
|
+
fontSize: '18px',
|
|
331
336
|
}}
|
|
332
|
-
primary={f.name}
|
|
333
337
|
/>
|
|
334
|
-
</
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
338
|
+
</ListItemIcon>
|
|
339
|
+
<ListItemText
|
|
340
|
+
sx={{
|
|
341
|
+
'.MuiListItemText-primary': {
|
|
342
|
+
fontSize: '16px',
|
|
343
|
+
color: '#030712',
|
|
344
|
+
fontWeight: '500',
|
|
345
|
+
},
|
|
346
|
+
}}
|
|
347
|
+
primary={f.name}
|
|
348
|
+
/>
|
|
349
|
+
</ListItem>
|
|
350
|
+
))}
|
|
351
|
+
</List>
|
|
352
|
+
</Box>
|
|
353
|
+
)}
|
|
354
|
+
<Subscribe x={x} action={action} onSelect={onSelect} currencyId={currency?.id} />
|
|
355
|
+
</Stack>
|
|
356
|
+
);
|
|
357
|
+
})}
|
|
344
358
|
</Stack>
|
|
345
359
|
</Stack>
|
|
346
360
|
</Root>
|
package/src/locales/en.tsx
CHANGED