@blocklet/did-domain-react 0.3.39
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/LICENSE +13 -0
- package/README.md +1 -0
- package/babel.config.es.js +8 -0
- package/build.config.ts +29 -0
- package/env.d.ts +17 -0
- package/es/buy.d.ts +19 -0
- package/es/buy.js +124 -0
- package/es/domain.d.ts +11 -0
- package/es/domain.js +73 -0
- package/es/index.d.ts +4 -0
- package/es/index.js +4 -0
- package/es/libs/api.d.ts +3 -0
- package/es/libs/api.js +21 -0
- package/es/libs/util.d.ts +21 -0
- package/es/libs/util.js +73 -0
- package/es/locales/en.d.ts +2 -0
- package/es/locales/en.js +15 -0
- package/es/locales/index.d.ts +4 -0
- package/es/locales/index.js +6 -0
- package/es/locales/zh.d.ts +2 -0
- package/es/locales/zh.js +15 -0
- package/es/types/index.d.ts +0 -0
- package/es/types/index.js +0 -0
- package/es/types/shims.d.ts +15 -0
- package/global.d.ts +54 -0
- package/lib/buy.d.ts +19 -0
- package/lib/buy.js +177 -0
- package/lib/domain.d.ts +11 -0
- package/lib/domain.js +80 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +27 -0
- package/lib/libs/api.d.ts +3 -0
- package/lib/libs/api.js +25 -0
- package/lib/libs/util.d.ts +21 -0
- package/lib/libs/util.js +83 -0
- package/lib/locales/en.d.ts +2 -0
- package/lib/locales/en.js +22 -0
- package/lib/locales/index.d.ts +4 -0
- package/lib/locales/index.js +13 -0
- package/lib/locales/zh.d.ts +2 -0
- package/lib/locales/zh.js +22 -0
- package/lib/types/index.d.ts +0 -0
- package/lib/types/index.js +1 -0
- package/lib/types/shims.d.ts +15 -0
- package/package.json +106 -0
- package/src/buy.tsx +154 -0
- package/src/domain.tsx +77 -0
- package/src/index.ts +6 -0
- package/src/libs/api.ts +28 -0
- package/src/libs/util.ts +100 -0
- package/src/locales/en.ts +17 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/zh.ts +16 -0
- package/src/types/index.ts +0 -0
- package/src/types/shims.d.ts +15 -0
- package/vite.config.ts +6 -0
package/src/buy.tsx
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import Button from '@arcblock/ux/lib/Button';
|
|
2
|
+
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
3
|
+
import { LocaleProvider, useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
|
+
import type { Locale } from '@arcblock/ux/lib/type';
|
|
5
|
+
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
6
|
+
import ShoppingCart from '@mui/icons-material/ShoppingCart';
|
|
7
|
+
import type { ButtonProps } from '@mui/material';
|
|
8
|
+
import Alert from '@mui/material/Alert';
|
|
9
|
+
import Box from '@mui/material/Box';
|
|
10
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
11
|
+
import Typography from '@mui/material/Typography';
|
|
12
|
+
import { useRequest as useAHooksRequest, useMemoizedFn, useSetState, useUnmount } from 'ahooks';
|
|
13
|
+
import { useRef } from 'react';
|
|
14
|
+
import { joinURL } from 'ufo';
|
|
15
|
+
|
|
16
|
+
import { create } from './libs/api';
|
|
17
|
+
import { openPopup } from './libs/util';
|
|
18
|
+
import { translations } from './locales';
|
|
19
|
+
|
|
20
|
+
enum BuyState {
|
|
21
|
+
Prepare = 1,
|
|
22
|
+
InProgress = 2,
|
|
23
|
+
Success = 4,
|
|
24
|
+
Completed = 8,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
delegatee: string;
|
|
29
|
+
delegateePk: string;
|
|
30
|
+
didDomainURL: string;
|
|
31
|
+
locale: Locale;
|
|
32
|
+
onSuccess?: (data: { nftDid: string; domain: string; chainHost: string }) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Component({ delegatee, delegateePk, didDomainURL, locale, onSuccess, ...props }: Props & ButtonProps) {
|
|
36
|
+
const { t } = useLocaleContext();
|
|
37
|
+
const [state, setState] = useSetState<{ currentState: BuyState }>({
|
|
38
|
+
currentState: BuyState.Prepare,
|
|
39
|
+
});
|
|
40
|
+
const popup = useRef<Window | null>(null);
|
|
41
|
+
const domainURLObj = new URL(didDomainURL);
|
|
42
|
+
domainURLObj.searchParams.set('locale', locale);
|
|
43
|
+
|
|
44
|
+
const api = useMemoizedFn(() => create({ baseURL: joinURL(domainURLObj.origin, domainURLObj.pathname, 'api') }))();
|
|
45
|
+
|
|
46
|
+
const onMessage = useMemoizedFn((event: MessageEvent) => {
|
|
47
|
+
if (event.data.type === 'didDomain.success') {
|
|
48
|
+
onSuccess?.({ nftDid: event.data?.nftDid, domain: event.data?.domain, chainHost: event.data?.chainHost });
|
|
49
|
+
popup.current?.close();
|
|
50
|
+
popup.current = null;
|
|
51
|
+
setState({ currentState: BuyState.Success });
|
|
52
|
+
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
setState({ currentState: BuyState.Completed });
|
|
55
|
+
}, 2000);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useUnmount(() => {
|
|
60
|
+
try {
|
|
61
|
+
window.removeEventListener('message', onMessage);
|
|
62
|
+
popup.current?.close();
|
|
63
|
+
popup.current = null;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn('Failed to close popup', error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const handleCloseDialog = useMemoizedFn(() => {
|
|
70
|
+
setState({ currentState: BuyState.Prepare });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const handleOpenDialog = useMemoizedFn(() => {
|
|
74
|
+
setState({ currentState: BuyState.InProgress });
|
|
75
|
+
sessionRequest.run();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const handleCreatedSession = useMemoizedFn(({ sessionId }: { sessionId: string }) => {
|
|
79
|
+
window.addEventListener('message', onMessage);
|
|
80
|
+
domainURLObj.searchParams.set('sessionId', sessionId);
|
|
81
|
+
popup.current = openPopup(domainURLObj.toString());
|
|
82
|
+
|
|
83
|
+
const timer = setInterval(() => {
|
|
84
|
+
if (popup.current?.closed) {
|
|
85
|
+
clearInterval(timer);
|
|
86
|
+
handleCloseDialog();
|
|
87
|
+
}
|
|
88
|
+
}, 1000);
|
|
89
|
+
|
|
90
|
+
return () => clearInterval(timer);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const sessionRequest = useAHooksRequest(
|
|
94
|
+
async () => {
|
|
95
|
+
const { data } = await api.post('/payment/session', {
|
|
96
|
+
delegatee,
|
|
97
|
+
delegateePk,
|
|
98
|
+
});
|
|
99
|
+
return handleCreatedSession({ sessionId: data.sessionId });
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
manual: true,
|
|
103
|
+
debounceWait: 500,
|
|
104
|
+
debounceLeading: true,
|
|
105
|
+
debounceTrailing: false,
|
|
106
|
+
debounceMaxWait: 3000,
|
|
107
|
+
refreshDeps: [],
|
|
108
|
+
onError(error: Error) {
|
|
109
|
+
console.error(error);
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
<Button onClick={handleOpenDialog} variant="contained" startIcon={<ShoppingCart />} {...props}>
|
|
117
|
+
{t('buy.button.title')}
|
|
118
|
+
</Button>
|
|
119
|
+
<Dialog
|
|
120
|
+
fullWidth
|
|
121
|
+
maxWidth="xs"
|
|
122
|
+
open={state.currentState > BuyState.Prepare && state.currentState < BuyState.Completed}
|
|
123
|
+
onClose={handleCloseDialog}>
|
|
124
|
+
{!sessionRequest.error && (
|
|
125
|
+
<Box sx={{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: 2 }}>
|
|
126
|
+
{state.currentState === BuyState.InProgress && <CircularProgress size={50} />}
|
|
127
|
+
{state.currentState === BuyState.Success && (
|
|
128
|
+
<CheckCircle sx={{ color: 'primary.main', fontSize: '5rem' }} />
|
|
129
|
+
)}
|
|
130
|
+
{state.currentState === BuyState.InProgress && <Typography>{t('buy.status.inProgress')}</Typography>}
|
|
131
|
+
{state.currentState === BuyState.Success && <Typography>{t('buy.status.success')}</Typography>}
|
|
132
|
+
</Box>
|
|
133
|
+
)}
|
|
134
|
+
{sessionRequest.error && <Alert severity="error">{t('buy.error.failedToCreateSession')}</Alert>}
|
|
135
|
+
</Dialog>
|
|
136
|
+
</>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Component.defaultProps = {
|
|
141
|
+
onSuccess: () => {},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default function Buy(props: Props) {
|
|
145
|
+
return (
|
|
146
|
+
<LocaleProvider locale={props.locale} translations={translations}>
|
|
147
|
+
<Component {...props} />
|
|
148
|
+
</LocaleProvider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Buy.defaultProps = {
|
|
153
|
+
onSuccess: () => {},
|
|
154
|
+
};
|
package/src/domain.tsx
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Popover from '@mui/material/Popover';
|
|
4
|
+
import { useTheme } from '@mui/material/styles';
|
|
5
|
+
import useMediaQuery from '@mui/material/useMediaQuery';
|
|
6
|
+
import { useRef } from 'react';
|
|
7
|
+
import useSetState from 'react-use/lib/useSetState';
|
|
8
|
+
import { joinURL } from 'ufo';
|
|
9
|
+
|
|
10
|
+
import { mergeSx } from './libs/util';
|
|
11
|
+
|
|
12
|
+
export function Domain({
|
|
13
|
+
nftDid,
|
|
14
|
+
didDomainURL,
|
|
15
|
+
locale,
|
|
16
|
+
sx,
|
|
17
|
+
}: {
|
|
18
|
+
nftDid: string;
|
|
19
|
+
didDomainURL: string;
|
|
20
|
+
locale: string;
|
|
21
|
+
sx?: any;
|
|
22
|
+
}) {
|
|
23
|
+
const { t } = useLocaleContext();
|
|
24
|
+
const [state, setState] = useSetState({
|
|
25
|
+
safeIframeRef: null,
|
|
26
|
+
popoverAnchorEl: null,
|
|
27
|
+
});
|
|
28
|
+
const iframeRef = useRef(null);
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
31
|
+
|
|
32
|
+
const handlePopoverClick = (event: any) => {
|
|
33
|
+
setState({ popoverAnchorEl: event.currentTarget });
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handlePopoverClose = () => {
|
|
37
|
+
setState({ popoverAnchorEl: null });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const iframeSrc = joinURL(didDomainURL, `/app/embed/domain?nft-did=${nftDid}&locale=${locale}`);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
<Box
|
|
45
|
+
component="img"
|
|
46
|
+
src={joinURL(didDomainURL, '/.well-known/service/blocklet/logo')}
|
|
47
|
+
alt="logo"
|
|
48
|
+
width={16}
|
|
49
|
+
height={16}
|
|
50
|
+
onClick={handlePopoverClick}
|
|
51
|
+
sx={mergeSx({ cursor: 'pointer' }, sx)}
|
|
52
|
+
/>
|
|
53
|
+
<Popover
|
|
54
|
+
id="popover-info"
|
|
55
|
+
open={Boolean(state.popoverAnchorEl)}
|
|
56
|
+
anchorEl={state.popoverAnchorEl}
|
|
57
|
+
onClose={handlePopoverClose}
|
|
58
|
+
anchorOrigin={{
|
|
59
|
+
vertical: 'bottom',
|
|
60
|
+
horizontal: 'right',
|
|
61
|
+
}}>
|
|
62
|
+
<iframe
|
|
63
|
+
ref={iframeRef}
|
|
64
|
+
title={t('common.subscription')}
|
|
65
|
+
width={isMobile ? '320px' : '400px'}
|
|
66
|
+
height={isMobile ? '600px' : '680px'}
|
|
67
|
+
style={{ border: 0 }}
|
|
68
|
+
src={iframeSrc}
|
|
69
|
+
/>
|
|
70
|
+
</Popover>
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
Domain.defaultProps = {
|
|
76
|
+
sx: {},
|
|
77
|
+
};
|
package/src/index.ts
ADDED
package/src/libs/api.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getLocale } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import { createAxios } from '@blocklet/js-sdk';
|
|
3
|
+
import { joinURL } from 'ufo';
|
|
4
|
+
|
|
5
|
+
const axios = createAxios({});
|
|
6
|
+
|
|
7
|
+
axios.interceptors.request.use(
|
|
8
|
+
(config) => {
|
|
9
|
+
const prefix = window.blocklet ? window.blocklet.prefix : '/';
|
|
10
|
+
config.baseURL = prefix || '';
|
|
11
|
+
config.url = joinURL('/api', String(config.url));
|
|
12
|
+
config.timeout = 200000;
|
|
13
|
+
|
|
14
|
+
config.params = config.params || {};
|
|
15
|
+
|
|
16
|
+
const searchParams = new URLSearchParams(config.url.split('?')[1]);
|
|
17
|
+
if (!searchParams.has('locale')) {
|
|
18
|
+
config.params.locale = getLocale();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return config;
|
|
22
|
+
},
|
|
23
|
+
(error) => Promise.reject(error)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const create = (...args: any) => createAxios(...args);
|
|
27
|
+
|
|
28
|
+
export default axios;
|
package/src/libs/util.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { withQuery } from 'ufo';
|
|
2
|
+
|
|
3
|
+
export const SEARCH_BUTTON_WIDTH = 120;
|
|
4
|
+
|
|
5
|
+
export const COMMON_REQUEST_OPTIONS = {
|
|
6
|
+
debounceWait: 500,
|
|
7
|
+
debounceLeading: true,
|
|
8
|
+
debounceTrailing: false,
|
|
9
|
+
debounceMaxWait: 3000,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const formatLocale = (locale = 'en') => {
|
|
13
|
+
if (locale === 'tw') {
|
|
14
|
+
return 'zh';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return locale;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const formatError = (err: any, t: any) => {
|
|
21
|
+
const { errors, response } = err;
|
|
22
|
+
|
|
23
|
+
// graphql error
|
|
24
|
+
if (Array.isArray(errors)) {
|
|
25
|
+
return errors.map((x) => x.message).join('\n');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// joi validate error
|
|
29
|
+
const joiError = err.response?.data?.error;
|
|
30
|
+
if (Array.isArray(joiError?.details)) {
|
|
31
|
+
const formatted = joiError?.details.map((e: any) => {
|
|
32
|
+
const errorMessage = e.message.replace(/["]/g, "'");
|
|
33
|
+
const errorPath = e.path.join('.');
|
|
34
|
+
return `${errorPath}: ${errorMessage}`;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return `Validate failed: ${formatted.join(';')}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (response?.data?.code) {
|
|
41
|
+
return t(`error.${response?.data?.code}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (response?.data?.message) {
|
|
45
|
+
return response.data.message;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// axios error
|
|
49
|
+
if (response) {
|
|
50
|
+
return `Request failed: ${response.status} ${response.statusText}: ${JSON.stringify(response.data)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return err.message;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const mergeSx = (initial: any, sx: any) => {
|
|
57
|
+
if (!sx) {
|
|
58
|
+
return initial;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!initial) {
|
|
62
|
+
return sx;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return [initial, ...(Array.isArray(sx) ? sx : [sx])];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const getChainHost = () => window.blocklet?.preferences?.chainHost || '';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {string} url - 打开一个弹窗
|
|
73
|
+
* @returns
|
|
74
|
+
*/
|
|
75
|
+
export const openPopup = (url: string, { width = 600, height = 700, name = 'did-domain:popup' } = {}) => {
|
|
76
|
+
const left = window.screenX + (window.innerWidth - width) / 2;
|
|
77
|
+
const top = window.screenY + (window.innerHeight - height) / 2;
|
|
78
|
+
|
|
79
|
+
const windowFeatures = [
|
|
80
|
+
`left=${left}`,
|
|
81
|
+
`top=${top}`,
|
|
82
|
+
`width=${width}`,
|
|
83
|
+
`height=${height}`,
|
|
84
|
+
'resizable=no', // not working
|
|
85
|
+
'scrollbars=yes',
|
|
86
|
+
'status=yes',
|
|
87
|
+
'popup=yes',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// HACK: IOS 必须以这种形式打开,才能被识别为 dialog 模式
|
|
91
|
+
const popup = window.open('', name, windowFeatures.join(','));
|
|
92
|
+
if (popup === null) {
|
|
93
|
+
throw new Error('Failed to open popup');
|
|
94
|
+
}
|
|
95
|
+
popup.location.href = withQuery(url, {
|
|
96
|
+
// NOTICE: 携带当前页面的 origin,用于在 popup 中通过该参数判断是否可以发送 postMessage,即使该参数被伪造,最终也只有该域名能接收到消息,所以没有关系
|
|
97
|
+
opener: window.location.origin,
|
|
98
|
+
});
|
|
99
|
+
return popup;
|
|
100
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import flat from 'flat';
|
|
2
|
+
|
|
3
|
+
export default flat({
|
|
4
|
+
buy: {
|
|
5
|
+
button: {
|
|
6
|
+
title: 'Buy DID Domain',
|
|
7
|
+
},
|
|
8
|
+
status: {
|
|
9
|
+
inProgress:
|
|
10
|
+
'Please wait while we are processing your request, the domain will be automatically resolved to the current app after the purchase...',
|
|
11
|
+
success: 'Purchase completed!',
|
|
12
|
+
},
|
|
13
|
+
error: {
|
|
14
|
+
failedToCreateSession: 'Failed to create session',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import flat from 'flat';
|
|
2
|
+
|
|
3
|
+
export default flat({
|
|
4
|
+
buy: {
|
|
5
|
+
button: {
|
|
6
|
+
title: '购买 DID 域名',
|
|
7
|
+
},
|
|
8
|
+
status: {
|
|
9
|
+
inProgress: '请稍等,我们正在处理您的请求,购买域名后会自动解析到当前的应用...',
|
|
10
|
+
success: '购买完成!',
|
|
11
|
+
},
|
|
12
|
+
error: {
|
|
13
|
+
failedToCreateSession: '创建会话失败',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare module '@arcblock/ux/*';
|
|
2
|
+
|
|
3
|
+
declare module '@arcblock/did-connect/*';
|
|
4
|
+
|
|
5
|
+
declare module '*.png';
|
|
6
|
+
|
|
7
|
+
declare module 'flat';
|
|
8
|
+
|
|
9
|
+
declare module '@arcblock/*';
|
|
10
|
+
|
|
11
|
+
declare module '@blocklet/*';
|
|
12
|
+
|
|
13
|
+
declare module 'pretty-ms-i18n';
|
|
14
|
+
|
|
15
|
+
declare var blocklet: import('@blocklet/sdk').WindowBlocklet;
|