@blocklet/payment-react 1.13.291 → 1.13.293

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.
@@ -1,5 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import type { DonationSettings, TCheckoutSessionExpanded, TPaymentCurrency, TPaymentMethod } from '@blocklet/payment-types';
3
+ import { ButtonProps as MUIButtonProps } from '@mui/material';
3
4
  import { CheckoutProps } from '../types';
4
5
  export type DonateHistory = {
5
6
  supporters: TCheckoutSessionExpanded[];
@@ -7,16 +8,29 @@ export type DonateHistory = {
7
8
  method: TPaymentMethod;
8
9
  total: number;
9
10
  };
11
+ export interface ButtonType extends Omit<MUIButtonProps, 'text'> {
12
+ text: string;
13
+ }
10
14
  export type DonateProps = Pick<CheckoutProps, 'onPaid' | 'onError'> & {
11
15
  settings: DonationSettings;
12
16
  livemode?: boolean;
13
17
  timeout?: number;
18
+ mode?: 'inline' | 'default';
19
+ inlineOptions?: {
20
+ button?: ButtonType;
21
+ };
14
22
  };
15
- declare function CheckoutDonate({ settings, livemode, timeout, onPaid, onError }: DonateProps): import("react").JSX.Element | null;
23
+ declare function CheckoutDonate({ settings, livemode, timeout, onPaid, onError, mode, inlineOptions, }: DonateProps): import("react").JSX.Element;
16
24
  declare namespace CheckoutDonate {
17
25
  var defaultProps: {
18
26
  livemode: boolean;
19
27
  timeout: number;
28
+ mode: string;
29
+ inlineOptions: {
30
+ button: {
31
+ text: string;
32
+ };
33
+ };
20
34
  };
21
35
  }
22
36
  export default CheckoutDonate;
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import Dialog from "@arcblock/ux/lib/Dialog";
3
3
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
4
4
  import {
@@ -7,7 +7,9 @@ import {
7
7
  AvatarGroup,
8
8
  Box,
9
9
  Button,
10
+ CircularProgress,
10
11
  Hidden,
12
+ Popover,
11
13
  Stack,
12
14
  Table,
13
15
  TableBody,
@@ -15,13 +17,13 @@ import {
15
17
  TableRow,
16
18
  Typography
17
19
  } from "@mui/material";
18
- import { useRequest, useSetState } from "ahooks";
20
+ import { useMount, useRequest, useSetState } from "ahooks";
19
21
  import omit from "lodash/omit";
20
22
  import uniqBy from "lodash/unionBy";
21
- import { useEffect } from "react";
23
+ import { useEffect, useState } from "react";
22
24
  import TxLink from "../components/blockchain/tx.js";
23
25
  import api from "../libs/api.js";
24
- import { formatBNStr, formatDateTime, formatError } from "../libs/util.js";
26
+ import { formatBNStr, formatDateTime, formatError, lazyLoad } from "../libs/util.js";
25
27
  import CheckoutForm from "./form.js";
26
28
  const donationCache = {};
27
29
  const createOrUpdateDonation = (settings, livemode = true) => {
@@ -122,22 +124,90 @@ function SupporterTable({ supporters = [], total = 0, currency, method }) {
122
124
  )) }) })
123
125
  ] });
124
126
  }
125
- export default function CheckoutDonate({ settings, livemode, timeout, onPaid, onError }) {
127
+ function SupporterSimple({ supporters = [], total = 0, currency, method }) {
128
+ const { t } = useLocaleContext();
129
+ const customers = uniqBy(supporters, "customer_did");
130
+ return /* @__PURE__ */ jsxs(
131
+ Box,
132
+ {
133
+ display: "flex",
134
+ alignItems: "center",
135
+ sx: {
136
+ ".MuiAvatar-root": {
137
+ width: "32px",
138
+ height: "32px"
139
+ }
140
+ },
141
+ gap: {
142
+ xs: 0.5,
143
+ sm: 1
144
+ },
145
+ children: [
146
+ /* @__PURE__ */ jsx(AvatarGroup, { total, max: 10, children: customers.map((x) => /* @__PURE__ */ jsx(
147
+ Avatar,
148
+ {
149
+ title: x.customer?.name,
150
+ src: `/.well-known/service/user/avatar/${x.customer?.did}?imageFilter=resize&w=48&h=48`,
151
+ variant: "circular",
152
+ sx: { width: 32, height: 32 }
153
+ },
154
+ x.id
155
+ )) }),
156
+ /* @__PURE__ */ jsx(Typography, { component: "p", color: "text.secondary", children: t("payment.checkout.donation.simpleSummary", { total }) })
157
+ ]
158
+ }
159
+ );
160
+ }
161
+ function useDonation(settings, livemode = true, mode = "default") {
126
162
  const [state, setState] = useSetState({
127
163
  open: false,
128
164
  supporterLoaded: false,
129
165
  exist: false
130
166
  });
131
- const donation = useRequest(() => createOrUpdateDonation(settings, livemode));
167
+ const donation = useRequest(() => createOrUpdateDonation(settings, livemode), {
168
+ manual: true,
169
+ loadingDelay: 300
170
+ });
132
171
  const supporters = useRequest(
133
- () => donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({})
172
+ () => donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({}),
173
+ {
174
+ manual: true,
175
+ loadingDelay: 300
176
+ }
134
177
  );
178
+ useMount(() => {
179
+ if (mode !== "inline") {
180
+ lazyLoad(() => {
181
+ donation.run();
182
+ supporters.run();
183
+ });
184
+ }
185
+ });
135
186
  useEffect(() => {
136
187
  if (donation.data && state.supporterLoaded === false) {
137
188
  setState({ supporterLoaded: true });
138
189
  supporters.runAsync().catch(console.error);
139
190
  }
140
191
  }, [donation.data]);
192
+ return {
193
+ donation,
194
+ supporters,
195
+ state,
196
+ setState
197
+ };
198
+ }
199
+ export default function CheckoutDonate({
200
+ settings,
201
+ livemode,
202
+ timeout,
203
+ onPaid,
204
+ onError,
205
+ mode,
206
+ inlineOptions = {}
207
+ }) {
208
+ const { state, setState, donation, supporters } = useDonation(settings, livemode, mode);
209
+ const [anchorEl, setAnchorEl] = useState(null);
210
+ const [popoverOpen, setPopoverOpen] = useState(false);
141
211
  const handlePaid = (...args) => {
142
212
  if (onPaid) {
143
213
  onPaid(...args);
@@ -150,59 +220,138 @@ export default function CheckoutDonate({ settings, livemode, timeout, onPaid, on
150
220
  if (donation.error) {
151
221
  return /* @__PURE__ */ jsx(Alert, { severity: "error", children: formatError(donation.error) });
152
222
  }
153
- if (donation.loading || !donation.data) {
154
- return null;
155
- }
156
- return /* @__PURE__ */ jsxs(
157
- Box,
158
- {
159
- sx: { width: "100%", minWidth: 300, maxWidth: 720 },
160
- display: "flex",
161
- flexDirection: "column",
162
- alignItems: "center",
163
- gap: { xs: 1, sm: 2 },
164
- children: [
165
- /* @__PURE__ */ jsx(
166
- Button,
167
- {
168
- size: settings.appearance?.button?.size || "medium",
169
- color: settings.appearance?.button?.color || "primary",
170
- variant: settings.appearance?.button?.variant || "contained",
171
- onClick: () => setState({ open: true }),
172
- children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
173
- settings.appearance.button.icon,
174
- typeof settings.appearance.button.text === "string" ? /* @__PURE__ */ jsx(Typography, { children: settings.appearance.button.text }) : settings.appearance.button.text
175
- ] })
176
- }
177
- ),
178
- supporters.data && settings.appearance.history.variant === "avatar" && /* @__PURE__ */ jsx(SupporterAvatar, { ...supporters.data }),
179
- supporters.data && settings.appearance.history.variant === "table" && /* @__PURE__ */ jsx(SupporterTable, { ...supporters.data }),
180
- /* @__PURE__ */ jsx(
181
- Dialog,
223
+ const handlePopoverOpen = (event) => {
224
+ donation.run();
225
+ supporters.run();
226
+ setAnchorEl(event.currentTarget);
227
+ setPopoverOpen(true);
228
+ };
229
+ const handlePopoverClose = () => {
230
+ setPopoverOpen(false);
231
+ };
232
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
233
+ mode === "inline" ? /* @__PURE__ */ jsxs(Fragment, { children: [
234
+ /* @__PURE__ */ jsx(
235
+ Button,
236
+ {
237
+ size: settings.appearance?.button?.size || "medium",
238
+ color: settings.appearance?.button?.color || "primary",
239
+ variant: settings.appearance?.button?.variant || "contained",
240
+ ...settings.appearance?.button,
241
+ onClick: handlePopoverOpen,
242
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
243
+ settings.appearance.button.icon,
244
+ typeof settings.appearance.button.text === "string" ? /* @__PURE__ */ jsx(Typography, { sx: { whiteSpace: "nowrap" }, children: settings.appearance.button.text }) : settings.appearance.button.text
245
+ ] })
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsx(
249
+ Popover,
250
+ {
251
+ id: "mouse-over-popper",
252
+ open: popoverOpen,
253
+ anchorEl,
254
+ onClose: handlePopoverClose,
255
+ anchorOrigin: {
256
+ vertical: "top",
257
+ horizontal: "center"
258
+ },
259
+ transformOrigin: {
260
+ vertical: "bottom",
261
+ horizontal: "center"
262
+ },
263
+ children: /* @__PURE__ */ jsxs(
264
+ Box,
265
+ {
266
+ p: 1,
267
+ sx: {
268
+ minWidth: 200
269
+ },
270
+ children: [
271
+ supporters.loading && /* @__PURE__ */ jsx(
272
+ "div",
273
+ {
274
+ style: {
275
+ position: "absolute",
276
+ top: 0,
277
+ left: 0,
278
+ right: 0,
279
+ bottom: 0,
280
+ display: "flex",
281
+ justifyContent: "center",
282
+ alignItems: "center",
283
+ backgroundColor: "rgba(255, 255, 255, 0.7)"
284
+ },
285
+ children: /* @__PURE__ */ jsx(CircularProgress, {})
286
+ }
287
+ ),
288
+ /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, children: [
289
+ /* @__PURE__ */ jsx(SupporterSimple, { ...supporters.data }),
290
+ /* @__PURE__ */ jsx(Button, { ...inlineOptions.button, onClick: () => setState({ open: true }), children: inlineOptions?.button?.text })
291
+ ] })
292
+ ]
293
+ }
294
+ )
295
+ }
296
+ )
297
+ ] }) : /* @__PURE__ */ jsxs(
298
+ Box,
299
+ {
300
+ sx: { width: "100%", minWidth: 300, maxWidth: 720 },
301
+ display: "flex",
302
+ flexDirection: "column",
303
+ alignItems: "center",
304
+ gap: { xs: 1, sm: 2 },
305
+ children: [
306
+ /* @__PURE__ */ jsx(
307
+ Button,
308
+ {
309
+ size: settings.appearance?.button?.size || "medium",
310
+ color: settings.appearance?.button?.color || "primary",
311
+ variant: settings.appearance?.button?.variant || "contained",
312
+ ...settings.appearance?.button,
313
+ onClick: () => setState({ open: true }),
314
+ children: /* @__PURE__ */ jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, children: [
315
+ settings.appearance.button.icon,
316
+ typeof settings.appearance.button.text === "string" ? /* @__PURE__ */ jsx(Typography, { children: settings.appearance.button.text }) : settings.appearance.button.text
317
+ ] })
318
+ }
319
+ ),
320
+ supporters.data && settings.appearance.history.variant === "avatar" && /* @__PURE__ */ jsx(SupporterAvatar, { ...supporters.data }),
321
+ supporters.data && settings.appearance.history.variant === "table" && /* @__PURE__ */ jsx(SupporterTable, { ...supporters.data })
322
+ ]
323
+ }
324
+ ),
325
+ donation.data && /* @__PURE__ */ jsx(
326
+ Dialog,
327
+ {
328
+ open: state.open,
329
+ title: settings.title,
330
+ maxWidth: "md",
331
+ showCloseButton: true,
332
+ disableEscapeKeyDown: true,
333
+ onClose: (e, reason) => setState({ open: reason === "backdropClick" }),
334
+ children: /* @__PURE__ */ jsx(Box, { sx: { mb: 1, mt: -2, height: "100%" }, children: /* @__PURE__ */ jsx(
335
+ CheckoutForm,
182
336
  {
183
- open: state.open,
184
- title: settings.title,
185
- maxWidth: "md",
186
- showCloseButton: true,
187
- disableEscapeKeyDown: true,
188
- onClose: (e, reason) => setState({ open: reason === "backdropClick" }),
189
- children: /* @__PURE__ */ jsx(Box, { sx: { mb: 1, mt: -2, height: "100%" }, children: /* @__PURE__ */ jsx(
190
- CheckoutForm,
191
- {
192
- id: donation.data.id,
193
- onPaid: handlePaid,
194
- onError,
195
- action: settings.appearance?.button?.text,
196
- mode: "inline"
197
- }
198
- ) })
337
+ id: donation.data?.id,
338
+ onPaid: handlePaid,
339
+ onError,
340
+ action: settings.appearance?.button?.text,
341
+ mode: "inline"
199
342
  }
200
- )
201
- ]
202
- }
203
- );
343
+ ) })
344
+ }
345
+ )
346
+ ] });
204
347
  }
205
348
  CheckoutDonate.defaultProps = {
206
349
  livemode: true,
207
- timeout: 5e3
350
+ timeout: 5e3,
351
+ mode: "default",
352
+ inlineOptions: {
353
+ button: {
354
+ text: "Support"
355
+ }
356
+ }
208
357
  };
package/es/libs/util.d.ts CHANGED
@@ -68,3 +68,4 @@ export declare const getTxLink: (method: TPaymentMethod, details: PaymentDetails
68
68
  gas: any;
69
69
  };
70
70
  export declare function getQueryParams(url: string): Record<string, string>;
71
+ export declare function lazyLoad(lazyRun: () => void): void;
package/es/libs/util.js CHANGED
@@ -615,3 +615,24 @@ export function getQueryParams(url) {
615
615
  });
616
616
  return queryParams;
617
617
  }
618
+ export function lazyLoad(lazyRun) {
619
+ if ("requestIdleCallback" in window) {
620
+ window.requestIdleCallback(() => {
621
+ lazyRun();
622
+ });
623
+ return;
624
+ }
625
+ if (document.readyState === "complete") {
626
+ lazyRun();
627
+ return;
628
+ }
629
+ if ("onload" in window) {
630
+ window.onload = () => {
631
+ lazyRun();
632
+ };
633
+ return;
634
+ }
635
+ setTimeout(() => {
636
+ lazyRun();
637
+ }, 0);
638
+ }
package/es/locales/en.js CHANGED
@@ -113,7 +113,8 @@ export default flat({
113
113
  between: "Please enter an amount between {min} and {max}.",
114
114
  custom: "Custom Amount",
115
115
  select: "Select Amount",
116
- summary: "{total} supporters"
116
+ summary: "{total} supporters",
117
+ simpleSummary: "{total} supporters"
117
118
  },
118
119
  cardPay: "{action} with card",
119
120
  empty: "No thing to pay",
package/es/locales/zh.js CHANGED
@@ -113,7 +113,8 @@ export default flat({
113
113
  between: "\u91D1\u989D\u5FC5\u987B\u5927\u4E8E {min} \u4E14\u5C0F\u4E8E {max}",
114
114
  custom: "\u8F93\u5165\u91D1\u989D",
115
115
  select: "\u9009\u62E9\u91D1\u989D",
116
- summary: "\u5DF2\u7ECF\u6709 {total} \u4EBA\u652F\u6301"
116
+ summary: "\u5DF2\u7ECF\u6709 {total} \u4EBA\u652F\u6301",
117
+ simpleSummary: "{total} \u4EBA\u652F\u6301"
117
118
  },
118
119
  cardPay: "\u4F7F\u7528\u5361\u7247{action}",
119
120
  empty: "\u6CA1\u6709\u53EF\u652F\u4ED8\u7684\u9879\u76EE",
@@ -1,5 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import type { DonationSettings, TCheckoutSessionExpanded, TPaymentCurrency, TPaymentMethod } from '@blocklet/payment-types';
3
+ import { ButtonProps as MUIButtonProps } from '@mui/material';
3
4
  import { CheckoutProps } from '../types';
4
5
  export type DonateHistory = {
5
6
  supporters: TCheckoutSessionExpanded[];
@@ -7,16 +8,29 @@ export type DonateHistory = {
7
8
  method: TPaymentMethod;
8
9
  total: number;
9
10
  };
11
+ export interface ButtonType extends Omit<MUIButtonProps, 'text'> {
12
+ text: string;
13
+ }
10
14
  export type DonateProps = Pick<CheckoutProps, 'onPaid' | 'onError'> & {
11
15
  settings: DonationSettings;
12
16
  livemode?: boolean;
13
17
  timeout?: number;
18
+ mode?: 'inline' | 'default';
19
+ inlineOptions?: {
20
+ button?: ButtonType;
21
+ };
14
22
  };
15
- declare function CheckoutDonate({ settings, livemode, timeout, onPaid, onError }: DonateProps): import("react").JSX.Element | null;
23
+ declare function CheckoutDonate({ settings, livemode, timeout, onPaid, onError, mode, inlineOptions, }: DonateProps): import("react").JSX.Element;
16
24
  declare namespace CheckoutDonate {
17
25
  var defaultProps: {
18
26
  livemode: boolean;
19
27
  timeout: number;
28
+ mode: string;
29
+ inlineOptions: {
30
+ button: {
31
+ text: string;
32
+ };
33
+ };
20
34
  };
21
35
  }
22
36
  export default CheckoutDonate;
@@ -187,20 +187,72 @@ function SupporterTable({
187
187
  })]
188
188
  });
189
189
  }
190
- function CheckoutDonate({
191
- settings,
192
- livemode,
193
- timeout,
194
- onPaid,
195
- onError
190
+ function SupporterSimple({
191
+ supporters = [],
192
+ total = 0,
193
+ currency,
194
+ method
196
195
  }) {
196
+ const {
197
+ t
198
+ } = (0, _context.useLocaleContext)();
199
+ const customers = (0, _unionBy.default)(supporters, "customer_did");
200
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
201
+ display: "flex",
202
+ alignItems: "center",
203
+ sx: {
204
+ ".MuiAvatar-root": {
205
+ width: "32px",
206
+ height: "32px"
207
+ }
208
+ },
209
+ gap: {
210
+ xs: 0.5,
211
+ sm: 1
212
+ },
213
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.AvatarGroup, {
214
+ total,
215
+ max: 10,
216
+ children: customers.map(x => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Avatar, {
217
+ title: x.customer?.name,
218
+ src: `/.well-known/service/user/avatar/${x.customer?.did}?imageFilter=resize&w=48&h=48`,
219
+ variant: "circular",
220
+ sx: {
221
+ width: 32,
222
+ height: 32
223
+ }
224
+ }, x.id))
225
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
226
+ component: "p",
227
+ color: "text.secondary",
228
+ children: t("payment.checkout.donation.simpleSummary", {
229
+ total
230
+ })
231
+ })]
232
+ });
233
+ }
234
+ function useDonation(settings, livemode = true, mode = "default") {
197
235
  const [state, setState] = (0, _ahooks.useSetState)({
198
236
  open: false,
199
237
  supporterLoaded: false,
200
238
  exist: false
201
239
  });
202
- const donation = (0, _ahooks.useRequest)(() => createOrUpdateDonation(settings, livemode));
203
- const supporters = (0, _ahooks.useRequest)(() => donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({}));
240
+ const donation = (0, _ahooks.useRequest)(() => createOrUpdateDonation(settings, livemode), {
241
+ manual: true,
242
+ loadingDelay: 300
243
+ });
244
+ const supporters = (0, _ahooks.useRequest)(() => donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({}), {
245
+ manual: true,
246
+ loadingDelay: 300
247
+ });
248
+ (0, _ahooks.useMount)(() => {
249
+ if (mode !== "inline") {
250
+ (0, _util.lazyLoad)(() => {
251
+ donation.run();
252
+ supporters.run();
253
+ });
254
+ }
255
+ });
204
256
  (0, _react.useEffect)(() => {
205
257
  if (donation.data && state.supporterLoaded === false) {
206
258
  setState({
@@ -209,6 +261,30 @@ function CheckoutDonate({
209
261
  supporters.runAsync().catch(console.error);
210
262
  }
211
263
  }, [donation.data]);
264
+ return {
265
+ donation,
266
+ supporters,
267
+ state,
268
+ setState
269
+ };
270
+ }
271
+ function CheckoutDonate({
272
+ settings,
273
+ livemode,
274
+ timeout,
275
+ onPaid,
276
+ onError,
277
+ mode,
278
+ inlineOptions = {}
279
+ }) {
280
+ const {
281
+ state,
282
+ setState,
283
+ donation,
284
+ supporters
285
+ } = useDonation(settings, livemode, mode);
286
+ const [anchorEl, setAnchorEl] = (0, _react.useState)(null);
287
+ const [popoverOpen, setPopoverOpen] = (0, _react.useState)(false);
212
288
  const handlePaid = (...args) => {
213
289
  if (onPaid) {
214
290
  onPaid(...args);
@@ -226,42 +302,117 @@ function CheckoutDonate({
226
302
  children: (0, _util.formatError)(donation.error)
227
303
  });
228
304
  }
229
- if (donation.loading || !donation.data) {
230
- return null;
231
- }
232
- return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
233
- sx: {
234
- width: "100%",
235
- minWidth: 300,
236
- maxWidth: 720
237
- },
238
- display: "flex",
239
- flexDirection: "column",
240
- alignItems: "center",
241
- gap: {
242
- xs: 1,
243
- sm: 2
244
- },
245
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
246
- size: settings.appearance?.button?.size || "medium",
247
- color: settings.appearance?.button?.color || "primary",
248
- variant: settings.appearance?.button?.variant || "contained",
249
- onClick: () => setState({
250
- open: true
251
- }),
252
- children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
253
- direction: "row",
254
- alignItems: "center",
255
- spacing: 0.5,
256
- children: [settings.appearance.button.icon, typeof settings.appearance.button.text === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
257
- children: settings.appearance.button.text
258
- }) : settings.appearance.button.text]
259
- })
260
- }), supporters.data && settings.appearance.history.variant === "avatar" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterAvatar, {
261
- ...supporters.data
262
- }), supporters.data && settings.appearance.history.variant === "table" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterTable, {
263
- ...supporters.data
264
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, {
305
+ const handlePopoverOpen = event => {
306
+ donation.run();
307
+ supporters.run();
308
+ setAnchorEl(event.currentTarget);
309
+ setPopoverOpen(true);
310
+ };
311
+ const handlePopoverClose = () => {
312
+ setPopoverOpen(false);
313
+ };
314
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
315
+ children: [mode === "inline" ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
316
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
317
+ size: settings.appearance?.button?.size || "medium",
318
+ color: settings.appearance?.button?.color || "primary",
319
+ variant: settings.appearance?.button?.variant || "contained",
320
+ ...settings.appearance?.button,
321
+ onClick: handlePopoverOpen,
322
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
323
+ direction: "row",
324
+ alignItems: "center",
325
+ spacing: 0.5,
326
+ children: [settings.appearance.button.icon, typeof settings.appearance.button.text === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
327
+ sx: {
328
+ whiteSpace: "nowrap"
329
+ },
330
+ children: settings.appearance.button.text
331
+ }) : settings.appearance.button.text]
332
+ })
333
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Popover, {
334
+ id: "mouse-over-popper",
335
+ open: popoverOpen,
336
+ anchorEl,
337
+ onClose: handlePopoverClose,
338
+ anchorOrigin: {
339
+ vertical: "top",
340
+ horizontal: "center"
341
+ },
342
+ transformOrigin: {
343
+ vertical: "bottom",
344
+ horizontal: "center"
345
+ },
346
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
347
+ p: 1,
348
+ sx: {
349
+ minWidth: 200
350
+ },
351
+ children: [supporters.loading && /* @__PURE__ */(0, _jsxRuntime.jsx)("div", {
352
+ style: {
353
+ position: "absolute",
354
+ top: 0,
355
+ left: 0,
356
+ right: 0,
357
+ bottom: 0,
358
+ display: "flex",
359
+ justifyContent: "center",
360
+ alignItems: "center",
361
+ backgroundColor: "rgba(255, 255, 255, 0.7)"
362
+ },
363
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.CircularProgress, {})
364
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
365
+ display: "flex",
366
+ alignItems: "center",
367
+ justifyContent: "space-between",
368
+ gap: 2,
369
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterSimple, {
370
+ ...supporters.data
371
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
372
+ ...inlineOptions.button,
373
+ onClick: () => setState({
374
+ open: true
375
+ }),
376
+ children: inlineOptions?.button?.text
377
+ })]
378
+ })]
379
+ })
380
+ })]
381
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
382
+ sx: {
383
+ width: "100%",
384
+ minWidth: 300,
385
+ maxWidth: 720
386
+ },
387
+ display: "flex",
388
+ flexDirection: "column",
389
+ alignItems: "center",
390
+ gap: {
391
+ xs: 1,
392
+ sm: 2
393
+ },
394
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
395
+ size: settings.appearance?.button?.size || "medium",
396
+ color: settings.appearance?.button?.color || "primary",
397
+ variant: settings.appearance?.button?.variant || "contained",
398
+ ...settings.appearance?.button,
399
+ onClick: () => setState({
400
+ open: true
401
+ }),
402
+ children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
403
+ direction: "row",
404
+ alignItems: "center",
405
+ spacing: 0.5,
406
+ children: [settings.appearance.button.icon, typeof settings.appearance.button.text === "string" ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
407
+ children: settings.appearance.button.text
408
+ }) : settings.appearance.button.text]
409
+ })
410
+ }), supporters.data && settings.appearance.history.variant === "avatar" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterAvatar, {
411
+ ...supporters.data
412
+ }), supporters.data && settings.appearance.history.variant === "table" && /* @__PURE__ */(0, _jsxRuntime.jsx)(SupporterTable, {
413
+ ...supporters.data
414
+ })]
415
+ }), donation.data && /* @__PURE__ */(0, _jsxRuntime.jsx)(_Dialog.default, {
265
416
  open: state.open,
266
417
  title: settings.title,
267
418
  maxWidth: "md",
@@ -277,7 +428,7 @@ function CheckoutDonate({
277
428
  height: "100%"
278
429
  },
279
430
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_form.default, {
280
- id: donation.data.id,
431
+ id: donation.data?.id,
281
432
  onPaid: handlePaid,
282
433
  onError,
283
434
  action: settings.appearance?.button?.text,
@@ -289,5 +440,11 @@ function CheckoutDonate({
289
440
  }
290
441
  CheckoutDonate.defaultProps = {
291
442
  livemode: true,
292
- timeout: 5e3
443
+ timeout: 5e3,
444
+ mode: "default",
445
+ inlineOptions: {
446
+ button: {
447
+ text: "Support"
448
+ }
449
+ }
293
450
  };
@@ -68,3 +68,4 @@ export declare const getTxLink: (method: TPaymentMethod, details: PaymentDetails
68
68
  gas: any;
69
69
  };
70
70
  export declare function getQueryParams(url: string): Record<string, string>;
71
+ export declare function lazyLoad(lazyRun: () => void): void;
package/lib/libs/util.js CHANGED
@@ -38,6 +38,7 @@ exports.getTxLink = exports.getSubscriptionTimeSummary = void 0;
38
38
  exports.getWebhookStatusColor = getWebhookStatusColor;
39
39
  exports.isPaymentKitMounted = void 0;
40
40
  exports.isValidCountry = isValidCountry;
41
+ exports.lazyLoad = lazyLoad;
41
42
  exports.mergeExtraParams = void 0;
42
43
  exports.sleep = sleep;
43
44
  exports.stopEvent = stopEvent;
@@ -734,4 +735,25 @@ function getQueryParams(url) {
734
735
  queryParams[key] = value;
735
736
  });
736
737
  return queryParams;
738
+ }
739
+ function lazyLoad(lazyRun) {
740
+ if ("requestIdleCallback" in window) {
741
+ window.requestIdleCallback(() => {
742
+ lazyRun();
743
+ });
744
+ return;
745
+ }
746
+ if (document.readyState === "complete") {
747
+ lazyRun();
748
+ return;
749
+ }
750
+ if ("onload" in window) {
751
+ window.onload = () => {
752
+ lazyRun();
753
+ };
754
+ return;
755
+ }
756
+ setTimeout(() => {
757
+ lazyRun();
758
+ }, 0);
737
759
  }
package/lib/locales/en.js CHANGED
@@ -120,7 +120,8 @@ module.exports = (0, _flat.default)({
120
120
  between: "Please enter an amount between {min} and {max}.",
121
121
  custom: "Custom Amount",
122
122
  select: "Select Amount",
123
- summary: "{total} supporters"
123
+ summary: "{total} supporters",
124
+ simpleSummary: "{total} supporters"
124
125
  },
125
126
  cardPay: "{action} with card",
126
127
  empty: "No thing to pay",
package/lib/locales/zh.js CHANGED
@@ -120,7 +120,8 @@ module.exports = (0, _flat.default)({
120
120
  between: "\u91D1\u989D\u5FC5\u987B\u5927\u4E8E {min} \u4E14\u5C0F\u4E8E {max}",
121
121
  custom: "\u8F93\u5165\u91D1\u989D",
122
122
  select: "\u9009\u62E9\u91D1\u989D",
123
- summary: "\u5DF2\u7ECF\u6709 {total} \u4EBA\u652F\u6301"
123
+ summary: "\u5DF2\u7ECF\u6709 {total} \u4EBA\u652F\u6301",
124
+ simpleSummary: "{total} \u4EBA\u652F\u6301"
124
125
  },
125
126
  cardPay: "\u4F7F\u7528\u5361\u7247{action}",
126
127
  empty: "\u6CA1\u6709\u53EF\u652F\u4ED8\u7684\u9879\u76EE",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.13.291",
3
+ "version": "1.13.293",
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.291",
94
+ "@blocklet/payment-types": "1.13.293",
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": "6e2ff4e102d46a29b5808e9c198e30ad3d3e9e04"
123
+ "gitHead": "d2f0dd9b6c3c0ce4c4c8a1c7f1d2cc5e8a332e5c"
124
124
  }
@@ -14,22 +14,25 @@ import {
14
14
  AvatarGroup,
15
15
  Box,
16
16
  Button,
17
+ CircularProgress,
17
18
  Hidden,
19
+ Popover,
18
20
  Stack,
19
21
  Table,
20
22
  TableBody,
21
23
  TableCell,
22
24
  TableRow,
23
25
  Typography,
26
+ ButtonProps as MUIButtonProps,
24
27
  } from '@mui/material';
25
- import { useRequest, useSetState } from 'ahooks';
28
+ import { useMount, useRequest, useSetState } from 'ahooks';
26
29
  import omit from 'lodash/omit';
27
30
  import uniqBy from 'lodash/unionBy';
28
- import { useEffect } from 'react';
31
+ import { useEffect, useState } from 'react';
29
32
 
30
33
  import TxLink from '../components/blockchain/tx';
31
34
  import api from '../libs/api';
32
- import { formatBNStr, formatDateTime, formatError } from '../libs/util';
35
+ import { formatBNStr, formatDateTime, formatError, lazyLoad } from '../libs/util';
33
36
  import { CheckoutProps } from '../types';
34
37
  import CheckoutForm from './form';
35
38
 
@@ -39,10 +42,19 @@ export type DonateHistory = {
39
42
  method: TPaymentMethod;
40
43
  total: number;
41
44
  };
45
+
46
+ export interface ButtonType extends Omit<MUIButtonProps, 'text'> {
47
+ text: string;
48
+ }
49
+
42
50
  export type DonateProps = Pick<CheckoutProps, 'onPaid' | 'onError'> & {
43
51
  settings: DonationSettings;
44
52
  livemode?: boolean;
45
53
  timeout?: number;
54
+ mode?: 'inline' | 'default';
55
+ inlineOptions?: {
56
+ button?: ButtonType;
57
+ };
46
58
  };
47
59
 
48
60
  const donationCache: { [key: string]: Promise<TPaymentLink> } = {};
@@ -174,24 +186,100 @@ function SupporterTable({ supporters = [], total = 0, currency, method }: Donate
174
186
  );
175
187
  }
176
188
 
177
- export default function CheckoutDonate({ settings, livemode, timeout, onPaid, onError }: DonateProps) {
189
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
190
+ function SupporterSimple({ supporters = [], total = 0, currency, method }: DonateHistory) {
191
+ const { t } = useLocaleContext();
192
+ const customers = uniqBy(supporters, 'customer_did');
193
+ return (
194
+ <Box
195
+ display="flex"
196
+ alignItems="center"
197
+ sx={{
198
+ '.MuiAvatar-root': {
199
+ width: '32px',
200
+ height: '32px',
201
+ },
202
+ }}
203
+ gap={{
204
+ xs: 0.5,
205
+ sm: 1,
206
+ }}>
207
+ <AvatarGroup total={total} max={10}>
208
+ {customers.map((x) => (
209
+ <Avatar
210
+ key={x.id}
211
+ title={x.customer?.name}
212
+ src={`/.well-known/service/user/avatar/${x.customer?.did}?imageFilter=resize&w=48&h=48`}
213
+ variant="circular"
214
+ sx={{ width: 32, height: 32 }}
215
+ />
216
+ ))}
217
+ </AvatarGroup>
218
+ <Typography component="p" color="text.secondary">
219
+ {t('payment.checkout.donation.simpleSummary', { total })}
220
+ </Typography>
221
+ </Box>
222
+ );
223
+ }
224
+
225
+ function useDonation(settings: DonationSettings, livemode: boolean = true, mode = 'default') {
178
226
  const [state, setState] = useSetState({
179
227
  open: false,
180
228
  supporterLoaded: false,
181
229
  exist: false,
182
230
  });
183
-
184
- const donation = useRequest(() => createOrUpdateDonation(settings, livemode));
185
- const supporters = useRequest(() =>
186
- donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({})
231
+ const donation = useRequest(() => createOrUpdateDonation(settings, livemode), {
232
+ manual: true,
233
+ loadingDelay: 300,
234
+ });
235
+ const supporters = useRequest(
236
+ () => (donation.data ? fetchSupporters(donation.data.id, livemode) : Promise.resolve({})),
237
+ {
238
+ manual: true,
239
+ loadingDelay: 300,
240
+ }
187
241
  );
188
242
 
243
+ useMount(() => {
244
+ if (mode !== 'inline') {
245
+ lazyLoad(() => {
246
+ donation.run();
247
+ supporters.run();
248
+ });
249
+ }
250
+ // eslint-disable-next-line react-hooks/exhaustive-deps
251
+ });
252
+
189
253
  useEffect(() => {
190
254
  if (donation.data && state.supporterLoaded === false) {
191
255
  setState({ supporterLoaded: true });
192
256
  supporters.runAsync().catch(console.error);
193
257
  }
194
- }, [donation.data]); // eslint-disable-line
258
+ // eslint-disable-next-line react-hooks/exhaustive-deps
259
+ }, [donation.data]);
260
+
261
+ return {
262
+ donation,
263
+ supporters,
264
+ state,
265
+ setState,
266
+ };
267
+ }
268
+
269
+ export default function CheckoutDonate({
270
+ settings,
271
+ livemode,
272
+ timeout,
273
+ onPaid,
274
+ onError,
275
+ mode,
276
+ inlineOptions = {},
277
+ }: DonateProps) {
278
+ // eslint-disable-line
279
+ const { state, setState, donation, supporters } = useDonation(settings, livemode, mode);
280
+
281
+ const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
282
+ const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
195
283
 
196
284
  const handlePaid = (...args: any[]) => {
197
285
  if (onPaid) {
@@ -210,59 +298,139 @@ export default function CheckoutDonate({ settings, livemode, timeout, onPaid, on
210
298
  return <Alert severity="error">{formatError(donation.error)}</Alert>;
211
299
  }
212
300
 
213
- if (donation.loading || !donation.data) {
214
- return null;
215
- }
301
+ const handlePopoverOpen = (event: any) => {
302
+ donation.run();
303
+ supporters.run();
304
+ setAnchorEl(event.currentTarget);
305
+ setPopoverOpen(true);
306
+ };
307
+
308
+ const handlePopoverClose = () => {
309
+ setPopoverOpen(false);
310
+ };
216
311
 
217
312
  return (
218
- <Box
219
- sx={{ width: '100%', minWidth: 300, maxWidth: 720 }}
220
- display="flex"
221
- flexDirection="column"
222
- alignItems="center"
223
- gap={{ xs: 1, sm: 2 }}>
224
- <Button
225
- size={(settings.appearance?.button?.size || 'medium') as any}
226
- color={(settings.appearance?.button?.color || 'primary') as any}
227
- variant={(settings.appearance?.button?.variant || 'contained') as any}
228
- onClick={() => setState({ open: true })}>
229
- <Stack direction="row" alignItems="center" spacing={0.5}>
230
- {settings.appearance.button.icon}
231
- {typeof settings.appearance.button.text === 'string' ? (
232
- <Typography>{settings.appearance.button.text}</Typography>
233
- ) : (
234
- settings.appearance.button.text
313
+ <>
314
+ {mode === 'inline' ? (
315
+ <>
316
+ <Button
317
+ size={(settings.appearance?.button?.size || 'medium') as any}
318
+ color={(settings.appearance?.button?.color || 'primary') as any}
319
+ variant={(settings.appearance?.button?.variant || 'contained') as any}
320
+ {...settings.appearance?.button}
321
+ onClick={handlePopoverOpen}>
322
+ <Stack direction="row" alignItems="center" spacing={0.5}>
323
+ {settings.appearance.button.icon}
324
+ {typeof settings.appearance.button.text === 'string' ? (
325
+ <Typography sx={{ whiteSpace: 'nowrap' }}>{settings.appearance.button.text}</Typography>
326
+ ) : (
327
+ settings.appearance.button.text
328
+ )}
329
+ </Stack>
330
+ </Button>
331
+ <Popover
332
+ id="mouse-over-popper"
333
+ open={popoverOpen}
334
+ anchorEl={anchorEl}
335
+ onClose={handlePopoverClose}
336
+ anchorOrigin={{
337
+ vertical: 'top',
338
+ horizontal: 'center',
339
+ }}
340
+ transformOrigin={{
341
+ vertical: 'bottom',
342
+ horizontal: 'center',
343
+ }}>
344
+ <Box
345
+ p={1}
346
+ sx={{
347
+ minWidth: 200,
348
+ }}>
349
+ {supporters.loading && (
350
+ <div
351
+ style={{
352
+ position: 'absolute',
353
+ top: 0,
354
+ left: 0,
355
+ right: 0,
356
+ bottom: 0,
357
+ display: 'flex',
358
+ justifyContent: 'center',
359
+ alignItems: 'center',
360
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
361
+ }}>
362
+ <CircularProgress />
363
+ </div>
364
+ )}
365
+ <Box display="flex" alignItems="center" justifyContent="space-between" gap={2}>
366
+ <SupporterSimple {...(supporters.data as DonateHistory)} />
367
+ <Button {...inlineOptions.button} onClick={() => setState({ open: true })}>
368
+ {inlineOptions?.button?.text}
369
+ </Button>
370
+ </Box>
371
+ </Box>
372
+ </Popover>
373
+ </>
374
+ ) : (
375
+ <Box
376
+ sx={{ width: '100%', minWidth: 300, maxWidth: 720 }}
377
+ display="flex"
378
+ flexDirection="column"
379
+ alignItems="center"
380
+ gap={{ xs: 1, sm: 2 }}>
381
+ <Button
382
+ size={(settings.appearance?.button?.size || 'medium') as any}
383
+ color={(settings.appearance?.button?.color || 'primary') as any}
384
+ variant={(settings.appearance?.button?.variant || 'contained') as any}
385
+ {...settings.appearance?.button}
386
+ onClick={() => setState({ open: true })}>
387
+ <Stack direction="row" alignItems="center" spacing={0.5}>
388
+ {settings.appearance.button.icon}
389
+ {typeof settings.appearance.button.text === 'string' ? (
390
+ <Typography>{settings.appearance.button.text}</Typography>
391
+ ) : (
392
+ settings.appearance.button.text
393
+ )}
394
+ </Stack>
395
+ </Button>
396
+ {supporters.data && settings.appearance.history.variant === 'avatar' && (
397
+ <SupporterAvatar {...(supporters.data as DonateHistory)} />
398
+ )}
399
+ {supporters.data && settings.appearance.history.variant === 'table' && (
400
+ <SupporterTable {...(supporters.data as DonateHistory)} />
235
401
  )}
236
- </Stack>
237
- </Button>
238
- {supporters.data && settings.appearance.history.variant === 'avatar' && (
239
- <SupporterAvatar {...(supporters.data as DonateHistory)} />
402
+ </Box>
240
403
  )}
241
- {supporters.data && settings.appearance.history.variant === 'table' && (
242
- <SupporterTable {...(supporters.data as DonateHistory)} />
404
+ {donation.data && (
405
+ <Dialog
406
+ open={state.open}
407
+ title={settings.title}
408
+ maxWidth="md"
409
+ showCloseButton
410
+ disableEscapeKeyDown
411
+ onClose={(e: any, reason: string) => setState({ open: reason === 'backdropClick' })}>
412
+ <Box sx={{ mb: 1, mt: -2, height: '100%' }}>
413
+ <CheckoutForm
414
+ id={donation.data?.id}
415
+ onPaid={handlePaid}
416
+ onError={onError}
417
+ action={settings.appearance?.button?.text}
418
+ mode="inline"
419
+ />
420
+ </Box>
421
+ </Dialog>
243
422
  )}
244
- <Dialog
245
- open={state.open}
246
- title={settings.title}
247
- maxWidth="md"
248
- showCloseButton
249
- disableEscapeKeyDown
250
- onClose={(e: any, reason: string) => setState({ open: reason === 'backdropClick' })}>
251
- <Box sx={{ mb: 1, mt: -2, height: '100%' }}>
252
- <CheckoutForm
253
- id={donation.data.id}
254
- onPaid={handlePaid}
255
- onError={onError}
256
- action={settings.appearance?.button?.text}
257
- mode="inline"
258
- />
259
- </Box>
260
- </Dialog>
261
- </Box>
423
+ </>
262
424
  );
263
425
  }
264
426
 
265
427
  CheckoutDonate.defaultProps = {
266
428
  livemode: true,
267
429
  timeout: 5000,
430
+ mode: 'default',
431
+ inlineOptions: {
432
+ button: {
433
+ text: 'Support',
434
+ },
435
+ },
268
436
  };
package/src/libs/util.ts CHANGED
@@ -802,3 +802,25 @@ export function getQueryParams(url: string): Record<string, string> {
802
802
  });
803
803
  return queryParams;
804
804
  }
805
+
806
+ export function lazyLoad(lazyRun: () => void) {
807
+ if ('requestIdleCallback' in window) {
808
+ (window as any).requestIdleCallback(() => {
809
+ lazyRun();
810
+ });
811
+ return;
812
+ }
813
+ if (document.readyState === 'complete') {
814
+ lazyRun();
815
+ return;
816
+ }
817
+ if ('onload' in window) {
818
+ (window as any).onload = () => {
819
+ lazyRun();
820
+ };
821
+ return;
822
+ }
823
+ setTimeout(() => {
824
+ lazyRun();
825
+ }, 0);
826
+ }
@@ -117,6 +117,7 @@ export default flat({
117
117
  custom: 'Custom Amount',
118
118
  select: 'Select Amount',
119
119
  summary: '{total} supporters',
120
+ simpleSummary: '{total} supporters',
120
121
  },
121
122
  cardPay: '{action} with card',
122
123
  empty: 'No thing to pay',
@@ -116,6 +116,7 @@ export default flat({
116
116
  custom: '输入金额',
117
117
  select: '选择金额',
118
118
  summary: '已经有 {total} 人支持',
119
+ simpleSummary: '{total} 人支持',
119
120
  },
120
121
  cardPay: '使用卡片{action}',
121
122
  empty: '没有可支付的项目',