@blocklet/payment-react 1.18.24 → 1.18.26
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/checkout/donate.js +11 -1
- package/es/components/country-select.js +243 -21
- package/es/components/over-due-invoice-payment.d.ts +3 -1
- package/es/components/over-due-invoice-payment.js +6 -4
- package/es/contexts/payment.d.ts +2 -1
- package/es/contexts/payment.js +8 -1
- package/es/hooks/keyboard.js +3 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/libs/api.js +4 -0
- package/es/libs/currency.d.ts +3 -0
- package/es/libs/currency.js +22 -0
- package/es/libs/phone-validator.js +2 -0
- package/es/libs/util.d.ts +2 -2
- package/es/libs/util.js +7 -4
- package/es/libs/validator.d.ts +1 -0
- package/es/libs/validator.js +70 -0
- package/es/payment/form/address.js +17 -3
- package/es/payment/form/index.js +10 -1
- package/es/payment/form/phone.js +12 -1
- package/es/payment/form/stripe/form.js +72 -15
- package/es/payment/index.js +33 -11
- package/es/payment/product-donation.js +110 -12
- package/es/types/shims.d.ts +2 -0
- package/lib/checkout/donate.js +11 -1
- package/lib/components/country-select.js +243 -39
- package/lib/components/over-due-invoice-payment.d.ts +3 -1
- package/lib/components/over-due-invoice-payment.js +7 -4
- package/lib/contexts/payment.d.ts +2 -1
- package/lib/contexts/payment.js +9 -1
- package/lib/hooks/keyboard.js +3 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +12 -0
- package/lib/libs/api.js +4 -0
- package/lib/libs/currency.d.ts +3 -0
- package/lib/libs/currency.js +31 -0
- package/lib/libs/phone-validator.js +1 -0
- package/lib/libs/util.d.ts +2 -2
- package/lib/libs/util.js +7 -4
- package/lib/libs/validator.d.ts +1 -0
- package/lib/libs/validator.js +20 -0
- package/lib/payment/form/address.js +15 -2
- package/lib/payment/form/index.js +12 -1
- package/lib/payment/form/phone.js +13 -1
- package/lib/payment/form/stripe/form.js +98 -29
- package/lib/payment/index.js +34 -10
- package/lib/payment/product-donation.js +106 -15
- package/lib/types/shims.d.ts +2 -0
- package/package.json +8 -8
- package/src/checkout/donate.tsx +11 -1
- package/src/components/country-select.tsx +265 -20
- package/src/components/over-due-invoice-payment.tsx +6 -2
- package/src/contexts/payment.tsx +11 -1
- package/src/hooks/keyboard.ts +5 -3
- package/src/index.ts +1 -0
- package/src/libs/api.ts +4 -1
- package/src/libs/currency.ts +25 -0
- package/src/libs/phone-validator.ts +1 -0
- package/src/libs/util.ts +18 -4
- package/src/libs/validator.ts +70 -0
- package/src/payment/form/address.tsx +17 -4
- package/src/payment/form/index.tsx +11 -1
- package/src/payment/form/phone.tsx +15 -1
- package/src/payment/form/stripe/form.tsx +104 -32
- package/src/payment/index.tsx +45 -14
- package/src/payment/product-donation.tsx +129 -10
- package/src/types/shims.d.ts +2 -0
package/es/payment/form/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { useMobile } from "../../hooks/mobile.js";
|
|
|
31
31
|
import { formatPhone, validatePhoneNumber } from "../../libs/phone-validator.js";
|
|
32
32
|
import LoadingButton from "../../components/loading-button.js";
|
|
33
33
|
import OverdueInvoicePayment from "../../components/over-due-invoice-payment.js";
|
|
34
|
+
import { saveCurrencyPreference } from "../../libs/currency.js";
|
|
34
35
|
export const waitForCheckoutComplete = async (sessionId) => {
|
|
35
36
|
let result;
|
|
36
37
|
await pWaitFor(
|
|
@@ -135,6 +136,13 @@ export default function PaymentForm({
|
|
|
135
136
|
const index = currencies.findIndex((x) => x.id === queryCurrencyId);
|
|
136
137
|
return index >= 0 ? index : 0;
|
|
137
138
|
});
|
|
139
|
+
const handleCurrencyChange = (index) => {
|
|
140
|
+
setPaymentCurrencyIndex(index);
|
|
141
|
+
const selectedCurrencyId = currencies[index]?.id;
|
|
142
|
+
if (selectedCurrencyId) {
|
|
143
|
+
saveCurrencyPreference(selectedCurrencyId, session?.user?.did);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
138
146
|
const onCheckoutComplete = useMemoizedFn(async ({ response }) => {
|
|
139
147
|
if (response.id === checkoutSession.id && state.paid === false) {
|
|
140
148
|
await handleConnected();
|
|
@@ -248,6 +256,7 @@ export default function PaymentForm({
|
|
|
248
256
|
const showForm = !!session?.user;
|
|
249
257
|
const skipBindWallet = method.type === "stripe";
|
|
250
258
|
const handleConnected = async () => {
|
|
259
|
+
setState({ paying: true });
|
|
251
260
|
try {
|
|
252
261
|
const result = await waitForCheckoutComplete(checkoutSession.id);
|
|
253
262
|
if (state.paid === false) {
|
|
@@ -485,7 +494,7 @@ export default function PaymentForm({
|
|
|
485
494
|
{
|
|
486
495
|
value: paymentCurrencyIndex,
|
|
487
496
|
currencies,
|
|
488
|
-
onChange:
|
|
497
|
+
onChange: handleCurrencyChange
|
|
489
498
|
}
|
|
490
499
|
)
|
|
491
500
|
}
|
package/es/payment/form/phone.js
CHANGED
|
@@ -4,12 +4,14 @@ import omit from "lodash/omit";
|
|
|
4
4
|
import { useEffect, useRef, useCallback } from "react";
|
|
5
5
|
import { useFormContext, useWatch } from "react-hook-form";
|
|
6
6
|
import { defaultCountries, usePhoneInput } from "react-international-phone";
|
|
7
|
+
import { useMount } from "ahooks";
|
|
7
8
|
import FormInput from "../../components/input.js";
|
|
8
9
|
import { isValidCountry } from "../../libs/util.js";
|
|
9
10
|
import CountrySelect from "../../components/country-select.js";
|
|
11
|
+
import { getPhoneUtil } from "../../libs/phone-validator.js";
|
|
10
12
|
export default function PhoneInput({ ...props }) {
|
|
11
13
|
const countryFieldName = props.countryFieldName || "billing_address.country";
|
|
12
|
-
const { control, getValues, setValue } = useFormContext();
|
|
14
|
+
const { control, getValues, setValue, trigger } = useFormContext();
|
|
13
15
|
const values = getValues();
|
|
14
16
|
const isUpdatingRef = useRef(false);
|
|
15
17
|
const safeUpdate = useCallback((callback) => {
|
|
@@ -43,6 +45,11 @@ export default function PhoneInput({ ...props }) {
|
|
|
43
45
|
setCountry(userCountry);
|
|
44
46
|
});
|
|
45
47
|
}, [userCountry, country, setCountry, safeUpdate]);
|
|
48
|
+
useMount(() => {
|
|
49
|
+
getPhoneUtil().catch((err) => {
|
|
50
|
+
console.error("Failed to preload phone validator:", err);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
46
53
|
const onCountryChange = useCallback(
|
|
47
54
|
(v) => {
|
|
48
55
|
safeUpdate(() => {
|
|
@@ -51,6 +58,9 @@ export default function PhoneInput({ ...props }) {
|
|
|
51
58
|
},
|
|
52
59
|
[setCountry, safeUpdate]
|
|
53
60
|
);
|
|
61
|
+
const handleBlur = useCallback(() => {
|
|
62
|
+
trigger(props.name);
|
|
63
|
+
}, [props.name]);
|
|
54
64
|
return (
|
|
55
65
|
// @ts-ignore
|
|
56
66
|
/* @__PURE__ */ jsx(
|
|
@@ -60,6 +70,7 @@ export default function PhoneInput({ ...props }) {
|
|
|
60
70
|
onChange: handlePhoneValueChange,
|
|
61
71
|
type: "tel",
|
|
62
72
|
inputRef,
|
|
73
|
+
onBlur: handleBlur,
|
|
63
74
|
InputProps: {
|
|
64
75
|
startAdornment: /* @__PURE__ */ jsx(InputAdornment, { position: "start", style: { marginRight: "2px", marginLeft: "-8px" }, children: /* @__PURE__ */ jsx(
|
|
65
76
|
CountrySelect,
|
|
@@ -9,6 +9,13 @@ import { useEffect, useCallback } from "react";
|
|
|
9
9
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
10
10
|
import LoadingButton from "../../../components/loading-button.js";
|
|
11
11
|
const { Elements, PaymentElement, useElements, useStripe, loadStripe, LinkAuthenticationElement } = globalThis.__STRIPE_COMPONENTS__;
|
|
12
|
+
const PaymentElementContainer = styled("div")`
|
|
13
|
+
opacity: 0;
|
|
14
|
+
transition: opacity 300ms ease;
|
|
15
|
+
&.visible {
|
|
16
|
+
opacity: 1;
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
12
19
|
function StripeCheckoutForm({
|
|
13
20
|
clientSecret,
|
|
14
21
|
intentType,
|
|
@@ -23,8 +30,35 @@ function StripeCheckoutForm({
|
|
|
23
30
|
const [state, setState] = useSetState({
|
|
24
31
|
message: "",
|
|
25
32
|
confirming: false,
|
|
26
|
-
loaded: false
|
|
33
|
+
loaded: false,
|
|
34
|
+
showBillingForm: false,
|
|
35
|
+
isTransitioning: false,
|
|
36
|
+
paymentMethod: "card"
|
|
27
37
|
});
|
|
38
|
+
const handlePaymentMethodChange = (event) => {
|
|
39
|
+
const method = event.value?.type;
|
|
40
|
+
const needsBillingInfo = method === "google_pay" || method === "apple_pay";
|
|
41
|
+
const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address);
|
|
42
|
+
if (shouldShowForm && !state.showBillingForm) {
|
|
43
|
+
setState({ isTransitioning: true });
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
setState({
|
|
46
|
+
isTransitioning: false,
|
|
47
|
+
paymentMethod: method,
|
|
48
|
+
showBillingForm: true
|
|
49
|
+
});
|
|
50
|
+
}, 300);
|
|
51
|
+
} else {
|
|
52
|
+
setState({
|
|
53
|
+
showBillingForm: false,
|
|
54
|
+
paymentMethod: method,
|
|
55
|
+
isTransitioning: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const isCompleteBillingAddress = (address) => {
|
|
60
|
+
return address && address.line1 && address.city && address.state && address.postal_code && address.country;
|
|
61
|
+
};
|
|
28
62
|
useEffect(() => {
|
|
29
63
|
if (!stripe) {
|
|
30
64
|
return;
|
|
@@ -55,23 +89,43 @@ function StripeCheckoutForm({
|
|
|
55
89
|
return;
|
|
56
90
|
}
|
|
57
91
|
try {
|
|
58
|
-
setState({ confirming: true });
|
|
92
|
+
setState({ confirming: true, message: "" });
|
|
59
93
|
const method = intentType === "payment_intent" ? "confirmPayment" : "confirmSetup";
|
|
60
|
-
const { error } = await
|
|
94
|
+
const { error: submitError } = await elements.submit();
|
|
95
|
+
if (submitError) {
|
|
96
|
+
setState({ confirming: false });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { error, paymentIntent, setupIntent } = await stripe[method]({
|
|
61
100
|
elements,
|
|
62
101
|
redirect: "if_required",
|
|
63
102
|
confirmParams: {
|
|
64
103
|
return_url: returnUrl || window.location.href,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
104
|
+
...!state.showBillingForm ? {
|
|
105
|
+
payment_method_data: {
|
|
106
|
+
billing_details: {
|
|
107
|
+
name: customer.name,
|
|
108
|
+
phone: customer.phone,
|
|
109
|
+
email: customer.email,
|
|
110
|
+
address: {
|
|
111
|
+
...customer.address || {},
|
|
112
|
+
country: customer.address?.country || "us",
|
|
113
|
+
line1: customer.address?.line1 || "",
|
|
114
|
+
line2: customer.address?.line2 || "",
|
|
115
|
+
city: customer.address?.city || "",
|
|
116
|
+
state: customer.address?.state || "",
|
|
117
|
+
postal_code: customer.address?.postal_code || "00000"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
71
120
|
}
|
|
72
|
-
}
|
|
121
|
+
} : {}
|
|
73
122
|
}
|
|
74
123
|
});
|
|
124
|
+
const intent = paymentIntent || setupIntent;
|
|
125
|
+
if (intent?.status === "canceled" || intent?.status === "requires_payment_method") {
|
|
126
|
+
setState({ confirming: false });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
75
129
|
setState({ confirming: false });
|
|
76
130
|
if (error) {
|
|
77
131
|
if (error.type === "validation_error") {
|
|
@@ -86,11 +140,11 @@ function StripeCheckoutForm({
|
|
|
86
140
|
setState({ confirming: false, message: err.message });
|
|
87
141
|
}
|
|
88
142
|
},
|
|
89
|
-
[customer, intentType, stripe]
|
|
143
|
+
[customer, intentType, stripe, state.showBillingForm, returnUrl]
|
|
90
144
|
// eslint-disable-line
|
|
91
145
|
);
|
|
92
146
|
return /* @__PURE__ */ jsxs(Content, { onSubmit: handleSubmit, children: [
|
|
93
|
-
/* @__PURE__ */ jsx(
|
|
147
|
+
(!state.paymentMethod || state.paymentMethod === "card") && /* @__PURE__ */ jsx(
|
|
94
148
|
LinkAuthenticationElement,
|
|
95
149
|
{
|
|
96
150
|
options: {
|
|
@@ -98,12 +152,14 @@ function StripeCheckoutForm({
|
|
|
98
152
|
}
|
|
99
153
|
}
|
|
100
154
|
),
|
|
101
|
-
/* @__PURE__ */ jsx(
|
|
155
|
+
/* @__PURE__ */ jsx(PaymentElementContainer, { className: !state.isTransitioning ? "visible" : "", children: /* @__PURE__ */ jsx(
|
|
102
156
|
PaymentElement,
|
|
103
157
|
{
|
|
104
158
|
options: {
|
|
105
159
|
layout: "auto",
|
|
106
|
-
fields: {
|
|
160
|
+
fields: {
|
|
161
|
+
billingDetails: state.showBillingForm ? "auto" : "never"
|
|
162
|
+
},
|
|
107
163
|
readOnly: state.confirming,
|
|
108
164
|
defaultValues: {
|
|
109
165
|
billingDetails: {
|
|
@@ -114,9 +170,10 @@ function StripeCheckoutForm({
|
|
|
114
170
|
}
|
|
115
171
|
}
|
|
116
172
|
},
|
|
173
|
+
onChange: handlePaymentMethodChange,
|
|
117
174
|
onReady: () => setState({ loaded: true })
|
|
118
175
|
}
|
|
119
|
-
),
|
|
176
|
+
) }),
|
|
120
177
|
(!stripe || !elements || !state.loaded) && /* @__PURE__ */ jsx(Center, { relative: "parent", children: /* @__PURE__ */ jsx(CircularProgress, {}) }),
|
|
121
178
|
stripe && elements && state.loaded && /* @__PURE__ */ jsx(
|
|
122
179
|
LoadingButton,
|
package/es/payment/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Box, Fade, Stack } from "@mui/material";
|
|
|
7
7
|
import { styled } from "@mui/system";
|
|
8
8
|
import { fromTokenToUnit } from "@ocap/util";
|
|
9
9
|
import { useSetState } from "ahooks";
|
|
10
|
-
import { useEffect, useState } from "react";
|
|
10
|
+
import { useEffect, useState, useMemo } from "react";
|
|
11
11
|
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
|
12
12
|
import trim from "lodash/trim";
|
|
13
13
|
import { usePaymentContext } from "../contexts/payment.js";
|
|
@@ -22,13 +22,14 @@ import {
|
|
|
22
22
|
} from "../libs/util.js";
|
|
23
23
|
import PaymentError from "./error.js";
|
|
24
24
|
import CheckoutFooter from "./footer.js";
|
|
25
|
-
import PaymentForm from "./form/index.js";
|
|
25
|
+
import PaymentForm, { hasDidWallet } from "./form/index.js";
|
|
26
26
|
import OverviewSkeleton from "./skeleton/overview.js";
|
|
27
27
|
import PaymentSkeleton from "./skeleton/payment.js";
|
|
28
28
|
import PaymentSuccess from "./success.js";
|
|
29
29
|
import PaymentSummary from "./summary.js";
|
|
30
30
|
import { useMobile } from "../hooks/mobile.js";
|
|
31
31
|
import { formatPhone } from "../libs/phone-validator.js";
|
|
32
|
+
import { getCurrencyPreference } from "../libs/currency.js";
|
|
32
33
|
PaymentInner.defaultProps = {
|
|
33
34
|
completed: false,
|
|
34
35
|
showCheckoutSummary: true
|
|
@@ -52,7 +53,36 @@ function PaymentInner({
|
|
|
52
53
|
const { isMobile } = useMobile();
|
|
53
54
|
const [state, setState] = useSetState({ checkoutSession });
|
|
54
55
|
const query = getQueryParams(window.location.href);
|
|
55
|
-
const
|
|
56
|
+
const availableCurrencyIds = useMemo(() => {
|
|
57
|
+
const currencyIds = /* @__PURE__ */ new Set();
|
|
58
|
+
paymentMethods.forEach((method2) => {
|
|
59
|
+
method2.payment_currencies.forEach((currency2) => {
|
|
60
|
+
if (currency2.active) {
|
|
61
|
+
currencyIds.add(currency2.id);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
return Array.from(currencyIds);
|
|
66
|
+
}, [paymentMethods]);
|
|
67
|
+
const defaultCurrencyId = useMemo(() => {
|
|
68
|
+
if (query.currencyId && availableCurrencyIds.includes(query.currencyId)) {
|
|
69
|
+
return query.currencyId;
|
|
70
|
+
}
|
|
71
|
+
if (session?.user && !hasDidWallet(session.user)) {
|
|
72
|
+
const stripeCurrencyId = paymentMethods.find((m) => m.type === "stripe")?.payment_currencies.find((c) => c.active)?.id;
|
|
73
|
+
if (stripeCurrencyId) {
|
|
74
|
+
return stripeCurrencyId;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const savedPreference = getCurrencyPreference(session?.user?.did, availableCurrencyIds);
|
|
78
|
+
if (savedPreference) {
|
|
79
|
+
return savedPreference;
|
|
80
|
+
}
|
|
81
|
+
if (state.checkoutSession.currency_id && availableCurrencyIds.includes(state.checkoutSession.currency_id)) {
|
|
82
|
+
return state.checkoutSession.currency_id;
|
|
83
|
+
}
|
|
84
|
+
return availableCurrencyIds?.[0];
|
|
85
|
+
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession.currency_id, paymentMethods]);
|
|
56
86
|
const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
|
|
57
87
|
const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary;
|
|
58
88
|
const methods = useForm({
|
|
@@ -97,14 +127,6 @@ function PaymentInner({
|
|
|
97
127
|
document.body.removeEventListener("focusout", focusoutHandler);
|
|
98
128
|
};
|
|
99
129
|
}, []);
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
if (!methods || query.currencyId) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (state.checkoutSession.currency_id !== defaultCurrencyId) {
|
|
105
|
-
methods.setValue("payment_currency", state.checkoutSession.currency_id);
|
|
106
|
-
}
|
|
107
|
-
}, [state.checkoutSession, defaultCurrencyId, query.currencyId]);
|
|
108
130
|
const currencyId = useWatch({ control: methods.control, name: "payment_currency", defaultValue: defaultCurrencyId });
|
|
109
131
|
const currency = findCurrency(paymentMethods, currencyId) || settings.baseCurrency;
|
|
110
132
|
const method = paymentMethods.find((x) => x.id === currency.payment_method_id);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
3
|
-
import { Avatar, Box, Card, CardActionArea, Grid, Stack, TextField, Typography } from "@mui/material";
|
|
3
|
+
import { Avatar, Box, Card, CardActionArea, Grid, Stack, TextField, Typography, IconButton } from "@mui/material";
|
|
4
|
+
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
|
|
4
5
|
import { useSetState } from "ahooks";
|
|
5
6
|
import { useEffect, useRef } from "react";
|
|
6
7
|
import { formatAmountPrecisionLimit } from "../libs/util.js";
|
|
@@ -62,7 +63,8 @@ export default function ProductDonation({
|
|
|
62
63
|
selected: defaultPreset === "custom" ? "" : defaultPreset,
|
|
63
64
|
input: defaultCustomAmount,
|
|
64
65
|
custom: !supportPreset || defaultPreset === "custom",
|
|
65
|
-
error: ""
|
|
66
|
+
error: "",
|
|
67
|
+
animating: false
|
|
66
68
|
});
|
|
67
69
|
const customInputRef = useRef(null);
|
|
68
70
|
const containerRef = useRef(null);
|
|
@@ -73,15 +75,73 @@ export default function ProductDonation({
|
|
|
73
75
|
localStorage.setItem(getUserStorageKey(DONATION_PRESET_KEY_BASE), formatAmount(amount));
|
|
74
76
|
};
|
|
75
77
|
const handleCustomSelect = () => {
|
|
76
|
-
setState({
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
setState({
|
|
79
|
+
custom: true,
|
|
80
|
+
selected: "",
|
|
81
|
+
animating: true
|
|
82
|
+
});
|
|
83
|
+
const hasPresets = presets.length > 0;
|
|
84
|
+
let sortedPresets = [];
|
|
85
|
+
if (hasPresets) {
|
|
86
|
+
sortedPresets = [...presets].map((p) => parseFloat(p)).sort((a, b) => a - b);
|
|
87
|
+
}
|
|
88
|
+
const minPreset = hasPresets ? sortedPresets[0] : 1;
|
|
89
|
+
const middleIndex = Math.floor(sortedPresets.length / 2);
|
|
90
|
+
const maxPreset = hasPresets ? sortedPresets[middleIndex] : 10;
|
|
91
|
+
const detectPrecision = () => {
|
|
92
|
+
let maxPrecision = 2;
|
|
93
|
+
if (!hasPresets)
|
|
94
|
+
return 0;
|
|
95
|
+
const allIntegers = presets.every((preset) => {
|
|
96
|
+
const num = parseFloat(preset);
|
|
97
|
+
return num === Math.floor(num);
|
|
98
|
+
});
|
|
99
|
+
if (allIntegers)
|
|
100
|
+
return 0;
|
|
101
|
+
presets.forEach((preset) => {
|
|
102
|
+
const decimalPart = preset.toString().split(".")[1];
|
|
103
|
+
if (decimalPart) {
|
|
104
|
+
maxPrecision = Math.max(maxPrecision, decimalPart.length);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return maxPrecision;
|
|
108
|
+
};
|
|
109
|
+
const precision = detectPrecision();
|
|
110
|
+
let randomAmount;
|
|
111
|
+
if (precision === 0) {
|
|
112
|
+
randomAmount = (Math.round(Math.random() * (maxPreset - minPreset) + minPreset) || 1).toString();
|
|
113
|
+
} else {
|
|
114
|
+
randomAmount = (Math.random() * (maxPreset - minPreset) + minPreset).toFixed(precision);
|
|
84
115
|
}
|
|
116
|
+
const startValue = state.input ? parseFloat(state.input) : 0;
|
|
117
|
+
const targetValue = parseFloat(randomAmount);
|
|
118
|
+
const difference = targetValue - startValue;
|
|
119
|
+
const startTime = Date.now();
|
|
120
|
+
const duration = 800;
|
|
121
|
+
const updateCounter = () => {
|
|
122
|
+
const currentTime = Date.now();
|
|
123
|
+
const elapsed = currentTime - startTime;
|
|
124
|
+
if (elapsed < duration) {
|
|
125
|
+
const progress = elapsed / duration;
|
|
126
|
+
const intermediateValue = startValue + difference * progress;
|
|
127
|
+
const currentValue = precision === 0 ? Math.floor(intermediateValue).toString() : intermediateValue.toFixed(precision);
|
|
128
|
+
setState({ input: currentValue });
|
|
129
|
+
requestAnimationFrame(updateCounter);
|
|
130
|
+
} else {
|
|
131
|
+
setState({
|
|
132
|
+
input: randomAmount,
|
|
133
|
+
animating: false,
|
|
134
|
+
error: ""
|
|
135
|
+
});
|
|
136
|
+
onChange({ priceId: item.price_id, amount: formatAmount(randomAmount) });
|
|
137
|
+
setPayable(true);
|
|
138
|
+
localStorage.setItem(getUserStorageKey(DONATION_CUSTOM_AMOUNT_KEY_BASE), formatAmount(randomAmount));
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
customInputRef.current?.focus();
|
|
141
|
+
}, 200);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
requestAnimationFrame(updateCounter);
|
|
85
145
|
localStorage.setItem(getUserStorageKey(DONATION_PRESET_KEY_BASE), "custom");
|
|
86
146
|
};
|
|
87
147
|
const handleTabSelect = (selectedItem) => {
|
|
@@ -261,13 +321,51 @@ export default function ProductDonation({
|
|
|
261
321
|
inputRef: customInputRef,
|
|
262
322
|
InputProps: {
|
|
263
323
|
endAdornment: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", sx: { ml: 1 }, children: [
|
|
324
|
+
/* @__PURE__ */ jsx(
|
|
325
|
+
IconButton,
|
|
326
|
+
{
|
|
327
|
+
size: "small",
|
|
328
|
+
onClick: handleCustomSelect,
|
|
329
|
+
disabled: state.animating,
|
|
330
|
+
sx: {
|
|
331
|
+
mr: 0.5,
|
|
332
|
+
opacity: state.animating ? 0.5 : 1,
|
|
333
|
+
transition: "all 0.2s ease",
|
|
334
|
+
"&:hover": {
|
|
335
|
+
transform: "scale(1.2)",
|
|
336
|
+
transition: "transform 0.3s ease"
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"aria-label": t("common.random"),
|
|
340
|
+
children: /* @__PURE__ */ jsx(AutoAwesomeIcon, { fontSize: "small" })
|
|
341
|
+
}
|
|
342
|
+
),
|
|
264
343
|
/* @__PURE__ */ jsx(Avatar, { src: currency?.logo, sx: { width: 16, height: 16 }, alt: currency?.symbol }),
|
|
265
344
|
/* @__PURE__ */ jsx(Typography, { children: currency?.symbol })
|
|
266
345
|
] }),
|
|
267
|
-
autoComplete: "off"
|
|
346
|
+
autoComplete: "off",
|
|
347
|
+
sx: {
|
|
348
|
+
"& input": {
|
|
349
|
+
transition: "all 0.25s ease"
|
|
350
|
+
}
|
|
351
|
+
}
|
|
268
352
|
},
|
|
269
353
|
sx: {
|
|
270
|
-
mt: defaultPreset !== "0" ? 0 : 1
|
|
354
|
+
mt: defaultPreset !== "0" ? 0 : 1,
|
|
355
|
+
"& .MuiInputBase-root": {
|
|
356
|
+
transition: "all 0.3s ease"
|
|
357
|
+
},
|
|
358
|
+
"& input[type=number]": {
|
|
359
|
+
MozAppearance: "textfield"
|
|
360
|
+
},
|
|
361
|
+
"& input[type=number]::-webkit-outer-spin-button": {
|
|
362
|
+
WebkitAppearance: "none",
|
|
363
|
+
margin: 0
|
|
364
|
+
},
|
|
365
|
+
"& input[type=number]::-webkit-inner-spin-button": {
|
|
366
|
+
WebkitAppearance: "none",
|
|
367
|
+
margin: 0
|
|
368
|
+
}
|
|
271
369
|
}
|
|
272
370
|
}
|
|
273
371
|
)
|
package/es/types/shims.d.ts
CHANGED
package/lib/checkout/donate.js
CHANGED
|
@@ -63,6 +63,7 @@ function DonateDetails({
|
|
|
63
63
|
locale
|
|
64
64
|
} = (0, _context.useLocaleContext)();
|
|
65
65
|
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
|
|
66
|
+
className: "cko-donate-details",
|
|
66
67
|
sx: {
|
|
67
68
|
width: "100%",
|
|
68
69
|
minWidth: "256px",
|
|
@@ -227,8 +228,17 @@ function SupporterAvatar({
|
|
|
227
228
|
onClose: () => setOpen(false),
|
|
228
229
|
sx: {
|
|
229
230
|
".MuiDialogContent-root": {
|
|
230
|
-
width:
|
|
231
|
+
width: {
|
|
232
|
+
xs: "100%",
|
|
233
|
+
md: "450px"
|
|
234
|
+
},
|
|
231
235
|
padding: "8px"
|
|
236
|
+
},
|
|
237
|
+
".cko-donate-details": {
|
|
238
|
+
maxHeight: {
|
|
239
|
+
xs: "100%",
|
|
240
|
+
md: "300px"
|
|
241
|
+
}
|
|
232
242
|
}
|
|
233
243
|
},
|
|
234
244
|
title: `${customersNum} supporter${customersNum > 1 ? "s" : ""}`,
|