@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.
Files changed (43) hide show
  1. package/es/checkout/donate.d.ts +5 -3
  2. package/es/checkout/donate.js +109 -36
  3. package/es/contexts/donate.d.ts +41 -0
  4. package/es/contexts/donate.js +177 -0
  5. package/es/contexts/payment.js +3 -16
  6. package/es/index.d.ts +2 -0
  7. package/es/index.js +2 -0
  8. package/es/libs/cache.d.ts +14 -0
  9. package/es/libs/cache.js +23 -0
  10. package/es/libs/cached-request.d.ts +17 -0
  11. package/es/libs/cached-request.js +79 -0
  12. package/es/libs/util.d.ts +2 -0
  13. package/es/libs/util.js +11 -0
  14. package/es/locales/en.js +8 -1
  15. package/es/locales/zh.js +8 -1
  16. package/es/payment/skeleton/donation.js +1 -1
  17. package/lib/checkout/donate.d.ts +5 -3
  18. package/lib/checkout/donate.js +107 -36
  19. package/lib/contexts/donate.d.ts +41 -0
  20. package/lib/contexts/donate.js +187 -0
  21. package/lib/contexts/payment.js +7 -22
  22. package/lib/index.d.ts +2 -0
  23. package/lib/index.js +24 -0
  24. package/lib/libs/cache.d.ts +14 -0
  25. package/lib/libs/cache.js +29 -0
  26. package/lib/libs/cached-request.d.ts +17 -0
  27. package/lib/libs/cached-request.js +90 -0
  28. package/lib/libs/util.d.ts +2 -0
  29. package/lib/libs/util.js +13 -0
  30. package/lib/locales/en.js +8 -1
  31. package/lib/locales/zh.js +8 -1
  32. package/lib/payment/skeleton/donation.js +1 -1
  33. package/package.json +6 -6
  34. package/src/checkout/donate.tsx +135 -45
  35. package/src/contexts/donate.tsx +234 -0
  36. package/src/contexts/payment.tsx +5 -20
  37. package/src/index.ts +2 -0
  38. package/src/libs/cache.ts +33 -0
  39. package/src/libs/cached-request.ts +103 -0
  40. package/src/libs/util.ts +13 -0
  41. package/src/locales/en.tsx +7 -0
  42. package/src/locales/zh.tsx +7 -0
  43. package/src/payment/skeleton/donation.tsx +1 -1
@@ -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 { useMount, useRequest, useSetState } from 'ahooks';
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 { formatAmount, formatBNStr, formatDateTime, formatError, getCustomerAvatar, lazyLoad } from '../libs/util';
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: string | React.ReactNode;
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: DonationSettings;
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
- function useDonation(settings: DonationSettings, livemode: boolean, mode = 'default') {
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 donation = useRequest(() => createOrUpdateDonation(settings, livemode), {
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
- useMount(() => {
296
- if (mode !== 'inline') {
297
- lazyLoad(() => {
298
- donation.run();
299
- supporters.run();
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(settings, livemode, mode);
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={(settings.appearance?.button?.size || 'medium') as any}
377
- color={(settings.appearance?.button?.color || 'primary') as any}
378
- variant={(settings.appearance?.button?.variant || 'contained') as any}
379
- {...settings.appearance?.button}
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
- {settings.appearance.button.icon}
383
- {typeof settings.appearance.button.text === 'string' ? (
384
- <Typography sx={{ whiteSpace: 'nowrap' }}>{settings.appearance.button.text}</Typography>
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
- settings.appearance.button.text
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 inlineOptions?.button?.text === 'string' ? (
429
- <Typography sx={{ whiteSpace: 'nowrap' }}>{inlineOptions?.button?.text}</Typography>
503
+ {typeof inlineText === 'string' ? (
504
+ <Typography sx={{ whiteSpace: 'nowrap' }}>{inlineText}</Typography>
430
505
  ) : (
431
- inlineOptions?.button?.text
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={(settings.appearance?.button?.size || 'medium') as any}
450
- color={(settings.appearance?.button?.color || 'primary') as any}
451
- variant={(settings.appearance?.button?.variant || 'contained') as any}
452
- {...settings.appearance?.button}
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
- {settings.appearance.button.icon}
456
- {typeof settings.appearance.button.text === 'string' ? (
457
- <Typography>{settings.appearance.button.text}</Typography>
530
+ {donateSettings.appearance.button.icon}
531
+ {typeof donateSettings.appearance.button.text === 'string' ? (
532
+ <Typography>{donateSettings.appearance.button.text}</Typography>
458
533
  ) : (
459
- settings.appearance.button.text
534
+ donateSettings.appearance.button.text
460
535
  )}
461
536
  </Stack>
462
537
  </Button>
463
- {supporters.data && settings.appearance.history.variant === 'avatar' && (
538
+ {supporters.data && donateSettings.appearance.history.variant === 'avatar' && (
464
539
  <SupporterAvatar {...(supporters.data as DonateHistory)} />
465
540
  )}
466
- {supporters.data && settings.appearance.history.variant === 'table' && (
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
- {settings.title}
584
+ {donateSettings.title}
507
585
  </Typography>
508
- {!donation.data.livemode && <Livemode sx={{ width: 'fit-content' }} />}
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={settings.appearance?.button?.text}
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 };
@@ -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
- const cachedData = sessionStorage.getItem(cacheKey);
47
- if (cachedData && !forceRefresh) {
48
- return JSON.parse(cachedData);
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();