@gluwa/connect-kit 0.2.0-next.8 → 0.2.1-next.1
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/CHANGELOG.md +37 -0
- package/assets/wc.png +0 -0
- package/dist/{chunk-VE5LB7BN.js → chunk-CJANPJHY.js} +4 -2
- package/dist/credit-connect.js +1 -1
- package/dist/index.css +211 -156
- package/dist/index.d.ts +4 -1
- package/dist/index.js +521 -197
- package/dist/package.json +1 -1
- package/package.json +6 -6
- package/src/ConnectKitProvider.tsx +8 -1
- package/src/components/qrArea/index.tsx +28 -4
- package/src/components/qrArea/style.scss +85 -34
- package/src/components/toast/index.tsx +41 -0
- package/src/components/toast/style.scss +108 -0
- package/src/core/config.ts +23 -2
- package/src/core/deeplinkSignal.ts +25 -0
- package/src/creditConnectConnector.ts +15 -5
- package/src/events/account.ts +15 -6
- package/src/hooks/useConnectBanner.ts +79 -0
- package/src/hooks/useConnectTimeout.ts +21 -9
- package/src/hooks/useMetaMaskExtension.ts +30 -0
- package/src/hooks/useWCState.ts +14 -10
- package/src/hooks/useWagmiConnect.ts +42 -20
- package/src/index.ts +1 -0
- package/src/layout/allWallets/index.tsx +17 -3
- package/src/layout/allWallets/style.scss +2 -7
- package/src/layout/connectDialog/asset/warningIcon.tsx +21 -0
- package/src/layout/connectDialog/idle/assets/banner-placeholder.png +0 -0
- package/src/layout/connectDialog/idle/index.tsx +40 -16
- package/src/layout/connectDialog/idle/style.scss +42 -51
- package/src/layout/connectDialog/index.tsx +196 -47
- package/src/layout/connectDialog/metaMask/index.tsx +3 -2
- package/src/layout/connectDialog/qr/index.tsx +3 -1
- package/src/layout/connectDialog/style.scss +2 -49
- package/src/layout/connectDialog/timeout/index.tsx +0 -5
- package/src/layout/connectDialog/timeout/style.scss +0 -5
- package/src/layout/connectDialog/walletQR/index.tsx +3 -1
- package/src/layout/detailPanel/index.tsx +39 -6
- package/src/layout/detailPanel/style.scss +7 -1
- package/src/layout/selectorPanel/index.tsx +1 -0
- package/src/types.ts +20 -0
- package/src/utils/platform.ts +7 -4
- package/src/wallet-display-override.ts +15 -0
- package/src/components/snackbar/index.tsx +0 -25
- package/src/components/snackbar/style.scss +0 -51
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { ConnectBanner, ConnectBannerSiteKey } from '../types';
|
|
3
|
+
|
|
4
|
+
interface ConnectBannersResponse {
|
|
5
|
+
banners: ConnectBanner[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UseConnectBannerParams {
|
|
9
|
+
open: boolean;
|
|
10
|
+
siteKey: ConnectBannerSiteKey;
|
|
11
|
+
url: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface UseConnectBannerResult {
|
|
15
|
+
banner: ConnectBanner | null;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pickRandomBanner = (banners: ConnectBanner[]): ConnectBanner | null =>
|
|
20
|
+
banners[Math.floor(Math.random() * banners.length)] ?? null;
|
|
21
|
+
|
|
22
|
+
export const useConnectBanner = ({
|
|
23
|
+
open,
|
|
24
|
+
siteKey,
|
|
25
|
+
url,
|
|
26
|
+
}: UseConnectBannerParams): UseConnectBannerResult => {
|
|
27
|
+
const [candidates, setCandidates] = useState<ConnectBanner[]>([]);
|
|
28
|
+
const [banner, setBanner] = useState<ConnectBanner | null>(null);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const wasOpenRef = useRef(open);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!url) {
|
|
34
|
+
setCandidates([]);
|
|
35
|
+
setBanner(null);
|
|
36
|
+
setLoading(false);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
setCandidates([]);
|
|
42
|
+
setBanner(null);
|
|
43
|
+
setLoading(true);
|
|
44
|
+
|
|
45
|
+
fetch(url, { signal: controller.signal })
|
|
46
|
+
.then(async (response) => {
|
|
47
|
+
if (!response.ok) throw new Error(`Failed to fetch connect banners: ${response.status}`);
|
|
48
|
+
return (await response.json()) as ConnectBannersResponse;
|
|
49
|
+
})
|
|
50
|
+
.then(({ banners }) => {
|
|
51
|
+
if (controller.signal.aborted) return;
|
|
52
|
+
if (!Array.isArray(banners)) {
|
|
53
|
+
setLoading(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nextCandidates = banners.filter((item) => item.siteKey !== siteKey);
|
|
58
|
+
setCandidates(nextCandidates);
|
|
59
|
+
setBanner(pickRandomBanner(nextCandidates));
|
|
60
|
+
setLoading(false);
|
|
61
|
+
})
|
|
62
|
+
.catch(() => {
|
|
63
|
+
// API 실패 시 기존 Idle placeholder를 유지한다.
|
|
64
|
+
if (!controller.signal.aborted) setLoading(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return () => controller.abort();
|
|
68
|
+
}, [siteKey, url]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const wasOpen = wasOpenRef.current;
|
|
72
|
+
wasOpenRef.current = open;
|
|
73
|
+
|
|
74
|
+
// 닫힌 dialog 안에서 다음 배너 이미지를 미리 렌더링한다.
|
|
75
|
+
if (wasOpen && !open) setBanner(pickRandomBanner(candidates));
|
|
76
|
+
}, [candidates, open]);
|
|
77
|
+
|
|
78
|
+
return { banner, loading };
|
|
79
|
+
};
|
|
@@ -3,16 +3,19 @@ import { type ConnectorId } from '../types';
|
|
|
3
3
|
|
|
4
4
|
// MM / CC 폴백용 기본 타임아웃. (CC 는 원칙적으로 서버 push 로 만료가 오지만,
|
|
5
5
|
// 서버 값이 없거나 신호가 유실된 경우를 대비한 폴백 용도로도 쓸 수 있음.)
|
|
6
|
-
export const DEFAULT_CONNECT_TIMEOUT_MS =
|
|
6
|
+
export const DEFAULT_CONNECT_TIMEOUT_MS = 300_000;
|
|
7
7
|
|
|
8
8
|
interface UseConnectTimeoutParams {
|
|
9
9
|
/** 현재 연결 시도 중인 커넥터. null = 유휴 상태. */
|
|
10
10
|
connectorId: ConnectorId | null;
|
|
11
11
|
/** 타임아웃 감시 활성화 여부 — pending/QR 표시 중일 때 true. */
|
|
12
12
|
active: boolean;
|
|
13
|
+
/** Wagmi connect promise가 실제로 대기 중인지 여부. */
|
|
14
|
+
isConnecting: boolean;
|
|
13
15
|
/**
|
|
14
|
-
* 외부 이벤트 기반
|
|
16
|
+
* 외부 이벤트 기반 실패/만료 신호. 값이 증가할 때마다 연결 실패로 간주.
|
|
15
17
|
* - CC: dialog 가 connector 의 `session_expired`(서버 push) 를 받아 올림.
|
|
18
|
+
* - MM: connect promise reject.
|
|
16
19
|
* - WC: dialog 가 WC connect promise reject(프로토콜 ~5분 만료 등) 를 받아 올림.
|
|
17
20
|
*/
|
|
18
21
|
expiredSignal?: number;
|
|
@@ -23,6 +26,8 @@ interface UseConnectTimeoutParams {
|
|
|
23
26
|
interface UseConnectTimeoutResult {
|
|
24
27
|
/** 타임아웃 발생 여부 — true 면 dialog 가 <Timeout> 을 렌더. */
|
|
25
28
|
timedOut: boolean;
|
|
29
|
+
/** 다른 지갑 연결을 시작하기 전에 이전 타임아웃 상태를 즉시 초기화. */
|
|
30
|
+
reset: () => void;
|
|
26
31
|
/** "Try Again" 버튼에 연결할 함수 — 상태 리셋 + onRetry 실행. */
|
|
27
32
|
retry: () => void;
|
|
28
33
|
}
|
|
@@ -36,6 +41,7 @@ interface UseConnectTimeoutResult {
|
|
|
36
41
|
export const useConnectTimeout = ({
|
|
37
42
|
connectorId,
|
|
38
43
|
active,
|
|
44
|
+
isConnecting,
|
|
39
45
|
expiredSignal = 0,
|
|
40
46
|
onRetry,
|
|
41
47
|
}: UseConnectTimeoutParams): UseConnectTimeoutResult => {
|
|
@@ -54,16 +60,18 @@ export const useConnectTimeout = ({
|
|
|
54
60
|
// MM 만 client 타이머로 강제 (승인 대기에 네이티브 상한이 없음).
|
|
55
61
|
// CC(서버 push) / WC(connect reject) 는 아래 expiredSignal effect 에서 처리.
|
|
56
62
|
useEffect(() => {
|
|
57
|
-
if (!active || connectorId !== 'METAMASK') return;
|
|
58
|
-
const timer = setTimeout(
|
|
63
|
+
if (!active || !isConnecting || connectorId !== 'METAMASK') return;
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
fire();
|
|
66
|
+
}, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
59
67
|
return () => clearTimeout(timer);
|
|
60
|
-
}, [active, connectorId, fire]);
|
|
68
|
+
}, [active, isConnecting, connectorId, fire]);
|
|
61
69
|
|
|
62
|
-
// 외부
|
|
70
|
+
// 외부 실패/만료 신호 처리 (CC 서버 push / MM·WC connect reject).
|
|
63
71
|
// signal 값이 올라가면(새 만료 이벤트) 발화.
|
|
64
72
|
const prevSignal = useRef(expiredSignal);
|
|
65
73
|
useEffect(() => {
|
|
66
|
-
if (!active ||
|
|
74
|
+
if (!active || !connectorId) {
|
|
67
75
|
prevSignal.current = expiredSignal;
|
|
68
76
|
return;
|
|
69
77
|
}
|
|
@@ -76,7 +84,11 @@ export const useConnectTimeout = ({
|
|
|
76
84
|
const retry = useCallback(() => {
|
|
77
85
|
setTimedOut(false);
|
|
78
86
|
onRetry?.();
|
|
79
|
-
}, [onRetry]);
|
|
87
|
+
}, [connectorId, onRetry]);
|
|
88
|
+
|
|
89
|
+
const reset = useCallback(() => {
|
|
90
|
+
setTimedOut(false);
|
|
91
|
+
}, []);
|
|
80
92
|
|
|
81
|
-
return { timedOut, retry };
|
|
93
|
+
return { timedOut, reset, retry };
|
|
82
94
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const METAMASK_RDNS = 'io.metamask';
|
|
4
|
+
|
|
5
|
+
// EIP-6963 응답의 rdns 로 MetaMask 확장을 식별.
|
|
6
|
+
const isMetaMaskAnnouncement = (event: Event): boolean => {
|
|
7
|
+
const detail = (event as CustomEvent<{ info?: { rdns?: string }; provider?: unknown }>).detail;
|
|
8
|
+
return detail?.info?.rdns?.toLowerCase() === METAMASK_RDNS && detail.provider != null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const useMetaMaskExtension = (): boolean => {
|
|
12
|
+
const [isInstalled, setIsInstalled] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleAnnouncement = (event: Event): void => {
|
|
16
|
+
const isMetaMask = isMetaMaskAnnouncement(event);
|
|
17
|
+
if (isMetaMask) setIsInstalled(true);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 응답 listener를 먼저 등록한 뒤 설치된 provider 목록을 요청.
|
|
21
|
+
window.addEventListener('eip6963:announceProvider', handleAnnouncement);
|
|
22
|
+
window.dispatchEvent(new Event('eip6963:requestProvider'));
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener('eip6963:announceProvider', handleAnnouncement);
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
return isInstalled;
|
|
30
|
+
};
|
package/src/hooks/useWCState.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import type { WCWallet, WCSubView } from '../types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// CONNECTOR_META.CREDIT_CONNECT.label 은 selector button 표시용 ('Continue with Credit Wallet')
|
|
6
|
-
// 이라 다름 — 별도 상수로 분리.
|
|
7
|
-
const WC_EXPLORER_CREDIT_WALLET_NAME = 'Credit Wallet';
|
|
3
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
4
|
+
import { CREDIT_WALLET_LISTING_ID, overrideWalletDisplay } from '../wallet-display-override';
|
|
8
5
|
|
|
9
6
|
const sortCreditWalletFirst = (list: WCWallet[]): WCWallet[] =>
|
|
10
7
|
[...list].sort((a, b) => {
|
|
11
|
-
|
|
12
|
-
if (
|
|
8
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
9
|
+
if (a.id === CREDIT_WALLET_LISTING_ID) return -1;
|
|
10
|
+
if (b.id === CREDIT_WALLET_LISTING_ID) return 1;
|
|
13
11
|
return 0;
|
|
14
12
|
});
|
|
15
13
|
|
|
16
|
-
const fetchWCWalletList = async (
|
|
14
|
+
const fetchWCWalletList = async (
|
|
15
|
+
projectId: string,
|
|
16
|
+
signal: AbortSignal | undefined,
|
|
17
|
+
): Promise<WCWallet[]> => {
|
|
17
18
|
// WC Explorer API 가 page 경계에서 같은 wallet 을 중복 emit 하는 케이스 관찰됨
|
|
18
19
|
// (paginate 사이 sort/dedup 보장 X). id 기준 first-write-wins dedup.
|
|
19
20
|
const byId = new Map<string, WCWallet>();
|
|
@@ -108,14 +109,17 @@ export const useWCState = (): WCState => {
|
|
|
108
109
|
}, []);
|
|
109
110
|
|
|
110
111
|
const loadWalletList = useCallback(async (projectId: string): Promise<void> => {
|
|
111
|
-
if (loadedRef.current)
|
|
112
|
+
if (loadedRef.current) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
112
115
|
loadedRef.current = true;
|
|
113
116
|
const controller = new AbortController();
|
|
114
117
|
abortControllerRef.current = controller;
|
|
115
118
|
setWalletListLoading(true);
|
|
116
119
|
try {
|
|
117
120
|
const list = await fetchWCWalletList(projectId, controller.signal);
|
|
118
|
-
|
|
121
|
+
// TODO-CRCN-11: WC에 PenguinWallet 추가 시 변경
|
|
122
|
+
const sorted = sortCreditWalletFirst(list).map(overrideWalletDisplay);
|
|
119
123
|
// Credit Wallet 을 최상단으로 정렬한 상태로 state 저장 — view 측은 정렬 신경 X.
|
|
120
124
|
if (isMountedRef.current) setWalletList(sorted);
|
|
121
125
|
} catch (err) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { useConnect, useConfig } from 'wagmi';
|
|
3
|
+
import { getAccount } from '@wagmi/core';
|
|
3
4
|
import type { ConnectorId, ConnectResult } from '../types';
|
|
5
|
+
import { resolveConnectorId } from '../connector-meta';
|
|
6
|
+
import { resetDeeplinkSignal } from '../core/deeplinkSignal';
|
|
4
7
|
|
|
5
8
|
type WagmiConnectorLike = {
|
|
6
9
|
id: string;
|
|
@@ -13,15 +16,10 @@ const hasMetaMaskKeyword = (value: string): boolean => value.toLowerCase().inclu
|
|
|
13
16
|
const hasWalletConnectKeyword = (value: string): boolean =>
|
|
14
17
|
value.toLowerCase().includes('walletconnect');
|
|
15
18
|
|
|
19
|
+
// 전역 provider 대신 wagmi connector 메타데이터로 MetaMask를 판별.
|
|
16
20
|
const isMetaMaskConnector = (connector: WagmiConnectorLike): boolean => {
|
|
17
21
|
if (connector.id === 'metaMaskSDK') return true;
|
|
18
22
|
|
|
19
|
-
if (connector.id === 'injected') {
|
|
20
|
-
if (typeof window === 'undefined') return false;
|
|
21
|
-
const eth = (window as Window & { ethereum?: { isMetaMask?: boolean } }).ethereum;
|
|
22
|
-
return eth?.isMetaMask === true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
23
|
if (hasMetaMaskKeyword(connector.id) || hasMetaMaskKeyword(connector.name)) return true;
|
|
26
24
|
|
|
27
25
|
if (connector.rdns) {
|
|
@@ -78,6 +76,7 @@ export const useWagmiConnect = ({
|
|
|
78
76
|
}: Options): UseWagmiConnectResult => {
|
|
79
77
|
const config = useConfig();
|
|
80
78
|
const pendingConnectorId = useRef<ConnectorId | null>(null);
|
|
79
|
+
const pendingAttemptId = useRef<string | null>(null);
|
|
81
80
|
const attemptCounter = useRef(0);
|
|
82
81
|
|
|
83
82
|
const onConnectRef = useRef(onConnect);
|
|
@@ -94,17 +93,27 @@ export const useWagmiConnect = ({
|
|
|
94
93
|
const { connectAsync, reset, isPending } = useConnect();
|
|
95
94
|
|
|
96
95
|
useEffect(() => {
|
|
97
|
-
const handler = (data: unknown): void => {
|
|
98
|
-
const msg = data as { type?: string; data?: unknown };
|
|
99
|
-
if (msg?.type === 'display_uri' && typeof msg.data === 'string') {
|
|
100
|
-
onQrUriRef.current(msg.data);
|
|
101
|
-
}
|
|
102
|
-
if (msg?.type === 'session_expired') {
|
|
103
|
-
onSessionExpiredRef.current?.();
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
96
|
const unsubscribers = config.connectors.map((connector) => {
|
|
97
|
+
const emitterConnectorId = resolveConnectorId(connector.id);
|
|
98
|
+
const handler = (data: unknown): void => {
|
|
99
|
+
const msg = data as { type?: string; data?: unknown };
|
|
100
|
+
const pendingId = pendingConnectorId.current;
|
|
101
|
+
const belongsToPendingAttempt =
|
|
102
|
+
pendingId !== null && emitterConnectorId !== null && pendingId === emitterConnectorId;
|
|
103
|
+
|
|
104
|
+
if (msg?.type === 'display_uri' && typeof msg.data === 'string') {
|
|
105
|
+
if (!belongsToPendingAttempt) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
onQrUriRef.current(msg.data);
|
|
109
|
+
}
|
|
110
|
+
if (msg?.type === 'session_expired') {
|
|
111
|
+
if (pendingId !== null && !belongsToPendingAttempt) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
onSessionExpiredRef.current?.();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
108
117
|
connector.emitter.on('message', handler);
|
|
109
118
|
return (): void => {
|
|
110
119
|
connector.emitter.off('message', handler);
|
|
@@ -122,26 +131,38 @@ export const useWagmiConnect = ({
|
|
|
122
131
|
(connectorId: ConnectorId): void => {
|
|
123
132
|
const connector = resolveWagmiConnector(config.connectors, connectorId);
|
|
124
133
|
if (!connector) {
|
|
134
|
+
pendingAttemptId.current = null;
|
|
125
135
|
onErrorRef.current(new Error(`No wagmi connector found for ${connectorId}`), connectorId);
|
|
126
136
|
return;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
onQrUriRef.current(null);
|
|
140
|
+
resetDeeplinkSignal();
|
|
130
141
|
const attemptId = ++attemptCounter.current;
|
|
131
142
|
pendingConnectorId.current = connectorId;
|
|
132
143
|
connectAsync({ connector })
|
|
133
144
|
.then((result) => {
|
|
134
|
-
if (attemptCounter.current !== attemptId)
|
|
135
|
-
|
|
145
|
+
if (attemptCounter.current !== attemptId) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// connectAsync 의 resolve 값에 계정이 비어 오는 경우가 있다 (MetaMask 모바일에서
|
|
149
|
+
// authorized 직후 accounts 채워짐이 한 tick 늦는 race). 그때도 wagmi 스토어에는
|
|
150
|
+
// 이미 주소가 들어와 있으므로 '연결됐는가'의 원본인 스토어를 fallback 으로 쓴다.
|
|
151
|
+
// 이 fallback 이 없으면 성공한 연결 위에 실패/타임아웃 화면이 덮인다.
|
|
152
|
+
const address = result.accounts?.[0] ?? getAccount(config).address;
|
|
136
153
|
if (!address) {
|
|
137
154
|
throw new Error('Connected but no account returned');
|
|
138
155
|
}
|
|
139
156
|
pendingConnectorId.current = null;
|
|
157
|
+
pendingAttemptId.current = null;
|
|
140
158
|
onConnectRef.current({ address, connectorId });
|
|
141
159
|
})
|
|
142
160
|
.catch((error) => {
|
|
143
|
-
if (attemptCounter.current !== attemptId)
|
|
161
|
+
if (attemptCounter.current !== attemptId) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
144
164
|
pendingConnectorId.current = null;
|
|
165
|
+
pendingAttemptId.current = null;
|
|
145
166
|
onErrorRef.current(error as Error, connectorId);
|
|
146
167
|
});
|
|
147
168
|
},
|
|
@@ -151,9 +172,10 @@ export const useWagmiConnect = ({
|
|
|
151
172
|
const cancelConnect = useCallback((): void => {
|
|
152
173
|
attemptCounter.current += 1;
|
|
153
174
|
pendingConnectorId.current = null;
|
|
175
|
+
pendingAttemptId.current = null;
|
|
154
176
|
onQrUriRef.current(null);
|
|
155
177
|
reset();
|
|
156
|
-
}, [reset]);
|
|
178
|
+
}, [isPending, reset]);
|
|
157
179
|
|
|
158
180
|
return { triggerConnect, cancelConnect, isConnecting: isPending };
|
|
159
181
|
};
|
package/src/index.ts
CHANGED
|
@@ -34,15 +34,29 @@ export const AllWallets: FC<AllWalletsProps> = ({ wallets, isLoading, onSelectWa
|
|
|
34
34
|
return (
|
|
35
35
|
<div className="ck-all-wallets">
|
|
36
36
|
<div className="ck-all-wallets-controls">
|
|
37
|
-
<SearchBar
|
|
37
|
+
<SearchBar
|
|
38
|
+
value={search}
|
|
39
|
+
onChange={(value) => {
|
|
40
|
+
setSearch(value);
|
|
41
|
+
}}
|
|
42
|
+
onClear={() => {
|
|
43
|
+
setSearch('');
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
38
46
|
<div className="ck-wc-filter-btn">
|
|
39
47
|
<img className="ck-wc-filter-logo" src={wcLogo} alt="" aria-hidden="true" />
|
|
40
|
-
<Toggle
|
|
48
|
+
<Toggle
|
|
49
|
+
checked={filterInstalled}
|
|
50
|
+
label=""
|
|
51
|
+
onChange={(checked) => {
|
|
52
|
+
setFilterInstalled(checked);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
41
55
|
</div>
|
|
42
56
|
</div>
|
|
43
57
|
{!isLoading && filtered.length === 0 ? (
|
|
44
58
|
<div className="ck-all-wallets-empty" role="status">
|
|
45
|
-
<p>No
|
|
59
|
+
<p>No Wallet found</p>
|
|
46
60
|
</div>
|
|
47
61
|
) : (
|
|
48
62
|
<WalletGrid wallets={filtered} isLoading={isLoading} onSelect={onSelectWallet} />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FC, type SVGProps } from 'react';
|
|
2
|
+
|
|
3
|
+
export const WarningIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
|
4
|
+
return (
|
|
5
|
+
<svg
|
|
6
|
+
width={20}
|
|
7
|
+
height={18}
|
|
8
|
+
viewBox="0 0 20 18"
|
|
9
|
+
fill="none"
|
|
10
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
focusable="false"
|
|
13
|
+
{...props}
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M9.05688 0.833252C9.47596 0.107398 10.5234 0.107541 10.9426 0.833252L19.6663 15.9417C20.0852 16.6675 19.561 17.5744 18.7229 17.5745H1.27759C0.43938 17.5745 -0.0848319 16.6676 0.334229 15.9417L9.05688 0.833252ZM9.26685 13.0491V14.5149H10.7327V13.0491H9.26685ZM9.26685 6.65747V12.1018H10.7327V6.65747H9.26685Z"
|
|
17
|
+
fill="currentColor"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
Binary file
|
|
@@ -1,32 +1,56 @@
|
|
|
1
1
|
import { type FC } from 'react';
|
|
2
|
-
import './style.scss';
|
|
3
|
-
import graphic from '../../../../assets/graphic.png';
|
|
4
2
|
import { LinkIcon } from '../asset/linkIcon';
|
|
3
|
+
import placeholderImage from './assets/banner-placeholder.png';
|
|
4
|
+
import './style.scss';
|
|
5
|
+
|
|
6
|
+
interface IdleProps {
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
linkText?: string;
|
|
10
|
+
linkUrl?: string;
|
|
11
|
+
imageUrl?: string;
|
|
12
|
+
titleColor?: string;
|
|
13
|
+
descriptionColor?: string;
|
|
14
|
+
linkTextColor?: string;
|
|
15
|
+
}
|
|
5
16
|
|
|
6
|
-
export const Idle: FC = (
|
|
17
|
+
export const Idle: FC<IdleProps> = ({
|
|
18
|
+
title = 'PenguinWallet',
|
|
19
|
+
description = `Your non-custodial gateway to the Creditcoin network.
|
|
20
|
+
Hold tokens, swap, and play ecosystem games in one app.`,
|
|
21
|
+
linkText = 'Download',
|
|
22
|
+
// TODO: Change to Penguin Wallet
|
|
23
|
+
linkUrl = 'https://creditcoin.org/Credit-Wallet',
|
|
24
|
+
imageUrl = placeholderImage,
|
|
25
|
+
titleColor = '#FFFFFF',
|
|
26
|
+
descriptionColor = '#888A8C',
|
|
27
|
+
linkTextColor = '#3374FF',
|
|
28
|
+
}) => {
|
|
7
29
|
return (
|
|
8
30
|
<div className="ck-idle">
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
<img className="ck-idle-bg" src={imageUrl} alt="" />
|
|
32
|
+
{title && (
|
|
33
|
+
<h3 className="ck-idle-title" style={{ color: titleColor }}>
|
|
34
|
+
{title}
|
|
35
|
+
</h3>
|
|
36
|
+
)}
|
|
37
|
+
{description && (
|
|
38
|
+
<p className="ck-idle-desc" style={{ color: descriptionColor }}>
|
|
39
|
+
{description}
|
|
18
40
|
</p>
|
|
41
|
+
)}
|
|
42
|
+
{linkText && linkUrl && (
|
|
19
43
|
<a
|
|
20
44
|
className="ck-idle-link"
|
|
21
|
-
href=
|
|
45
|
+
href={linkUrl}
|
|
22
46
|
target="_blank"
|
|
23
47
|
rel="noopener noreferrer"
|
|
24
|
-
|
|
48
|
+
style={{ color: linkTextColor }}
|
|
25
49
|
>
|
|
26
|
-
<span>
|
|
50
|
+
<span>{linkText}</span>
|
|
27
51
|
<LinkIcon />
|
|
28
52
|
</a>
|
|
29
|
-
|
|
53
|
+
)}
|
|
30
54
|
</div>
|
|
31
55
|
);
|
|
32
56
|
};
|
|
@@ -1,61 +1,52 @@
|
|
|
1
1
|
@use '../../../style/mixin.scss' as *;
|
|
2
|
+
.ck-root {
|
|
3
|
+
.ck-idle {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: flex-start;
|
|
7
|
+
gap: 6px;
|
|
8
|
+
position: absolute;
|
|
9
|
+
inset: 0;
|
|
10
|
+
z-index: 0;
|
|
11
|
+
padding: 60px;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
2
14
|
|
|
3
|
-
.ck-idle {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
align-items: center;
|
|
7
|
-
justify-content: center;
|
|
8
|
-
gap: 20px;
|
|
9
|
-
height: 100%;
|
|
10
|
-
padding: 0px 40px;
|
|
11
|
-
margin-top: -30px;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.ck-idle-graphic {
|
|
15
|
-
max-width: 440px;
|
|
16
|
-
height: auto;
|
|
17
|
-
max-height: 240px;
|
|
18
|
-
flex-shrink: 0;
|
|
19
|
-
margin: 0;
|
|
20
|
-
|
|
21
|
-
img {
|
|
15
|
+
.ck-idle-bg {
|
|
16
|
+
position: absolute;
|
|
17
|
+
inset: 0;
|
|
22
18
|
width: 100%;
|
|
23
19
|
height: 100%;
|
|
24
|
-
object-fit:
|
|
20
|
+
object-fit: cover;
|
|
21
|
+
z-index: -1;
|
|
25
22
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.ck-idle-text {
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: column;
|
|
31
|
-
align-items: center;
|
|
32
|
-
gap: 6px;
|
|
33
|
-
width: 100%;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.ck-idle-title {
|
|
37
|
-
@include textToken('title1-bold');
|
|
38
|
-
color: var(--scale-gray-900);
|
|
39
|
-
text-align: center;
|
|
40
|
-
width: 100%;
|
|
41
|
-
letter-spacing: -0.96px;
|
|
42
|
-
}
|
|
43
23
|
|
|
44
|
-
.ck-idle-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
width: 100%;
|
|
49
|
-
}
|
|
24
|
+
.ck-idle-title {
|
|
25
|
+
@include textToken('title1-bold');
|
|
26
|
+
color: var(--scale-gray-900);
|
|
27
|
+
}
|
|
50
28
|
|
|
51
|
-
.ck-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
span {
|
|
29
|
+
.ck-idle-desc {
|
|
30
|
+
color: var(--scale-gray-600);
|
|
31
|
+
margin-top: 6px;
|
|
32
|
+
white-space: pre-line;
|
|
56
33
|
@include textToken('body4-l1-regular');
|
|
57
34
|
}
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
|
|
36
|
+
.ck-idle-link {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
margin: -2px -8px -8px -8px;
|
|
40
|
+
padding: 8px;
|
|
41
|
+
|
|
42
|
+
span {
|
|
43
|
+
@include textToken('body4-l1-bold');
|
|
44
|
+
color: inherit;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
svg {
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
color: inherit;
|
|
50
|
+
}
|
|
60
51
|
}
|
|
61
|
-
}
|
|
52
|
+
}
|