@blocklet/payment-react 1.18.3 → 1.18.5
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.d.ts +5 -3
- package/es/checkout/donate.js +109 -36
- package/es/contexts/donate.d.ts +41 -0
- package/es/contexts/donate.js +177 -0
- package/es/contexts/payment.js +3 -16
- package/es/index.d.ts +2 -0
- package/es/index.js +2 -0
- package/es/libs/cache.d.ts +14 -0
- package/es/libs/cache.js +23 -0
- package/es/libs/cached-request.d.ts +17 -0
- package/es/libs/cached-request.js +79 -0
- package/es/libs/util.d.ts +2 -0
- package/es/libs/util.js +11 -0
- package/es/locales/en.js +8 -1
- package/es/locales/zh.js +8 -1
- package/es/payment/skeleton/donation.js +1 -1
- package/lib/checkout/donate.d.ts +5 -3
- package/lib/checkout/donate.js +107 -36
- package/lib/contexts/donate.d.ts +41 -0
- package/lib/contexts/donate.js +187 -0
- package/lib/contexts/payment.js +7 -22
- package/lib/index.d.ts +2 -0
- package/lib/index.js +24 -0
- package/lib/libs/cache.d.ts +14 -0
- package/lib/libs/cache.js +29 -0
- package/lib/libs/cached-request.d.ts +17 -0
- package/lib/libs/cached-request.js +90 -0
- package/lib/libs/util.d.ts +2 -0
- package/lib/libs/util.js +13 -0
- package/lib/locales/en.js +8 -1
- package/lib/locales/zh.js +8 -1
- package/lib/payment/skeleton/donation.js +1 -1
- package/package.json +6 -6
- package/src/checkout/donate.tsx +135 -45
- package/src/contexts/donate.tsx +234 -0
- package/src/contexts/payment.tsx +5 -20
- package/src/index.ts +2 -0
- package/src/libs/cache.ts +33 -0
- package/src/libs/cached-request.ts +103 -0
- package/src/libs/util.ts +13 -0
- package/src/locales/en.tsx +7 -0
- package/src/locales/zh.tsx +7 -0
- package/src/payment/skeleton/donation.tsx +1 -1
package/src/checkout/donate.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
TPaymentCurrency,
|
|
10
10
|
TPaymentLink,
|
|
11
11
|
TPaymentMethod,
|
|
12
|
+
TSetting,
|
|
12
13
|
} from '@blocklet/payment-types';
|
|
13
14
|
import {
|
|
14
15
|
Alert,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
Button,
|
|
19
20
|
CircularProgress,
|
|
20
21
|
Hidden,
|
|
22
|
+
IconButton,
|
|
21
23
|
Popover,
|
|
22
24
|
Stack,
|
|
23
25
|
Table,
|
|
@@ -25,22 +27,33 @@ import {
|
|
|
25
27
|
TableCell,
|
|
26
28
|
TableRow,
|
|
27
29
|
Typography,
|
|
30
|
+
Tooltip,
|
|
28
31
|
type ButtonProps as MUIButtonProps,
|
|
29
32
|
} from '@mui/material';
|
|
30
|
-
import {
|
|
33
|
+
import { useRequest, useSetState } from 'ahooks';
|
|
31
34
|
import omit from 'lodash/omit';
|
|
32
35
|
import uniqBy from 'lodash/unionBy';
|
|
33
|
-
import { useEffect, useState } from 'react';
|
|
36
|
+
import { useEffect, useRef, useState } from 'react';
|
|
37
|
+
import { Settings } from '@mui/icons-material';
|
|
34
38
|
|
|
35
39
|
import TxLink from '../components/blockchain/tx';
|
|
36
40
|
import api from '../libs/api';
|
|
37
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
formatAmount,
|
|
43
|
+
formatBNStr,
|
|
44
|
+
formatDateTime,
|
|
45
|
+
formatError,
|
|
46
|
+
getCustomerAvatar,
|
|
47
|
+
lazyLoad,
|
|
48
|
+
openDonationSettings,
|
|
49
|
+
} from '../libs/util';
|
|
38
50
|
import type { CheckoutProps, PaymentThemeOptions } from '../types';
|
|
39
51
|
import CheckoutForm from './form';
|
|
40
52
|
import { PaymentThemeProvider } from '../theme';
|
|
41
53
|
import { usePaymentContext } from '../contexts/payment';
|
|
42
54
|
import Livemode from '../components/livemode';
|
|
43
55
|
import { useMobile } from '../hooks/mobile';
|
|
56
|
+
import { useDonateContext } from '../contexts/donate';
|
|
44
57
|
|
|
45
58
|
export type DonateHistory = {
|
|
46
59
|
supporters: TCheckoutSessionExpanded[];
|
|
@@ -49,14 +62,19 @@ export type DonateHistory = {
|
|
|
49
62
|
// total?: number;
|
|
50
63
|
totalAmount: string;
|
|
51
64
|
};
|
|
65
|
+
export type RequiredDonationSettings = Pick<
|
|
66
|
+
DonationSettings,
|
|
67
|
+
'target' | 'title' | 'description' | 'reference' | 'beneficiaries'
|
|
68
|
+
>;
|
|
69
|
+
type OptionalDonationSettings = Partial<Omit<DonationSettings, keyof RequiredDonationSettings>>;
|
|
52
70
|
|
|
53
71
|
export interface ButtonType extends Omit<MUIButtonProps, 'text' | 'icon'> {
|
|
54
|
-
text
|
|
72
|
+
text?: string | React.ReactNode;
|
|
55
73
|
icon: React.ReactNode;
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
export type DonateProps = Pick<CheckoutProps, 'onPaid' | 'onError'> & {
|
|
59
|
-
settings:
|
|
77
|
+
settings: RequiredDonationSettings & OptionalDonationSettings;
|
|
60
78
|
livemode?: boolean;
|
|
61
79
|
timeout?: number;
|
|
62
80
|
mode?: 'inline' | 'default' | 'custom';
|
|
@@ -68,7 +86,8 @@ export type DonateProps = Pick<CheckoutProps, 'onPaid' | 'onError'> & {
|
|
|
68
86
|
openDialog: () => void,
|
|
69
87
|
donateTotalAmount: string,
|
|
70
88
|
supporters: DonateHistory,
|
|
71
|
-
loading?: boolean
|
|
89
|
+
loading?: boolean,
|
|
90
|
+
donateSettings?: DonationSettings
|
|
72
91
|
) => React.ReactNode;
|
|
73
92
|
};
|
|
74
93
|
|
|
@@ -274,13 +293,44 @@ function SupporterSimple({ supporters = [], totalAmount = '0', currency, method
|
|
|
274
293
|
);
|
|
275
294
|
}
|
|
276
295
|
|
|
277
|
-
|
|
296
|
+
const defaultDonateAmount = {
|
|
297
|
+
presets: ['1', '5', '10'],
|
|
298
|
+
preset: '1',
|
|
299
|
+
minimum: '0.01',
|
|
300
|
+
maximum: '100',
|
|
301
|
+
custom: true,
|
|
302
|
+
};
|
|
303
|
+
function useDonation(
|
|
304
|
+
settings: RequiredDonationSettings & OptionalDonationSettings,
|
|
305
|
+
livemode: boolean,
|
|
306
|
+
mode = 'default'
|
|
307
|
+
) {
|
|
278
308
|
const [state, setState] = useSetState({
|
|
279
309
|
open: false,
|
|
280
310
|
supporterLoaded: false,
|
|
281
311
|
exist: false,
|
|
282
312
|
});
|
|
283
|
-
const
|
|
313
|
+
const donateContext = useDonateContext();
|
|
314
|
+
const { isMobile } = useMobile();
|
|
315
|
+
const { settings: donateConfig = {} as TSetting } = donateContext || {};
|
|
316
|
+
const donateSettings = {
|
|
317
|
+
...settings,
|
|
318
|
+
amount: settings.amount || donateConfig?.settings?.amount || defaultDonateAmount,
|
|
319
|
+
appearance: {
|
|
320
|
+
button: {
|
|
321
|
+
...(settings?.appearance?.button || {}),
|
|
322
|
+
text: settings?.appearance?.button?.text || donateConfig?.settings?.btnText || 'Donate',
|
|
323
|
+
icon: settings?.appearance?.button?.icon || donateConfig?.settings?.icon || null,
|
|
324
|
+
},
|
|
325
|
+
history: {
|
|
326
|
+
variant: settings?.appearance?.history?.variant || donateConfig?.settings?.historyType || 'avatar',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const hasRequestedRef = useRef(false);
|
|
332
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
333
|
+
const donation = useRequest(() => createOrUpdateDonation(donateSettings, livemode), {
|
|
284
334
|
manual: true,
|
|
285
335
|
loadingDelay: 300,
|
|
286
336
|
});
|
|
@@ -291,16 +341,32 @@ function useDonation(settings: DonationSettings, livemode: boolean, mode = 'defa
|
|
|
291
341
|
loadingDelay: 300,
|
|
292
342
|
}
|
|
293
343
|
);
|
|
344
|
+
const rootMargin = isMobile
|
|
345
|
+
? '50px' // 移动端
|
|
346
|
+
: `${Math.min(window.innerHeight / 2, 300)}px`;
|
|
294
347
|
|
|
295
|
-
|
|
296
|
-
if (mode
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (mode === 'inline') return;
|
|
350
|
+
const element = containerRef.current;
|
|
351
|
+
if (!element) return;
|
|
352
|
+
const observer = new IntersectionObserver(
|
|
353
|
+
([entry]) => {
|
|
354
|
+
if (entry.isIntersecting && !hasRequestedRef.current) {
|
|
355
|
+
hasRequestedRef.current = true;
|
|
356
|
+
lazyLoad(() => {
|
|
357
|
+
donation.run();
|
|
358
|
+
supporters.run();
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{ threshold: 0, rootMargin }
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
observer.observe(element);
|
|
366
|
+
// eslint-disable-next-line consistent-return
|
|
367
|
+
return () => observer.unobserve(element);
|
|
302
368
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
303
|
-
});
|
|
369
|
+
}, [mode]);
|
|
304
370
|
|
|
305
371
|
useEffect(() => {
|
|
306
372
|
if (donation.data && state.supporterLoaded === false) {
|
|
@@ -311,10 +377,13 @@ function useDonation(settings: DonationSettings, livemode: boolean, mode = 'defa
|
|
|
311
377
|
}, [donation.data]);
|
|
312
378
|
|
|
313
379
|
return {
|
|
380
|
+
containerRef,
|
|
314
381
|
donation,
|
|
315
382
|
supporters,
|
|
316
383
|
state,
|
|
317
384
|
setState,
|
|
385
|
+
donateSettings,
|
|
386
|
+
supportUpdateSettings: !!donateContext.settings,
|
|
318
387
|
};
|
|
319
388
|
}
|
|
320
389
|
|
|
@@ -330,13 +399,17 @@ function CheckoutDonateInner({
|
|
|
330
399
|
children,
|
|
331
400
|
}: DonateProps) {
|
|
332
401
|
// eslint-disable-line
|
|
333
|
-
const { state, setState, donation, supporters } = useDonation(
|
|
402
|
+
const { containerRef, state, setState, donation, supporters, donateSettings, supportUpdateSettings } = useDonation(
|
|
403
|
+
settings,
|
|
404
|
+
livemode,
|
|
405
|
+
mode
|
|
406
|
+
);
|
|
334
407
|
const customers = uniqBy((supporters?.data as DonateHistory)?.supporters || [], 'customer_did');
|
|
335
408
|
const { t } = useLocaleContext();
|
|
336
409
|
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
|
|
337
410
|
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
|
|
338
411
|
const { isMobile } = useMobile();
|
|
339
|
-
const { connect } = usePaymentContext();
|
|
412
|
+
const { connect, session } = usePaymentContext();
|
|
340
413
|
|
|
341
414
|
const handlePaid = (...args: any[]) => {
|
|
342
415
|
if (onPaid) {
|
|
@@ -370,20 +443,22 @@ function CheckoutDonateInner({
|
|
|
370
443
|
setState({ open: true });
|
|
371
444
|
};
|
|
372
445
|
|
|
446
|
+
const inlineText = inlineOptions?.button?.text || donateSettings.appearance.button.text;
|
|
447
|
+
|
|
373
448
|
const inlineRender = (
|
|
374
449
|
<>
|
|
375
450
|
<Button
|
|
376
|
-
size={(
|
|
377
|
-
color={(
|
|
378
|
-
variant={(
|
|
379
|
-
{...
|
|
451
|
+
size={(donateSettings.appearance?.button?.size || 'medium') as any}
|
|
452
|
+
color={(donateSettings.appearance?.button?.color || 'primary') as any}
|
|
453
|
+
variant={(donateSettings.appearance?.button?.variant || 'contained') as any}
|
|
454
|
+
{...donateSettings.appearance?.button}
|
|
380
455
|
onClick={handlePopoverOpen}>
|
|
381
456
|
<Stack direction="row" alignItems="center" spacing={0.5}>
|
|
382
|
-
{
|
|
383
|
-
{typeof
|
|
384
|
-
<Typography sx={{ whiteSpace: 'nowrap' }}>{
|
|
457
|
+
{donateSettings.appearance.button.icon}
|
|
458
|
+
{typeof donateSettings.appearance.button.text === 'string' ? (
|
|
459
|
+
<Typography sx={{ whiteSpace: 'nowrap' }}>{donateSettings.appearance.button.text}</Typography>
|
|
385
460
|
) : (
|
|
386
|
-
|
|
461
|
+
donateSettings.appearance.button.text
|
|
387
462
|
)}
|
|
388
463
|
</Stack>
|
|
389
464
|
</Button>
|
|
@@ -425,10 +500,10 @@ function CheckoutDonateInner({
|
|
|
425
500
|
<Button {...inlineOptions.button} onClick={() => startDonate()}>
|
|
426
501
|
<Stack direction="row" alignItems="center" spacing={0.5}>
|
|
427
502
|
{inlineOptions?.button?.icon}
|
|
428
|
-
{typeof
|
|
429
|
-
<Typography sx={{ whiteSpace: 'nowrap' }}>{
|
|
503
|
+
{typeof inlineText === 'string' ? (
|
|
504
|
+
<Typography sx={{ whiteSpace: 'nowrap' }}>{inlineText}</Typography>
|
|
430
505
|
) : (
|
|
431
|
-
|
|
506
|
+
inlineText
|
|
432
507
|
)}
|
|
433
508
|
</Stack>
|
|
434
509
|
</Button>
|
|
@@ -446,24 +521,24 @@ function CheckoutDonateInner({
|
|
|
446
521
|
alignItems="center"
|
|
447
522
|
gap={{ xs: 1, sm: 2 }}>
|
|
448
523
|
<Button
|
|
449
|
-
size={(
|
|
450
|
-
color={(
|
|
451
|
-
variant={(
|
|
452
|
-
{...
|
|
524
|
+
size={(donateSettings.appearance?.button?.size || 'medium') as any}
|
|
525
|
+
color={(donateSettings.appearance?.button?.color || 'primary') as any}
|
|
526
|
+
variant={(donateSettings.appearance?.button?.variant || 'contained') as any}
|
|
527
|
+
{...donateSettings.appearance?.button}
|
|
453
528
|
onClick={() => startDonate()}>
|
|
454
529
|
<Stack direction="row" alignItems="center" spacing={0.5}>
|
|
455
|
-
{
|
|
456
|
-
{typeof
|
|
457
|
-
<Typography>{
|
|
530
|
+
{donateSettings.appearance.button.icon}
|
|
531
|
+
{typeof donateSettings.appearance.button.text === 'string' ? (
|
|
532
|
+
<Typography>{donateSettings.appearance.button.text}</Typography>
|
|
458
533
|
) : (
|
|
459
|
-
|
|
534
|
+
donateSettings.appearance.button.text
|
|
460
535
|
)}
|
|
461
536
|
</Stack>
|
|
462
537
|
</Button>
|
|
463
|
-
{supporters.data &&
|
|
538
|
+
{supporters.data && donateSettings.appearance.history.variant === 'avatar' && (
|
|
464
539
|
<SupporterAvatar {...(supporters.data as DonateHistory)} />
|
|
465
540
|
)}
|
|
466
|
-
{supporters.data &&
|
|
541
|
+
{supporters.data && donateSettings.appearance.history.variant === 'table' && (
|
|
467
542
|
<SupporterTable {...(supporters.data as DonateHistory)} />
|
|
468
543
|
)}
|
|
469
544
|
</Box>
|
|
@@ -482,7 +557,8 @@ function CheckoutDonateInner({
|
|
|
482
557
|
(supporters.data as DonateHistory)?.currency?.decimal
|
|
483
558
|
)} ${(supporters.data as DonateHistory)?.currency?.symbol}`,
|
|
484
559
|
(supporters.data as DonateHistory) || {},
|
|
485
|
-
!!supporters.loading
|
|
560
|
+
!!supporters.loading,
|
|
561
|
+
donateSettings
|
|
486
562
|
)}
|
|
487
563
|
</>
|
|
488
564
|
) : (
|
|
@@ -494,8 +570,10 @@ function CheckoutDonateInner({
|
|
|
494
570
|
return defaultRender;
|
|
495
571
|
};
|
|
496
572
|
|
|
573
|
+
const isAdmin = ['owner', 'admin'].includes(session?.user?.role);
|
|
574
|
+
|
|
497
575
|
return (
|
|
498
|
-
|
|
576
|
+
<div ref={containerRef}>
|
|
499
577
|
{renderInnerView()}
|
|
500
578
|
{donation.data && (
|
|
501
579
|
<Dialog
|
|
@@ -503,9 +581,21 @@ function CheckoutDonateInner({
|
|
|
503
581
|
title={
|
|
504
582
|
<Box display="flex" alignItems="center" gap={0.5}>
|
|
505
583
|
<Typography variant="h3" sx={{ maxWidth: 320, textOverflow: 'ellipsis', overflow: 'hidden' }}>
|
|
506
|
-
{
|
|
584
|
+
{donateSettings.title}
|
|
507
585
|
</Typography>
|
|
508
|
-
{
|
|
586
|
+
{supportUpdateSettings && isAdmin && (
|
|
587
|
+
<Tooltip title={t('payment.checkout.donation.configTip')} placement="bottom">
|
|
588
|
+
<IconButton
|
|
589
|
+
size="small"
|
|
590
|
+
onClick={(e) => {
|
|
591
|
+
e.stopPropagation();
|
|
592
|
+
openDonationSettings(true);
|
|
593
|
+
}}>
|
|
594
|
+
<Settings fontSize="small" sx={{ ml: -0.5 }} />
|
|
595
|
+
</IconButton>
|
|
596
|
+
</Tooltip>
|
|
597
|
+
)}
|
|
598
|
+
{!donation.data.livemode && <Livemode sx={{ width: 'fit-content', ml: 0.5 }} />}
|
|
509
599
|
</Box>
|
|
510
600
|
}
|
|
511
601
|
maxWidth="md"
|
|
@@ -552,7 +642,7 @@ function CheckoutDonateInner({
|
|
|
552
642
|
id={donation.data?.id}
|
|
553
643
|
onPaid={handlePaid}
|
|
554
644
|
onError={onError}
|
|
555
|
-
action={
|
|
645
|
+
action={donateSettings.appearance?.button?.text}
|
|
556
646
|
mode="inline"
|
|
557
647
|
theme={theme}
|
|
558
648
|
formType="donation"
|
|
@@ -580,7 +670,7 @@ function CheckoutDonateInner({
|
|
|
580
670
|
</Box>
|
|
581
671
|
</Dialog>
|
|
582
672
|
)}
|
|
583
|
-
|
|
673
|
+
</div>
|
|
584
674
|
);
|
|
585
675
|
}
|
|
586
676
|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { TSetting } from '@blocklet/payment-types';
|
|
2
|
+
import { useRequest } from 'ahooks';
|
|
3
|
+
import type { Axios } from 'axios';
|
|
4
|
+
import { createContext, useContext, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
7
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
8
|
+
import { Button, Stack, Typography } from '@mui/material';
|
|
9
|
+
import api from '../libs/api';
|
|
10
|
+
import { formatError, getPaymentKitComponent, openDonationSettings } from '../libs/util';
|
|
11
|
+
import { CachedRequest } from '../libs/cached-request';
|
|
12
|
+
import ConfirmDialog from '../components/confirm';
|
|
13
|
+
import { usePaymentContext } from './payment';
|
|
14
|
+
|
|
15
|
+
export interface DonateConfigSettings {
|
|
16
|
+
amount?: {
|
|
17
|
+
presets?: string[];
|
|
18
|
+
preset?: string;
|
|
19
|
+
custom: boolean;
|
|
20
|
+
minimum?: string;
|
|
21
|
+
maximum?: string;
|
|
22
|
+
};
|
|
23
|
+
btnText?: string;
|
|
24
|
+
historyType?: 'table' | 'avatar';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DonateContextType = {
|
|
28
|
+
settings: TSetting;
|
|
29
|
+
refresh: (forceRefresh?: boolean) => void;
|
|
30
|
+
updateSettings: (newSettings: DonateConfigSettings) => Promise<void>;
|
|
31
|
+
api: Axios;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DonateContextProps = {
|
|
35
|
+
mountLocation: string;
|
|
36
|
+
description: string;
|
|
37
|
+
defaultSettings?: DonateConfigSettings;
|
|
38
|
+
children: any;
|
|
39
|
+
active?: boolean;
|
|
40
|
+
enableDonate?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
const DonateContext = createContext<DonateContextType>({ api });
|
|
45
|
+
const { Provider, Consumer } = DonateContext;
|
|
46
|
+
|
|
47
|
+
const fetchDonateSetting = (
|
|
48
|
+
params: {
|
|
49
|
+
mountLocation: string;
|
|
50
|
+
description: string;
|
|
51
|
+
defaultSettings?: DonateConfigSettings;
|
|
52
|
+
active?: boolean;
|
|
53
|
+
componentDid?: string;
|
|
54
|
+
},
|
|
55
|
+
forceRefresh = false
|
|
56
|
+
) => {
|
|
57
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
58
|
+
const cacheKey = `donate-settings-${params.mountLocation}-${livemode}`;
|
|
59
|
+
|
|
60
|
+
const cachedRequest = new CachedRequest(
|
|
61
|
+
cacheKey,
|
|
62
|
+
() =>
|
|
63
|
+
api.post('/api/settings', {
|
|
64
|
+
...params,
|
|
65
|
+
type: 'donate',
|
|
66
|
+
livemode,
|
|
67
|
+
settings: params.defaultSettings,
|
|
68
|
+
}),
|
|
69
|
+
{
|
|
70
|
+
ttl: 1000 * 60 * 60,
|
|
71
|
+
strategy: 'local',
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
return cachedRequest.fetch(forceRefresh);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function DonateProvider({
|
|
78
|
+
mountLocation,
|
|
79
|
+
description,
|
|
80
|
+
defaultSettings = {},
|
|
81
|
+
children,
|
|
82
|
+
active = true,
|
|
83
|
+
enableDonate = false,
|
|
84
|
+
}: DonateContextProps) {
|
|
85
|
+
const { t } = useLocaleContext();
|
|
86
|
+
const [showConfirm, setShowConfirm] = useState(false);
|
|
87
|
+
const { session } = usePaymentContext();
|
|
88
|
+
const isAdmin = ['owner', 'admin'].includes(session?.user?.role);
|
|
89
|
+
const {
|
|
90
|
+
data = {
|
|
91
|
+
settings: {},
|
|
92
|
+
active: true,
|
|
93
|
+
},
|
|
94
|
+
error,
|
|
95
|
+
run,
|
|
96
|
+
loading,
|
|
97
|
+
} = useRequest(
|
|
98
|
+
(forceRender) =>
|
|
99
|
+
fetchDonateSetting(
|
|
100
|
+
{
|
|
101
|
+
mountLocation,
|
|
102
|
+
description,
|
|
103
|
+
defaultSettings,
|
|
104
|
+
active,
|
|
105
|
+
componentDid: window.blocklet?.componentId?.split('/').pop(),
|
|
106
|
+
},
|
|
107
|
+
forceRender
|
|
108
|
+
),
|
|
109
|
+
{
|
|
110
|
+
refreshDeps: [mountLocation],
|
|
111
|
+
onError: (err) => {
|
|
112
|
+
Toast.error(formatError(err));
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const updateSettings = async (newSettings: DonateConfigSettings) => {
|
|
118
|
+
try {
|
|
119
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
120
|
+
await api.put(`/api/settings/${mountLocation}`, {
|
|
121
|
+
livemode,
|
|
122
|
+
settings: newSettings,
|
|
123
|
+
});
|
|
124
|
+
run(true);
|
|
125
|
+
Toast.success(t('common.saved'));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
Toast.error(formatError(err));
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const supportPaymentKit = getPaymentKitComponent();
|
|
133
|
+
|
|
134
|
+
const handleEnable = async () => {
|
|
135
|
+
if (!enableDonate || !data || (data as TSetting)?.active) return;
|
|
136
|
+
try {
|
|
137
|
+
await api.put(`/api/settings/${(data as TSetting).id}`, { active: true });
|
|
138
|
+
if (supportPaymentKit) {
|
|
139
|
+
setShowConfirm(true);
|
|
140
|
+
} else {
|
|
141
|
+
Toast.success(t('payment.checkout.donation.enableSuccess'));
|
|
142
|
+
run(true);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
Toast.error(formatError(err));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (loading || error) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Provider
|
|
155
|
+
value={{
|
|
156
|
+
settings: data as TSetting,
|
|
157
|
+
refresh: run,
|
|
158
|
+
updateSettings,
|
|
159
|
+
api,
|
|
160
|
+
}}>
|
|
161
|
+
{(data as TSetting)?.active === false ? (
|
|
162
|
+
<>
|
|
163
|
+
{enableDonate && isAdmin && (
|
|
164
|
+
<Stack spacing={1} sx={{ alignItems: 'center' }}>
|
|
165
|
+
<Typography color="text.secondary">{t('payment.checkout.donation.inactive')}</Typography>
|
|
166
|
+
<Button
|
|
167
|
+
size="small"
|
|
168
|
+
variant="outlined"
|
|
169
|
+
color="primary"
|
|
170
|
+
onClick={handleEnable}
|
|
171
|
+
sx={{ width: 'fit-content' }}>
|
|
172
|
+
{t('payment.checkout.donation.enable')}
|
|
173
|
+
</Button>
|
|
174
|
+
</Stack>
|
|
175
|
+
)}
|
|
176
|
+
|
|
177
|
+
{showConfirm && (
|
|
178
|
+
<ConfirmDialog
|
|
179
|
+
title={t('payment.checkout.donation.enableSuccess')}
|
|
180
|
+
message={t('payment.checkout.donation.configPrompt')}
|
|
181
|
+
cancel={t('payment.checkout.donation.later')}
|
|
182
|
+
confirm={t('payment.checkout.donation.configNow')}
|
|
183
|
+
onCancel={() => {
|
|
184
|
+
setShowConfirm(false);
|
|
185
|
+
run(true);
|
|
186
|
+
}}
|
|
187
|
+
color="primary"
|
|
188
|
+
onConfirm={() => {
|
|
189
|
+
run(true);
|
|
190
|
+
openDonationSettings(false);
|
|
191
|
+
setShowConfirm(false);
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
</>
|
|
196
|
+
) : (
|
|
197
|
+
children
|
|
198
|
+
)}
|
|
199
|
+
</Provider>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function useDonateContext() {
|
|
204
|
+
const context = useContext(DonateContext);
|
|
205
|
+
return context;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
DonateProvider.defaultProps = {
|
|
209
|
+
defaultSettings: {},
|
|
210
|
+
active: true,
|
|
211
|
+
enableDonate: false,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const clearDonateCache = (mountLocation: string) => {
|
|
215
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
216
|
+
const cacheKey = `donate-settings-${mountLocation}-${livemode}`;
|
|
217
|
+
localStorage.removeItem(cacheKey);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const clearDonateSettings = async (mountLocation: string) => {
|
|
221
|
+
try {
|
|
222
|
+
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
223
|
+
await api.delete(`/api/settings/${mountLocation}`, {
|
|
224
|
+
params: {
|
|
225
|
+
livemode,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
clearDonateCache(mountLocation);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
Toast.error(formatError(err));
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export { DonateContext, DonateProvider, Consumer as DonateConsumer, useDonateContext };
|
package/src/contexts/payment.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { createContext, useContext, useState } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import api from '../libs/api';
|
|
8
8
|
import { getPrefix } from '../libs/util';
|
|
9
|
+
import { CachedRequest } from '../libs/cached-request';
|
|
9
10
|
|
|
10
11
|
export interface Settings {
|
|
11
12
|
paymentMethods: TPaymentMethodExpanded[];
|
|
@@ -39,29 +40,13 @@ export type PaymentContextProps = {
|
|
|
39
40
|
const PaymentContext = createContext<PaymentContextType>({ api });
|
|
40
41
|
const { Provider, Consumer } = PaymentContext;
|
|
41
42
|
|
|
42
|
-
let settingsPromise: Promise<any> | null = null;
|
|
43
43
|
const getSettings = (forceRefresh = false) => {
|
|
44
44
|
const livemode = localStorage.getItem('livemode') !== 'false';
|
|
45
45
|
const cacheKey = `payment-settings-${window.location.pathname}-${livemode}`;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!settingsPromise) {
|
|
51
|
-
settingsPromise = api
|
|
52
|
-
.get('/api/settings', { params: { livemode } })
|
|
53
|
-
.then(({ data }: any) => {
|
|
54
|
-
sessionStorage.setItem(cacheKey, JSON.stringify(data));
|
|
55
|
-
return data;
|
|
56
|
-
})
|
|
57
|
-
.catch((error: any) => {
|
|
58
|
-
throw error;
|
|
59
|
-
})
|
|
60
|
-
.finally(() => {
|
|
61
|
-
settingsPromise = null;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return settingsPromise;
|
|
46
|
+
|
|
47
|
+
const cachedRequest = new CachedRequest(cacheKey, () => api.get('/api/settings', { params: { livemode } }));
|
|
48
|
+
|
|
49
|
+
return cachedRequest.fetch(forceRefresh);
|
|
65
50
|
};
|
|
66
51
|
|
|
67
52
|
const getCurrency = (currencyId: string, methods: TPaymentMethodExpanded[]) => {
|
package/src/index.ts
CHANGED
|
@@ -36,7 +36,9 @@ export { PaymentThemeProvider } from './theme';
|
|
|
36
36
|
export * from './libs/util';
|
|
37
37
|
export * from './libs/connect';
|
|
38
38
|
export * from './libs/phone-validator';
|
|
39
|
+
export * from './libs/cached-request';
|
|
39
40
|
export * from './contexts/payment';
|
|
41
|
+
export * from './contexts/donate';
|
|
40
42
|
export * from './hooks/subscription';
|
|
41
43
|
export * from './hooks/mobile';
|
|
42
44
|
export * from './hooks/table';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type CacheItem = {
|
|
2
|
+
promise: Promise<any> | null;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
class GlobalCacheManager {
|
|
6
|
+
private static instance: GlobalCacheManager;
|
|
7
|
+
private cacheMap: Map<string, CacheItem>;
|
|
8
|
+
|
|
9
|
+
private constructor() {
|
|
10
|
+
this.cacheMap = new Map();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static getInstance() {
|
|
14
|
+
if (!GlobalCacheManager.instance) {
|
|
15
|
+
GlobalCacheManager.instance = new GlobalCacheManager();
|
|
16
|
+
}
|
|
17
|
+
return GlobalCacheManager.instance;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(cacheKey: string): CacheItem | undefined {
|
|
21
|
+
return this.cacheMap.get(cacheKey);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set(cacheKey: string, item: CacheItem) {
|
|
25
|
+
this.cacheMap.set(cacheKey, item);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
delete(cacheKey: string) {
|
|
29
|
+
this.cacheMap.delete(cacheKey);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const globalCache = GlobalCacheManager.getInstance();
|