@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.
Files changed (67) hide show
  1. package/es/checkout/donate.js +11 -1
  2. package/es/components/country-select.js +243 -21
  3. package/es/components/over-due-invoice-payment.d.ts +3 -1
  4. package/es/components/over-due-invoice-payment.js +6 -4
  5. package/es/contexts/payment.d.ts +2 -1
  6. package/es/contexts/payment.js +8 -1
  7. package/es/hooks/keyboard.js +3 -0
  8. package/es/index.d.ts +1 -0
  9. package/es/index.js +1 -0
  10. package/es/libs/api.js +4 -0
  11. package/es/libs/currency.d.ts +3 -0
  12. package/es/libs/currency.js +22 -0
  13. package/es/libs/phone-validator.js +2 -0
  14. package/es/libs/util.d.ts +2 -2
  15. package/es/libs/util.js +7 -4
  16. package/es/libs/validator.d.ts +1 -0
  17. package/es/libs/validator.js +70 -0
  18. package/es/payment/form/address.js +17 -3
  19. package/es/payment/form/index.js +10 -1
  20. package/es/payment/form/phone.js +12 -1
  21. package/es/payment/form/stripe/form.js +72 -15
  22. package/es/payment/index.js +33 -11
  23. package/es/payment/product-donation.js +110 -12
  24. package/es/types/shims.d.ts +2 -0
  25. package/lib/checkout/donate.js +11 -1
  26. package/lib/components/country-select.js +243 -39
  27. package/lib/components/over-due-invoice-payment.d.ts +3 -1
  28. package/lib/components/over-due-invoice-payment.js +7 -4
  29. package/lib/contexts/payment.d.ts +2 -1
  30. package/lib/contexts/payment.js +9 -1
  31. package/lib/hooks/keyboard.js +3 -0
  32. package/lib/index.d.ts +1 -0
  33. package/lib/index.js +12 -0
  34. package/lib/libs/api.js +4 -0
  35. package/lib/libs/currency.d.ts +3 -0
  36. package/lib/libs/currency.js +31 -0
  37. package/lib/libs/phone-validator.js +1 -0
  38. package/lib/libs/util.d.ts +2 -2
  39. package/lib/libs/util.js +7 -4
  40. package/lib/libs/validator.d.ts +1 -0
  41. package/lib/libs/validator.js +20 -0
  42. package/lib/payment/form/address.js +15 -2
  43. package/lib/payment/form/index.js +12 -1
  44. package/lib/payment/form/phone.js +13 -1
  45. package/lib/payment/form/stripe/form.js +98 -29
  46. package/lib/payment/index.js +34 -10
  47. package/lib/payment/product-donation.js +106 -15
  48. package/lib/types/shims.d.ts +2 -0
  49. package/package.json +8 -8
  50. package/src/checkout/donate.tsx +11 -1
  51. package/src/components/country-select.tsx +265 -20
  52. package/src/components/over-due-invoice-payment.tsx +6 -2
  53. package/src/contexts/payment.tsx +11 -1
  54. package/src/hooks/keyboard.ts +5 -3
  55. package/src/index.ts +1 -0
  56. package/src/libs/api.ts +4 -1
  57. package/src/libs/currency.ts +25 -0
  58. package/src/libs/phone-validator.ts +1 -0
  59. package/src/libs/util.ts +18 -4
  60. package/src/libs/validator.ts +70 -0
  61. package/src/payment/form/address.tsx +17 -4
  62. package/src/payment/form/index.tsx +11 -1
  63. package/src/payment/form/phone.tsx +15 -1
  64. package/src/payment/form/stripe/form.tsx +104 -32
  65. package/src/payment/index.tsx +45 -14
  66. package/src/payment/product-donation.tsx +129 -10
  67. package/src/types/shims.d.ts +2 -0
@@ -65,6 +65,7 @@ export function DonateDetails({ supporters = [], currency, method }) {
65
65
  return /* @__PURE__ */ jsx(
66
66
  Stack,
67
67
  {
68
+ className: "cko-donate-details",
68
69
  sx: {
69
70
  width: "100%",
70
71
  minWidth: "256px",
@@ -246,8 +247,17 @@ function SupporterAvatar({
246
247
  onClose: () => setOpen(false),
247
248
  sx: {
248
249
  ".MuiDialogContent-root": {
249
- width: "450px",
250
+ width: {
251
+ xs: "100%",
252
+ md: "450px"
253
+ },
250
254
  padding: "8px"
255
+ },
256
+ ".cko-donate-details": {
257
+ maxHeight: {
258
+ xs: "100%",
259
+ md: "300px"
260
+ }
251
261
  }
252
262
  },
253
263
  title: `${customersNum} supporter${customersNum > 1 ? "s" : ""}`,
@@ -1,10 +1,110 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useMemo, forwardRef } from "react";
3
- import { Box, MenuItem, Select, Typography } from "@mui/material";
2
+ import { useMemo, forwardRef, useState, useEffect, useRef } from "react";
3
+ import { Box, MenuItem, Select, Typography, TextField } from "@mui/material";
4
4
  import { useFormContext } from "react-hook-form";
5
5
  import { FlagEmoji, defaultCountries, parseCountry } from "react-international-phone";
6
+ import { useMobile } from "../hooks/mobile.js";
6
7
  const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
7
8
  const { setValue } = useFormContext();
9
+ const [open, setOpen] = useState(false);
10
+ const [searchText, setSearchText] = useState("");
11
+ const inputRef = useRef(null);
12
+ const menuRef = useRef(null);
13
+ const listRef = useRef(null);
14
+ const [focusedIndex, setFocusedIndex] = useState(-1);
15
+ const itemHeightRef = useRef(40);
16
+ const { isMobile } = useMobile();
17
+ const measuredRef = useRef(false);
18
+ useEffect(() => {
19
+ if (!open)
20
+ return () => {
21
+ };
22
+ const handleResize = () => {
23
+ measuredRef.current = false;
24
+ };
25
+ window.addEventListener("resize", handleResize);
26
+ return () => {
27
+ window.removeEventListener("resize", handleResize);
28
+ };
29
+ }, [open]);
30
+ const scrollToTop = () => {
31
+ if (listRef.current) {
32
+ listRef.current.scrollTop = 0;
33
+ }
34
+ };
35
+ const measureItemHeight = () => {
36
+ if (!listRef.current || !open)
37
+ return;
38
+ const items = listRef.current.querySelectorAll(".MuiMenuItem-root");
39
+ if (items.length > 0) {
40
+ const firstItem = items[0];
41
+ if (firstItem.offsetHeight > 0) {
42
+ itemHeightRef.current = firstItem.offsetHeight;
43
+ }
44
+ }
45
+ };
46
+ const controlScrollPosition = (index) => {
47
+ if (!listRef.current)
48
+ return;
49
+ if (open && !measuredRef.current) {
50
+ measureItemHeight();
51
+ measuredRef.current = true;
52
+ }
53
+ const listHeight = listRef.current.clientHeight;
54
+ const targetPosition = index * itemHeightRef.current;
55
+ if (index < 2) {
56
+ listRef.current.scrollTop = 0;
57
+ } else if (index > filteredCountries.length - 3) {
58
+ listRef.current.scrollTop = listRef.current.scrollHeight - listHeight;
59
+ } else {
60
+ const scrollPosition = targetPosition - listHeight / 2 + itemHeightRef.current / 2;
61
+ listRef.current.scrollTop = Math.max(0, scrollPosition);
62
+ }
63
+ };
64
+ useEffect(() => {
65
+ let timeout = null;
66
+ if (open) {
67
+ timeout = setTimeout(() => {
68
+ scrollToTop();
69
+ if (!isMobile && inputRef.current) {
70
+ inputRef.current.focus();
71
+ }
72
+ }, 100);
73
+ } else {
74
+ setSearchText("");
75
+ setFocusedIndex(-1);
76
+ }
77
+ return () => {
78
+ if (timeout) {
79
+ clearTimeout(timeout);
80
+ }
81
+ };
82
+ }, [open, isMobile]);
83
+ const filteredCountries = useMemo(() => {
84
+ if (!searchText)
85
+ return defaultCountries;
86
+ return defaultCountries.filter((c) => {
87
+ const parsed = parseCountry(c);
88
+ return parsed.name.toLowerCase().includes(searchText.toLowerCase()) || parsed.iso2.toLowerCase().includes(searchText.toLowerCase()) || `+${parsed.dialCode}`.includes(searchText);
89
+ });
90
+ }, [searchText]);
91
+ useEffect(() => {
92
+ scrollToTop();
93
+ setFocusedIndex(-1);
94
+ }, [searchText]);
95
+ useEffect(() => {
96
+ let timeout = null;
97
+ if (focusedIndex >= 0) {
98
+ timeout = setTimeout(() => {
99
+ controlScrollPosition(focusedIndex);
100
+ }, 10);
101
+ }
102
+ return () => {
103
+ if (timeout) {
104
+ clearTimeout(timeout);
105
+ }
106
+ };
107
+ }, [focusedIndex, filteredCountries.length]);
8
108
  const countryDetail = useMemo(() => {
9
109
  const item = defaultCountries.find((v) => v[1] === value);
10
110
  return value && item ? parseCountry(item) : { name: "" };
@@ -13,13 +113,62 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
13
113
  onChange(e.target.value);
14
114
  setValue(name, e.target.value);
15
115
  };
16
- return /* @__PURE__ */ jsx(
116
+ const handleCountryClick = (code) => {
117
+ onChange(code);
118
+ setValue(name, code);
119
+ setOpen(false);
120
+ };
121
+ const handleSearchChange = (e) => {
122
+ e.stopPropagation();
123
+ setSearchText(e.target.value);
124
+ };
125
+ const handleKeyDown = (e) => {
126
+ e.stopPropagation();
127
+ if (e.key === "Escape") {
128
+ setOpen(false);
129
+ return;
130
+ }
131
+ const handleNavigation = (direction) => {
132
+ e.preventDefault();
133
+ setFocusedIndex((prev) => {
134
+ if (direction === "next") {
135
+ if (prev === -1)
136
+ return 0;
137
+ return prev >= filteredCountries.length - 1 ? 0 : prev + 1;
138
+ }
139
+ if (prev === -1)
140
+ return filteredCountries.length - 1;
141
+ return prev <= 0 ? filteredCountries.length - 1 : prev - 1;
142
+ });
143
+ };
144
+ if (e.key === "Tab") {
145
+ handleNavigation(e.shiftKey ? "prev" : "next");
146
+ return;
147
+ }
148
+ if (e.key === "ArrowDown") {
149
+ handleNavigation("next");
150
+ return;
151
+ }
152
+ if (e.key === "ArrowUp") {
153
+ handleNavigation("prev");
154
+ return;
155
+ }
156
+ if (e.key === "Enter" && focusedIndex >= 0 && focusedIndex < filteredCountries.length) {
157
+ e.preventDefault();
158
+ const country = parseCountry(filteredCountries[focusedIndex]);
159
+ handleCountryClick(country.iso2);
160
+ }
161
+ };
162
+ return /* @__PURE__ */ jsxs(
17
163
  Select,
18
164
  {
19
165
  ref,
166
+ open,
167
+ onOpen: () => setOpen(true),
168
+ onClose: () => setOpen(false),
20
169
  MenuProps: {
21
170
  style: {
22
- height: "300px",
171
+ maxHeight: "300px",
23
172
  top: "10px"
24
173
  },
25
174
  anchorOrigin: {
@@ -29,6 +178,18 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
29
178
  transformOrigin: {
30
179
  vertical: "top",
31
180
  horizontal: "left"
181
+ },
182
+ PaperProps: {
183
+ ref: menuRef,
184
+ sx: {
185
+ display: "flex",
186
+ "& .MuiList-root": {
187
+ pt: 0,
188
+ display: "flex",
189
+ flexDirection: "column",
190
+ overflowY: "hidden"
191
+ }
192
+ }
32
193
  }
33
194
  },
34
195
  sx: {
@@ -55,23 +216,84 @@ const CountrySelect = forwardRef(({ value, onChange, name, sx }, ref) => {
55
216
  },
56
217
  value,
57
218
  onChange: onCountryChange,
58
- renderValue: (code) => {
59
- return /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", flexWrap: "nowrap", gap: 0.5, sx: { cursor: "pointer" }, children: [
60
- /* @__PURE__ */ jsx(FlagEmoji, { iso2: code, style: { display: "flex" } }),
61
- /* @__PURE__ */ jsx(Typography, { children: countryDetail?.name })
62
- ] });
63
- },
64
- children: defaultCountries.map((c) => {
65
- const parsed = parseCountry(c);
66
- return /* @__PURE__ */ jsxs(MenuItem, { value: parsed.iso2, children: [
67
- /* @__PURE__ */ jsx(FlagEmoji, { iso2: parsed.iso2, style: { marginRight: "8px" } }),
68
- /* @__PURE__ */ jsx(Typography, { marginRight: "8px", children: parsed.name }),
69
- /* @__PURE__ */ jsxs(Typography, { color: "gray", children: [
70
- "+",
71
- parsed.dialCode
72
- ] })
73
- ] }, parsed.iso2);
74
- })
219
+ renderValue: (code) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", flexWrap: "nowrap", gap: 0.5, sx: { cursor: "pointer" }, children: [
220
+ /* @__PURE__ */ jsx(FlagEmoji, { iso2: code, style: { display: "flex" } }),
221
+ /* @__PURE__ */ jsx(Typography, { children: countryDetail?.name })
222
+ ] }),
223
+ children: [
224
+ /* @__PURE__ */ jsx(
225
+ Box,
226
+ {
227
+ sx: {
228
+ position: "sticky",
229
+ top: 0,
230
+ zIndex: 1,
231
+ bgcolor: "background.paper",
232
+ p: 1
233
+ },
234
+ onClick: (e) => {
235
+ e.stopPropagation();
236
+ },
237
+ children: /* @__PURE__ */ jsx(
238
+ TextField,
239
+ {
240
+ inputRef,
241
+ autoFocus: !isMobile,
242
+ fullWidth: true,
243
+ placeholder: "Search country...",
244
+ value: searchText,
245
+ onChange: handleSearchChange,
246
+ onKeyDown: handleKeyDown,
247
+ onClick: (e) => e.stopPropagation(),
248
+ size: "small",
249
+ variant: "outlined"
250
+ }
251
+ )
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsx(
255
+ Box,
256
+ {
257
+ ref: listRef,
258
+ sx: {
259
+ flex: 1,
260
+ overflowY: "auto",
261
+ overflowX: "hidden",
262
+ maxHeight: "calc(300px - 65px)",
263
+ scrollBehavior: "smooth"
264
+ },
265
+ children: filteredCountries.length > 0 ? filteredCountries.map((c, index) => {
266
+ const parsed = parseCountry(c);
267
+ const isFocused = index === focusedIndex;
268
+ return /* @__PURE__ */ jsxs(
269
+ MenuItem,
270
+ {
271
+ value: parsed.iso2,
272
+ selected: parsed.iso2 === value,
273
+ onClick: () => handleCountryClick(parsed.iso2),
274
+ sx: {
275
+ "&.Mui-selected": {
276
+ backgroundColor: "rgba(0, 0, 0, 0.04)"
277
+ },
278
+ "&:hover": {
279
+ backgroundColor: "var(--backgrounds-bg-highlight, #eff6ff)"
280
+ },
281
+ ...isFocused ? {
282
+ backgroundColor: "var(--backgrounds-bg-highlight, #eff6ff)",
283
+ outline: "none"
284
+ } : {}
285
+ },
286
+ children: [
287
+ /* @__PURE__ */ jsx(FlagEmoji, { iso2: parsed.iso2, style: { marginRight: "8px" } }),
288
+ /* @__PURE__ */ jsx(Typography, { children: parsed.name })
289
+ ]
290
+ },
291
+ parsed.iso2
292
+ );
293
+ }) : /* @__PURE__ */ jsx(MenuItem, { disabled: true, children: /* @__PURE__ */ jsx(Typography, { color: "text.secondary", children: "No countries found" }) })
294
+ }
295
+ )
296
+ ]
75
297
  }
76
298
  );
77
299
  });
@@ -27,13 +27,14 @@ type Props = {
27
27
  subscriptionCount?: number;
28
28
  detailUrl: string;
29
29
  }) => React.ReactNode;
30
+ authToken?: string;
30
31
  };
31
32
  type SummaryItem = {
32
33
  amount: string;
33
34
  currency: PaymentCurrency;
34
35
  method: PaymentMethod;
35
36
  };
36
- declare function OverdueInvoicePayment({ subscriptionId, customerId, mode, dialogProps, children, onPaid, detailLinkOptions, successToast, alertMessage, }: Props): import("react").JSX.Element | null;
37
+ declare function OverdueInvoicePayment({ subscriptionId, customerId, mode, dialogProps, children, onPaid, detailLinkOptions, successToast, alertMessage, authToken, }: Props): import("react").JSX.Element | null;
37
38
  declare namespace OverdueInvoicePayment {
38
39
  var defaultProps: {
39
40
  mode: string;
@@ -49,6 +50,7 @@ declare namespace OverdueInvoicePayment {
49
50
  customerId: undefined;
50
51
  successToast: boolean;
51
52
  alertMessage: string;
53
+ authToken: undefined;
52
54
  };
53
55
  }
54
56
  export default OverdueInvoicePayment;
@@ -23,7 +23,7 @@ const fetchOverdueInvoices = async (params) => {
23
23
  } else {
24
24
  url = `/api/customers/${params.customerId}/overdue/invoices`;
25
25
  }
26
- const res = await api.get(url);
26
+ const res = await api.get(params.authToken ? joinURL(url, `?authToken=${params.authToken}`) : url);
27
27
  return res.data;
28
28
  };
29
29
  function OverdueInvoicePayment({
@@ -36,7 +36,8 @@ function OverdueInvoicePayment({
36
36
  },
37
37
  detailLinkOptions = { enabled: true },
38
38
  successToast = true,
39
- alertMessage = ""
39
+ alertMessage = "",
40
+ authToken
40
41
  }) {
41
42
  const { t } = useLocaleContext();
42
43
  const { connect } = usePaymentContext();
@@ -55,7 +56,7 @@ function OverdueInvoicePayment({
55
56
  error,
56
57
  loading,
57
58
  runAsync: refresh
58
- } = useRequest(() => fetchOverdueInvoices({ subscriptionId, customerId }), {
59
+ } = useRequest(() => fetchOverdueInvoices({ subscriptionId, customerId, authToken }), {
59
60
  ready: !!subscriptionId || !!customerId
60
61
  });
61
62
  const detailUrl = useMemo(() => {
@@ -396,6 +397,7 @@ OverdueInvoicePayment.defaultProps = {
396
397
  subscriptionId: void 0,
397
398
  customerId: void 0,
398
399
  successToast: true,
399
- alertMessage: ""
400
+ alertMessage: "",
401
+ authToken: void 0
400
402
  };
401
403
  export default OverdueInvoicePayment;
@@ -25,10 +25,11 @@ export type PaymentContextProps = {
25
25
  connect: import('@arcblock/did-connect/lib/types').SessionContext['connectApi'];
26
26
  children: any;
27
27
  baseUrl?: string;
28
+ authToken?: string;
28
29
  };
29
30
  declare const PaymentContext: import("react").Context<PaymentContextType>;
30
31
  declare const Consumer: import("react").Consumer<PaymentContextType>;
31
- declare function PaymentProvider({ session, connect, children, baseUrl }: PaymentContextProps): import("react").JSX.Element | null;
32
+ declare function PaymentProvider({ session, connect, children, baseUrl, authToken }: PaymentContextProps): import("react").JSX.Element | null;
32
33
  declare namespace PaymentProvider {
33
34
  var defaultProps: {};
34
35
  }
@@ -33,9 +33,16 @@ const getCurrency = (currencyId, methods) => {
33
33
  const getMethod = (methodId, methods) => {
34
34
  return methods.find((x) => x.id === methodId);
35
35
  };
36
- function PaymentProvider({ session, connect, children, baseUrl }) {
36
+ function PaymentProvider({ session, connect, children, baseUrl, authToken }) {
37
37
  if (baseUrl) {
38
38
  window.__PAYMENT_KIT_BASE_URL = baseUrl;
39
+ } else {
40
+ window.__PAYMENT_KIT_BASE_URL = "";
41
+ }
42
+ if (authToken) {
43
+ window.__PAYMENT_KIT_AUTH_TOKEN = authToken;
44
+ } else {
45
+ window.__PAYMENT_KIT_AUTH_TOKEN = "";
39
46
  }
40
47
  const [livemode, setLivemode] = useLocalStorageState("livemode", { defaultValue: true });
41
48
  const {
@@ -39,6 +39,9 @@ export const useTabNavigation = (items, onSelect, options) => {
39
39
  return i;
40
40
  }
41
41
  }
42
+ if (includeCustom && navigableElements.length > 0) {
43
+ return navigableElements.length - 1;
44
+ }
42
45
  }
43
46
  return -1;
44
47
  }, [items, includeCustom, isCustomSelected, currentValue, valueType, compareValue, findNavigableElements]);
package/es/index.d.ts CHANGED
@@ -43,5 +43,6 @@ export * from './hooks/mobile';
43
43
  export * from './hooks/table';
44
44
  export * from './hooks/scroll';
45
45
  export * from './hooks/keyboard';
46
+ export * from './libs/validator';
46
47
  export { translations, createTranslator } from './locales';
47
48
  export { createLazyComponent, api, dayjs, FormInput, PhoneInput, AddressForm, StripeForm, Status, Livemode, Switch, ConfirmDialog, CheckoutForm, CheckoutTable, CheckoutDonate, CurrencySelector, Payment, PaymentSummary, PricingTable, ProductSkeleton, Amount, CustomerInvoiceList, CustomerPaymentList, TxLink, TxGas, SafeGuard, PricingItem, CountrySelect, Table, TruncatedText, Link, OverdueInvoicePayment, PaymentBeneficiaries, LoadingButton, DonateDetails, };
package/es/index.js CHANGED
@@ -43,6 +43,7 @@ export * from "./hooks/mobile.js";
43
43
  export * from "./hooks/table.js";
44
44
  export * from "./hooks/scroll.js";
45
45
  export * from "./hooks/keyboard.js";
46
+ export * from "./libs/validator.js";
46
47
  export { translations, createTranslator } from "./locales/index.js";
47
48
  export {
48
49
  createLazyComponent,
package/es/libs/api.js CHANGED
@@ -10,6 +10,10 @@ api.interceptors.request.use(
10
10
  const locale = getLocale(window.blocklet?.languages);
11
11
  const query = new URLSearchParams(config.url?.split("?").pop());
12
12
  config.params = { ...config.params || {}, locale };
13
+ const authToken = window.__PAYMENT_KIT_AUTH_TOKEN;
14
+ if (authToken && typeof config.params.authToken === "undefined" && !query.has("authToken")) {
15
+ config.params.authToken = authToken;
16
+ }
13
17
  if (typeof config.params.livemode === "undefined" && query.has("livemode") === false) {
14
18
  const livemode = localStorage.getItem("livemode");
15
19
  config.params.livemode = isNull(livemode) ? true : JSON.parse(livemode);
@@ -0,0 +1,3 @@
1
+ export declare const getUserStorageKey: (base: string, did?: string) => string;
2
+ export declare const saveCurrencyPreference: (currencyId: string, did?: string) => void;
3
+ export declare const getCurrencyPreference: (did?: string, availableCurrencyIds?: string[]) => string | null;
@@ -0,0 +1,22 @@
1
+ const CURRENCY_PREFERENCE_KEY_BASE = "payment-currency-preference";
2
+ export const getUserStorageKey = (base, did) => {
3
+ return did ? `${base}:${did}` : base;
4
+ };
5
+ export const saveCurrencyPreference = (currencyId, did) => {
6
+ try {
7
+ localStorage.setItem(getUserStorageKey(CURRENCY_PREFERENCE_KEY_BASE, did), currencyId);
8
+ } catch (e) {
9
+ console.warn("Failed to save currency preference", e);
10
+ }
11
+ };
12
+ export const getCurrencyPreference = (did, availableCurrencyIds) => {
13
+ try {
14
+ const saved = localStorage.getItem(getUserStorageKey(CURRENCY_PREFERENCE_KEY_BASE, did));
15
+ if (saved && (!availableCurrencyIds || availableCurrencyIds.includes(saved))) {
16
+ return saved;
17
+ }
18
+ } catch (e) {
19
+ console.warn("Failed to access localStorage", e);
20
+ }
21
+ return null;
22
+ };
@@ -14,6 +14,8 @@ export const getPhoneUtil = async () => {
14
14
  return phoneUtil;
15
15
  };
16
16
  export const validatePhoneNumber = async (phoneNumber) => {
17
+ if (!phoneNumber)
18
+ return true;
17
19
  try {
18
20
  let util = null;
19
21
  try {
package/es/libs/util.d.ts CHANGED
@@ -10,8 +10,8 @@ export declare function formatDateTime(date: Date | string | number, locale?: st
10
10
  export declare const formatLocale: (locale?: string) => string;
11
11
  export declare const formatPrettyMsLocale: (locale: string) => "zh_CN" | "en_US";
12
12
  export declare const formatError: (err: any) => any;
13
- export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean): string;
14
- export declare function formatNumber(n: number | string, precision?: number, trim?: boolean): string;
13
+ export declare function formatBNStr(str?: string, decimals?: number, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
14
+ export declare function formatNumber(n: number | string, precision?: number, trim?: boolean, thousandSeparated?: boolean): string;
15
15
  export declare const formatPrice: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean, locale?: string) => string;
16
16
  export declare const formatPriceAmount: (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity?: number, bn?: boolean) => string;
17
17
  export declare function getStatementDescriptor(items: any[]): any;
package/es/libs/util.js CHANGED
@@ -85,16 +85,19 @@ export const formatError = (err) => {
85
85
  }
86
86
  return err.message;
87
87
  };
88
- export function formatBNStr(str = "", decimals = 18, precision = 6, trim = true) {
89
- return formatNumber(fromUnitToToken(str, decimals), precision, trim);
88
+ export function formatBNStr(str = "", decimals = 18, precision = 6, trim = true, thousandSeparated = true) {
89
+ if (!str) {
90
+ return "0";
91
+ }
92
+ return formatNumber(fromUnitToToken(str, decimals), precision, trim, thousandSeparated);
90
93
  }
91
- export function formatNumber(n, precision = 6, trim = true) {
94
+ export function formatNumber(n, precision = 6, trim = true, thousandSeparated = true) {
92
95
  if (!n || n === "0") {
93
96
  return "0";
94
97
  }
95
98
  const num = numbro(n);
96
99
  const options = {
97
- thousandSeparated: true,
100
+ thousandSeparated,
98
101
  ...(precision || precision === 0) && { mantissa: precision }
99
102
  };
100
103
  const result = num.format(options);
@@ -0,0 +1 @@
1
+ export declare function validatePostalCode(postalCode: string, country?: string): boolean;
@@ -0,0 +1,70 @@
1
+ import isPostalCode from "validator/lib/isPostalCode";
2
+ const POSTAL_CODE_SUPPORTED_COUNTRIES = [
3
+ "AD",
4
+ "AT",
5
+ "AU",
6
+ "BE",
7
+ "BG",
8
+ "BR",
9
+ "CA",
10
+ "CH",
11
+ "CN",
12
+ "CZ",
13
+ "DE",
14
+ "DK",
15
+ "DZ",
16
+ "EE",
17
+ "ES",
18
+ "FI",
19
+ "FR",
20
+ "GB",
21
+ "GR",
22
+ "HR",
23
+ "HU",
24
+ "ID",
25
+ "IE",
26
+ "IL",
27
+ "IN",
28
+ "IR",
29
+ "IS",
30
+ "IT",
31
+ "JP",
32
+ "KE",
33
+ "KR",
34
+ "LI",
35
+ "LT",
36
+ "LU",
37
+ "LV",
38
+ "MX",
39
+ "MT",
40
+ "NL",
41
+ "NO",
42
+ "NZ",
43
+ "PL",
44
+ "PR",
45
+ "PT",
46
+ "RO",
47
+ "RU",
48
+ "SA",
49
+ "SE",
50
+ "SI",
51
+ "SK",
52
+ "TN",
53
+ "TW",
54
+ "UA",
55
+ "US",
56
+ "ZA",
57
+ "ZM"
58
+ ];
59
+ export function validatePostalCode(postalCode, country) {
60
+ if (!postalCode)
61
+ return true;
62
+ const countryUpper = country?.toUpperCase();
63
+ const isSupported = country && POSTAL_CODE_SUPPORTED_COUNTRIES.includes(countryUpper);
64
+ try {
65
+ return isPostalCode(postalCode, isSupported ? countryUpper : "any");
66
+ } catch (error) {
67
+ console.error(error);
68
+ return false;
69
+ }
70
+ }
@@ -1,15 +1,17 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
3
  import { Fade, FormLabel, InputAdornment, Stack } from "@mui/material";
4
- import { Controller, useFormContext } from "react-hook-form";
4
+ import { Controller, useFormContext, useWatch } from "react-hook-form";
5
5
  import FormInput from "../../components/input.js";
6
6
  import CountrySelect from "../../components/country-select.js";
7
+ import { validatePostalCode } from "../../libs/validator.js";
7
8
  AddressForm.defaultProps = {
8
9
  sx: {}
9
10
  };
10
11
  export default function AddressForm({ mode, stripe, sx = {} }) {
11
12
  const { t } = useLocaleContext();
12
13
  const { control } = useFormContext();
14
+ const country = useWatch({ control, name: "billing_address.country" });
13
15
  if (mode === "required") {
14
16
  return /* @__PURE__ */ jsx(Fade, { in: true, children: /* @__PURE__ */ jsx(Stack, { className: "cko-payment-address cko-payment-form", sx, children: /* @__PURE__ */ jsxs(Stack, { direction: "column", className: "cko-payment-form", spacing: 0, children: [
15
17
  /* @__PURE__ */ jsx(FormLabel, { className: "base-label", children: t("payment.checkout.billing.line1") }),
@@ -50,7 +52,13 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
50
52
  FormInput,
51
53
  {
52
54
  name: "billing_address.postal_code",
53
- rules: { required: t("payment.checkout.required") },
55
+ rules: {
56
+ required: t("payment.checkout.required"),
57
+ validate: (x) => {
58
+ const isValid = validatePostalCode(x, country);
59
+ return isValid ? true : t("payment.checkout.invalid");
60
+ }
61
+ },
54
62
  errorPosition: "right",
55
63
  variant: "outlined",
56
64
  placeholder: t("payment.checkout.billing.postal_code"),
@@ -85,7 +93,13 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
85
93
  FormInput,
86
94
  {
87
95
  name: "billing_address.postal_code",
88
- rules: { required: t("payment.checkout.required") },
96
+ rules: {
97
+ required: t("payment.checkout.required"),
98
+ validate: (x) => {
99
+ const isValid = validatePostalCode(x, country);
100
+ return isValid ? true : t("payment.checkout.invalid");
101
+ }
102
+ },
89
103
  errorPosition: "right",
90
104
  variant: "outlined",
91
105
  placeholder: t("payment.checkout.billing.postal_code"),