@gluwa/connect-kit 0.2.0-next.1 → 0.2.0-next.3
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 +11 -0
- package/assets/creditwallet.png +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Medium.woff2 +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-Regular.woff2 +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.woff2 +0 -0
- package/assets/graphic.png +0 -0
- package/assets/metamask.png +0 -0
- package/assets/wc-logo.svg +3 -0
- package/assets/wc-search.png +0 -0
- package/dist/chunk-EDNRW47A.js +401 -0
- package/dist/credit-connect.js +2 -409
- package/dist/index.css +1903 -390
- package/dist/index.d.ts +16 -10
- package/dist/index.js +1110 -870
- package/dist/package.json +7 -6
- package/package.json +8 -8
- package/src/ConnectKitProvider.tsx +90 -65
- package/src/assets.d.ts +5 -0
- package/src/components/actionItem/index.tsx +50 -0
- package/src/components/actionItem/style.scss +47 -0
- package/src/components/copyLink/index.tsx +16 -0
- package/src/components/copyLink/style.scss +16 -0
- package/src/components/qrArea/index.tsx +70 -0
- package/src/components/qrArea/style.scss +130 -0
- package/src/components/searchBar/asset/Search.tsx +20 -0
- package/src/components/searchBar/asset/XMarkCircle.tsx +24 -0
- package/src/components/searchBar/index.tsx +44 -0
- package/src/components/searchBar/style.scss +58 -0
- package/src/components/snackbar/index.tsx +25 -0
- package/src/components/snackbar/style.scss +51 -0
- package/src/components/toggle/index.tsx +27 -0
- package/src/components/toggle/style.scss +53 -0
- package/src/components/walletGrid/index.tsx +64 -0
- package/src/components/walletGrid/style.scss +108 -0
- package/src/components/walletItem/index.tsx +49 -0
- package/src/components/walletItem/style.scss +70 -0
- package/src/connector-meta.ts +12 -7
- package/src/core/config.ts +20 -1
- package/src/creditConnectConnector.ts +17 -25
- package/src/hooks/useWCState.ts +22 -5
- package/src/hooks/useWagmiConnect.ts +0 -1
- package/src/index.ts +2 -2
- package/src/layout/allWallets/index.tsx +52 -0
- package/src/layout/allWallets/style.scss +62 -0
- package/src/layout/connectDialog/asset/backIcon.tsx +24 -0
- package/src/layout/connectDialog/asset/checkIcon.tsx +28 -0
- package/src/layout/connectDialog/asset/closeIcon.tsx +21 -0
- package/src/layout/connectDialog/asset/copyIcon.tsx +28 -0
- package/src/layout/connectDialog/asset/linkIcon.tsx +24 -0
- package/src/layout/connectDialog/asset/spinner.tsx +21 -0
- package/src/layout/connectDialog/idle/index.tsx +32 -0
- package/src/layout/connectDialog/idle/style.scss +61 -0
- package/src/layout/connectDialog/index.tsx +286 -0
- package/src/layout/connectDialog/loading/index.tsx +13 -0
- package/src/layout/connectDialog/loading/style.scss +19 -0
- package/src/layout/connectDialog/metaMask/index.tsx +61 -0
- package/src/layout/connectDialog/metaMask/style.scss +65 -0
- package/src/layout/connectDialog/qr/index.tsx +33 -0
- package/src/layout/connectDialog/style.scss +95 -0
- package/src/layout/connectDialog/timeout/index.tsx +31 -0
- package/src/layout/connectDialog/timeout/style.scss +64 -0
- package/src/layout/connectDialog/walletConnect/index.tsx +45 -0
- package/src/layout/connectDialog/walletConnect/style.scss +48 -0
- package/src/layout/connectDialog/walletQR/index.tsx +37 -0
- package/src/layout/detailPanel/index.tsx +147 -0
- package/src/layout/detailPanel/style.scss +64 -0
- package/src/layout/selectorPanel/index.tsx +100 -0
- package/src/layout/selectorPanel/style.scss +113 -0
- package/src/style/color.scss +417 -0
- package/src/style/font.scss +28 -0
- package/src/style/index.scss +5 -0
- package/src/style/mixin.scss +217 -0
- package/src/style/reset.scss +110 -0
- package/src/style/variables.scss +13 -0
- package/src/types.ts +11 -8
- package/src/utils/platform.ts +6 -0
- package/tsup.config.ts +32 -2
- package/dist/chunk-DDA6FP6U.js +0 -7
- package/src/ConnectModal.scss +0 -665
- package/src/ConnectModal.tsx +0 -573
- package/src/components/QRFrame.tsx +0 -43
- package/src/views/CreditWalletView.tsx +0 -58
- package/src/views/IdleView.tsx +0 -10
- package/src/views/MetaMaskView.tsx +0 -66
- package/src/views/SwitchChainView.tsx +0 -72
- package/src/views/WalletConnectView.tsx +0 -206
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { type FC, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useConfig } from 'wagmi';
|
|
3
|
+
import './style.scss';
|
|
4
|
+
import {
|
|
5
|
+
type ConnectorId,
|
|
6
|
+
type ConnectKitTheme,
|
|
7
|
+
type WCWallet,
|
|
8
|
+
type ConnectResult,
|
|
9
|
+
} from '../../types';
|
|
10
|
+
import { type DetailContent } from '../detailPanel';
|
|
11
|
+
import { SelectorPanel } from '../selectorPanel';
|
|
12
|
+
import { DetailPanel } from '../detailPanel';
|
|
13
|
+
import { Snackbar } from '../../components/snackbar';
|
|
14
|
+
import { resolveConnectorId } from '../../connector-meta';
|
|
15
|
+
import { useWagmiConnect } from '../../hooks/useWagmiConnect';
|
|
16
|
+
import { useWCState } from '../../hooks/useWCState';
|
|
17
|
+
import {
|
|
18
|
+
detectMetaMaskExtension,
|
|
19
|
+
isMobileDevice,
|
|
20
|
+
tryOpenDeepLink,
|
|
21
|
+
tryOpenCreditWalletDeeplink,
|
|
22
|
+
} from '../../utils/platform';
|
|
23
|
+
|
|
24
|
+
export type DialogStep = 'selector' | 'detail';
|
|
25
|
+
|
|
26
|
+
export interface ConnectDialogProps {
|
|
27
|
+
open: boolean;
|
|
28
|
+
theme?: ConnectKitTheme;
|
|
29
|
+
connectors?: ConnectorId[];
|
|
30
|
+
selectedConnector?: ConnectorId | null;
|
|
31
|
+
step?: DialogStep;
|
|
32
|
+
/** DEV: force initial content state for UI preview */
|
|
33
|
+
initialContent?: DetailContent;
|
|
34
|
+
/** WalletConnect Cloud projectId — useWCState.loadWalletList 호출 시 필요. */
|
|
35
|
+
wcProjectId?: string;
|
|
36
|
+
onSelectConnector?: (id: ConnectorId) => void;
|
|
37
|
+
onBack?: () => void;
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
onConnect?: (result: ConnectResult) => void;
|
|
40
|
+
onError?: (error: Error, connectorId: ConnectorId) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const getContentForConnector = (id: ConnectorId): DetailContent => {
|
|
44
|
+
if (id === 'CREDIT_CONNECT') return 'qr';
|
|
45
|
+
if (id === 'METAMASK') return 'metamask-ext';
|
|
46
|
+
if (id === 'WALLET_CONNECT') return 'walletconnect';
|
|
47
|
+
return 'idle';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const ConnectDialog: FC<ConnectDialogProps> = ({
|
|
51
|
+
open,
|
|
52
|
+
theme = 'dark',
|
|
53
|
+
connectors = ['CREDIT_CONNECT', 'METAMASK', 'WALLET_CONNECT'],
|
|
54
|
+
selectedConnector: selectedConnectorProp = null,
|
|
55
|
+
// NOTE: default values below are for dev/markup preview only
|
|
56
|
+
step: stepProp = 'detail',
|
|
57
|
+
wcProjectId,
|
|
58
|
+
onSelectConnector,
|
|
59
|
+
onBack,
|
|
60
|
+
onClose = () => undefined,
|
|
61
|
+
onConnect,
|
|
62
|
+
onError,
|
|
63
|
+
}) => {
|
|
64
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
65
|
+
const [selectedConnector, setSelectedConnector] = useState<ConnectorId | null>(
|
|
66
|
+
selectedConnectorProp,
|
|
67
|
+
);
|
|
68
|
+
const [recentConnector, setRecentConnector] = useState<ConnectorId | null>(null);
|
|
69
|
+
const [selectedWallet, setSelectedWallet] = useState<WCWallet | null>(null);
|
|
70
|
+
const [step, setStep] = useState<DialogStep>(stepProp);
|
|
71
|
+
/**
|
|
72
|
+
* change this initial value to preview a specific content state.
|
|
73
|
+
* - 'idle' selector만 표시 (detail 없음)
|
|
74
|
+
* - 'loading' 스피너
|
|
75
|
+
* - 'qr' Credit Wallet QR
|
|
76
|
+
* - 'timeout' 연결 실패 화면
|
|
77
|
+
* - 'metamask-ext' MetaMask 익스텐션 연결 요청
|
|
78
|
+
* - 'metamask-qr' MetaMask QR
|
|
79
|
+
* - 'walletconnect' WalletConnect QR + All Wallets 버튼
|
|
80
|
+
* - 'all-wallets' 지갑 목록 그리드
|
|
81
|
+
* - 'wallet-qr' 개별 지갑 QR
|
|
82
|
+
*/
|
|
83
|
+
const [content, setContent] = useState<DetailContent>('idle');
|
|
84
|
+
const [showCopySnackbar, setShowCopySnackbar] = useState(false);
|
|
85
|
+
const [qrUri, setQrUri] = useState<string | null>(null);
|
|
86
|
+
|
|
87
|
+
const config = useConfig();
|
|
88
|
+
|
|
89
|
+
// wagmi connect + display_uri 이벤트 → qrUri state.
|
|
90
|
+
// 모든 wagmi connector 의 message 이벤트를 구독하므로 CC connector 의 display_uri emit 도 자동 포착.
|
|
91
|
+
const { triggerConnect, cancelConnect } = useWagmiConnect({
|
|
92
|
+
onConnect: (result) => {
|
|
93
|
+
// recent = 연결 성공 기준. wagmi 가 recentConnectorId 를 localStorage 에 저장하므로
|
|
94
|
+
// UI state 만 동기화. 다음 마운트 시 아래 effect 가 storage 에서 복원.
|
|
95
|
+
setRecentConnector(result.connectorId);
|
|
96
|
+
onConnect?.(result);
|
|
97
|
+
},
|
|
98
|
+
onError: (err, connectorId) => onError?.(err, connectorId),
|
|
99
|
+
onQrUri: (uri) => setQrUri(uri),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// 마운트 시 wagmi storage 의 recentConnectorId(연결 성공 시 wagmi 가 저장) 복원.
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const restoreRecent = async (): Promise<void> => {
|
|
105
|
+
const wagmiId = await config.storage?.getItem('recentConnectorId');
|
|
106
|
+
const resolved = resolveConnectorId(wagmiId ?? undefined);
|
|
107
|
+
if (resolved) setRecentConnector(resolved);
|
|
108
|
+
};
|
|
109
|
+
restoreRecent().catch(() => undefined);
|
|
110
|
+
}, [config]);
|
|
111
|
+
|
|
112
|
+
// WalletConnect Explorer API (walletList + loading + 등)
|
|
113
|
+
const wc = useWCState();
|
|
114
|
+
|
|
115
|
+
const handleSelect = (id: ConnectorId) => {
|
|
116
|
+
setSelectedConnector(id);
|
|
117
|
+
// recent 는 클릭이 아니라 연결 성공(onConnect) 시점에 기록 — 위 useWagmiConnect onConnect 참조.
|
|
118
|
+
// MetaMask 는 확장이 있으면 즉시 extension 연결, 아니면 QR.
|
|
119
|
+
const initialContent: DetailContent =
|
|
120
|
+
id === 'METAMASK' && !detectMetaMaskExtension() ? 'metamask-qr' : getContentForConnector(id);
|
|
121
|
+
setContent(initialContent);
|
|
122
|
+
setStep('detail');
|
|
123
|
+
// wagmi connect 시작 (display_uri 도착 시 setQrUri 자동 호출)
|
|
124
|
+
triggerConnect(id);
|
|
125
|
+
onSelectConnector?.(id);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const handleSelectWallet = (wallet: WCWallet) => {
|
|
129
|
+
setSelectedWallet(wallet);
|
|
130
|
+
setContent('wallet-qr');
|
|
131
|
+
// 자동 deeplink 는 아래 useEffect (selectedWallet + qrUri + content === 'wallet-qr') 에서.
|
|
132
|
+
// 모바일 + display_uri 도착 시 native/universal scheme 으로 redirect, 아니면 WalletQR fallback.
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleBack = () => {
|
|
136
|
+
if (content === 'wallet-qr') {
|
|
137
|
+
setSelectedWallet(null);
|
|
138
|
+
setContent('all-wallets');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (content === 'all-wallets') {
|
|
142
|
+
setContent('walletconnect');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// selector 로 복귀 → wagmi connect attempt 취소 + qrUri 초기화
|
|
146
|
+
cancelConnect();
|
|
147
|
+
setQrUri(null);
|
|
148
|
+
setSelectedConnector(null);
|
|
149
|
+
setContent('idle');
|
|
150
|
+
setStep('selector');
|
|
151
|
+
onBack?.();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const handleShowAllWallets = () => {
|
|
155
|
+
setContent('all-wallets');
|
|
156
|
+
// 처음 진입 시 WC Explorer API fetch (loadedRef guard 로 중복 호출 없음)
|
|
157
|
+
if (wcProjectId) {
|
|
158
|
+
wc.loadWalletList(wcProjectId).catch(() => undefined);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// showModal / close driven by open prop
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const dialog = dialogRef.current;
|
|
165
|
+
if (!dialog) return;
|
|
166
|
+
if (open && !dialog.open) dialog.showModal();
|
|
167
|
+
if (!open && dialog.open) dialog.close();
|
|
168
|
+
}, [open]);
|
|
169
|
+
|
|
170
|
+
// selectedWallet 가 먼저 set 됐고 qrUri 가 뒤늦게 도착한 경우 자동 deeplink.
|
|
171
|
+
// handleSelectWallet 의 inline 호출과 중복 방지를 위해 deeplinkTriedRef 가드.
|
|
172
|
+
const deeplinkTriedRef = useRef(false);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
deeplinkTriedRef.current = false;
|
|
175
|
+
}, [selectedWallet]);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (deeplinkTriedRef.current) return;
|
|
178
|
+
if (!selectedWallet || !qrUri || content !== 'wallet-qr') return;
|
|
179
|
+
if (!isMobileDevice()) return;
|
|
180
|
+
const base = selectedWallet.mobileNative || selectedWallet.mobileUniversal;
|
|
181
|
+
if (!base) return;
|
|
182
|
+
deeplinkTriedRef.current = true;
|
|
183
|
+
tryOpenDeepLink(qrUri, base);
|
|
184
|
+
}, [selectedWallet, qrUri, content]);
|
|
185
|
+
|
|
186
|
+
// CC connector direct path: selector 의 'Continue with Credit Wallet' → content='qr'.
|
|
187
|
+
// wagmi MM connector 는 SDK 가 자체 deeplink 하지만 CC connector 는 emit display_uri 만 함 —
|
|
188
|
+
// kit 가 mobile redirect 책임. payload 는 full URL (`https://app.creditcoin.org/?creditconnect=...`).
|
|
189
|
+
const creditConnectDeeplinkTriedRef = useRef(false);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
creditConnectDeeplinkTriedRef.current = false;
|
|
192
|
+
}, [selectedConnector]);
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
if (creditConnectDeeplinkTriedRef.current) return;
|
|
195
|
+
if (content !== 'qr' || !qrUri) return;
|
|
196
|
+
if (selectedConnector !== 'CREDIT_CONNECT') return;
|
|
197
|
+
if (!isMobileDevice()) return;
|
|
198
|
+
creditConnectDeeplinkTriedRef.current = true;
|
|
199
|
+
tryOpenCreditWalletDeeplink(qrUri);
|
|
200
|
+
}, [content, qrUri, selectedConnector]);
|
|
201
|
+
|
|
202
|
+
// Prevent background scroll while dialog is open
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const prev = document.body.style.overflow;
|
|
205
|
+
if (open) document.body.style.overflow = 'hidden';
|
|
206
|
+
return () => {
|
|
207
|
+
document.body.style.overflow = prev;
|
|
208
|
+
};
|
|
209
|
+
}, [open]);
|
|
210
|
+
|
|
211
|
+
// Reset on close
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!open) {
|
|
214
|
+
cancelConnect();
|
|
215
|
+
setQrUri(null);
|
|
216
|
+
setSelectedConnector(null);
|
|
217
|
+
setContent('idle');
|
|
218
|
+
setStep('selector');
|
|
219
|
+
setShowCopySnackbar(false);
|
|
220
|
+
}
|
|
221
|
+
}, [open, cancelConnect]);
|
|
222
|
+
|
|
223
|
+
// Intercept native Escape (cancel event) and delegate to onClose
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
const dialog = dialogRef.current;
|
|
226
|
+
if (!dialog) return undefined;
|
|
227
|
+
const handleCancel = (e: Event) => {
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
setShowCopySnackbar(false);
|
|
230
|
+
onClose();
|
|
231
|
+
};
|
|
232
|
+
dialog.addEventListener('cancel', handleCancel);
|
|
233
|
+
return () => dialog.removeEventListener('cancel', handleCancel);
|
|
234
|
+
}, [onClose]);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<div className="ck-root" data-theme={theme} data-open={open}>
|
|
238
|
+
<dialog
|
|
239
|
+
ref={dialogRef}
|
|
240
|
+
className="ck-dialog"
|
|
241
|
+
data-step={step}
|
|
242
|
+
aria-label="Connect Wallet"
|
|
243
|
+
onClick={(e) => {
|
|
244
|
+
if (e.target === dialogRef.current) onClose();
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<SelectorPanel
|
|
248
|
+
connectors={connectors}
|
|
249
|
+
selectedConnector={selectedConnector}
|
|
250
|
+
recentConnector={recentConnector}
|
|
251
|
+
onSelect={handleSelect}
|
|
252
|
+
onClose={onClose}
|
|
253
|
+
/>
|
|
254
|
+
<DetailPanel
|
|
255
|
+
selectedConnector={selectedConnector}
|
|
256
|
+
selectedWallet={selectedWallet}
|
|
257
|
+
content={content}
|
|
258
|
+
qrUri={qrUri}
|
|
259
|
+
wallets={wc.walletList}
|
|
260
|
+
walletListLoading={wc.walletListLoading}
|
|
261
|
+
walletCount={wc.walletList.length}
|
|
262
|
+
onBack={handleBack}
|
|
263
|
+
onClose={onClose}
|
|
264
|
+
onShowAllWallets={handleShowAllWallets}
|
|
265
|
+
onSelectWallet={handleSelectWallet}
|
|
266
|
+
onCopyLink={() => {
|
|
267
|
+
// qrUri 미도착 시 silent no-op (사용자가 잘못 누른 게 아니라 wagmi 가 아직 안 emit).
|
|
268
|
+
if (!qrUri) return;
|
|
269
|
+
navigator.clipboard
|
|
270
|
+
.writeText(qrUri)
|
|
271
|
+
.then(() => setShowCopySnackbar(true))
|
|
272
|
+
.catch((err) => {
|
|
273
|
+
// eslint-disable-next-line no-console
|
|
274
|
+
console.warn('[copyLink] clipboard write failed', err);
|
|
275
|
+
});
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
{showCopySnackbar && (
|
|
279
|
+
<div className="ck-dialog-snackbar">
|
|
280
|
+
<Snackbar message="Link copied" onClose={() => setShowCopySnackbar(false)} />
|
|
281
|
+
</div>
|
|
282
|
+
)}
|
|
283
|
+
</dialog>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
import { Spinner } from '../asset/spinner';
|
|
4
|
+
|
|
5
|
+
export const Loading: FC = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div className="ck-loading">
|
|
8
|
+
<span className="ck-loading-spinner" role="status" aria-label="Connecting...">
|
|
9
|
+
<Spinner />
|
|
10
|
+
</span>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@use '../../../style/mixin.scss' as *;
|
|
2
|
+
|
|
3
|
+
.ck-loading {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
flex: 1;
|
|
8
|
+
min-height: 200px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ck-loading-spinner {
|
|
12
|
+
display: flex;
|
|
13
|
+
color: var(--scale-gray-400);
|
|
14
|
+
animation: ck-spin 2.5s linear infinite;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@keyframes ck-spin {
|
|
18
|
+
to { transform: rotate(360deg); }
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
import { QrArea } from '../../../components/qrArea';
|
|
4
|
+
import { ActionItem } from '../../../components/actionItem';
|
|
5
|
+
import { Spinner } from '../asset/spinner';
|
|
6
|
+
|
|
7
|
+
type MetaMaskVariant = 'extension' | 'qr';
|
|
8
|
+
|
|
9
|
+
interface MetaMaskProps {
|
|
10
|
+
variant: MetaMaskVariant;
|
|
11
|
+
uri?: string | null;
|
|
12
|
+
logoUrl?: string;
|
|
13
|
+
downloadUrl?: string;
|
|
14
|
+
onCopy?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MetaMask: FC<MetaMaskProps> = ({
|
|
18
|
+
variant,
|
|
19
|
+
uri,
|
|
20
|
+
logoUrl,
|
|
21
|
+
downloadUrl = '',
|
|
22
|
+
onCopy,
|
|
23
|
+
}) => {
|
|
24
|
+
if (variant === 'extension') {
|
|
25
|
+
return (
|
|
26
|
+
<div className="ck-metamask ck-metamask-extension">
|
|
27
|
+
{logoUrl && (
|
|
28
|
+
<img className="ck-metamask-logo" src={logoUrl} alt="MetaMask" width={48} height={48} />
|
|
29
|
+
)}
|
|
30
|
+
<div className="ck-metamask-text">
|
|
31
|
+
<h3 className="ck-metamask-title">Open MetaMask</h3>
|
|
32
|
+
<p className="ck-metamask-desc">Check the connection in your extension</p>
|
|
33
|
+
</div>
|
|
34
|
+
<span className="ck-metamask-spinner" aria-label="Connecting...">
|
|
35
|
+
<Spinner />
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<QrArea
|
|
44
|
+
uri={uri}
|
|
45
|
+
status={!uri ? 'loading' : 'ready'}
|
|
46
|
+
logoUrl={logoUrl}
|
|
47
|
+
hasBg
|
|
48
|
+
label="Scan this QR Code with your phone"
|
|
49
|
+
onCopy={onCopy}
|
|
50
|
+
/>
|
|
51
|
+
<ActionItem
|
|
52
|
+
as="a"
|
|
53
|
+
href={downloadUrl}
|
|
54
|
+
target="_blank"
|
|
55
|
+
rel="noopener noreferrer"
|
|
56
|
+
label="Don't have MetaMask Wallet?"
|
|
57
|
+
action="Download"
|
|
58
|
+
/>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@use '../../../style/mixin.scss' as *;
|
|
2
|
+
|
|
3
|
+
// ── Extension ─────────────────────────────────────────────────────────────────
|
|
4
|
+
.ck-metamask-extension {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
gap: 16px;
|
|
10
|
+
flex: 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ck-metamask-logo {
|
|
14
|
+
width: 48px;
|
|
15
|
+
height: 48px;
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
object-fit: cover;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.ck-metamask-text {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0;
|
|
26
|
+
text-align: center;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ck-metamask-title {
|
|
30
|
+
@include textToken('subtitle2-bold');
|
|
31
|
+
color: var(--scale-gray-900);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.ck-metamask-desc {
|
|
35
|
+
@include textToken('body3-l1-regular');
|
|
36
|
+
color: var(--scale-gray-600);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.ck-metamask-spinner {
|
|
40
|
+
display: flex;
|
|
41
|
+
color: var(--scale-gray-400);
|
|
42
|
+
animation: ck-spin 2.5s linear infinite;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes ck-spin {
|
|
46
|
+
to { transform: rotate(360deg); }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
.ck-root .ck-metamask-copy-btn {
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: 6px;
|
|
55
|
+
@include textToken('body3-l1-regular');
|
|
56
|
+
color: var(--scale-gray-500);
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ck-metamask-copy-icon {
|
|
61
|
+
display: block;
|
|
62
|
+
width: 20px;
|
|
63
|
+
height: 20px;
|
|
64
|
+
// Icon SVG will be added later
|
|
65
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import { QrArea } from '../../../components/qrArea';
|
|
3
|
+
import { ActionItem } from '../../../components/actionItem';
|
|
4
|
+
|
|
5
|
+
interface QRProps {
|
|
6
|
+
uri?: string | null;
|
|
7
|
+
logoUrl?: string;
|
|
8
|
+
downloadUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const QR: FC<QRProps> = ({ uri, logoUrl, downloadUrl }) => {
|
|
12
|
+
const status = !uri ? 'loading' : 'ready';
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<QrArea
|
|
17
|
+
uri={uri}
|
|
18
|
+
status={status}
|
|
19
|
+
logoUrl={logoUrl}
|
|
20
|
+
label="Open the Credit Wallet app and scan the QR"
|
|
21
|
+
/>
|
|
22
|
+
<ActionItem
|
|
23
|
+
as="a"
|
|
24
|
+
href={downloadUrl ?? '#'}
|
|
25
|
+
target="_blank"
|
|
26
|
+
rel="noopener noreferrer"
|
|
27
|
+
label="Get Credit Wallet app"
|
|
28
|
+
description="Once connected, you can log in with it next time"
|
|
29
|
+
action="Download"
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
@use '../../style/index.scss';
|
|
2
|
+
@use '../../style/mixin.scss' as *;
|
|
3
|
+
|
|
4
|
+
.ck-root {
|
|
5
|
+
display: contents;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ck-dialog::backdrop {
|
|
9
|
+
background: var(--dialog-backdrop-color);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.ck-root .ck-dialog:not([open]) {
|
|
13
|
+
display: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ck-root .ck-dialog {
|
|
17
|
+
--dialog-backdrop-color: var(--semantic-overlay-overlay-dim);
|
|
18
|
+
position: fixed;
|
|
19
|
+
inset: 0;
|
|
20
|
+
margin: auto;
|
|
21
|
+
display: grid;
|
|
22
|
+
grid-template-columns: 1fr 1fr;
|
|
23
|
+
width: calc(100% - 40px);
|
|
24
|
+
max-width: 1120px;
|
|
25
|
+
height: min(620px, calc(100dvh - 40px));
|
|
26
|
+
|
|
27
|
+
border-radius: 12px;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
background: var(--semantic-paper-dialog);
|
|
30
|
+
border: none;
|
|
31
|
+
padding: 0;
|
|
32
|
+
|
|
33
|
+
@include tablet {
|
|
34
|
+
position: fixed;
|
|
35
|
+
inset: auto 0 0 0;
|
|
36
|
+
margin: 0;
|
|
37
|
+
width: 100%;
|
|
38
|
+
max-width: none;
|
|
39
|
+
grid-template-columns: 1fr;
|
|
40
|
+
max-height: 82dvh;
|
|
41
|
+
height: 100%;
|
|
42
|
+
border-radius: 12px 12px 0 0;
|
|
43
|
+
|
|
44
|
+
&[data-step='selector'] .ck-detail { display: none !important; }
|
|
45
|
+
&[data-step='detail'] .ck-selector { display: none !important; }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ck-root .ck-dialog-snackbar {
|
|
50
|
+
position: fixed;
|
|
51
|
+
top: clamp(20px, 9.2vh, 99px);
|
|
52
|
+
left: 50%;
|
|
53
|
+
z-index: 1001;
|
|
54
|
+
width: min(calc(100vw - 40px), 400px);
|
|
55
|
+
transform: translateX(-50%);
|
|
56
|
+
animation: ck-dialog-snackbar-in 0.2s ease;
|
|
57
|
+
|
|
58
|
+
@include tablet {
|
|
59
|
+
top: clamp(
|
|
60
|
+
calc(env(safe-area-inset-top, 0px) + 20px),
|
|
61
|
+
calc(env(safe-area-inset-top, 0px) + 20px),
|
|
62
|
+
calc(env(safe-area-inset-top, 0px) + 20px)
|
|
63
|
+
);
|
|
64
|
+
left: 20px;
|
|
65
|
+
right: 20px;
|
|
66
|
+
width: auto;
|
|
67
|
+
transform: none;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes ck-dialog-snackbar-in {
|
|
72
|
+
from {
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: translateX(-50%) translateY(8px);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
to {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
transform: translateX(-50%) translateY(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@include tablet {
|
|
84
|
+
@keyframes ck-dialog-snackbar-in {
|
|
85
|
+
from {
|
|
86
|
+
opacity: 0;
|
|
87
|
+
transform: translateY(8px);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
to {
|
|
91
|
+
opacity: 1;
|
|
92
|
+
transform: translateY(0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
|
|
4
|
+
interface TimeoutProps {
|
|
5
|
+
onRetry?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Timeout: FC<TimeoutProps> = ({ onRetry }) => {
|
|
9
|
+
return (
|
|
10
|
+
<div className="ck-timeout">
|
|
11
|
+
<div className="ck-timeout-text">
|
|
12
|
+
<h3 className="ck-timeout-title">Connection Failed</h3>
|
|
13
|
+
<p className="ck-timeout-desc">
|
|
14
|
+
The connection took too long to respond.
|
|
15
|
+
<br />
|
|
16
|
+
Please check your network and try again.
|
|
17
|
+
</p>
|
|
18
|
+
</div>
|
|
19
|
+
<button type="button" className="ck-timeout-retry-btn" onClick={onRetry}>
|
|
20
|
+
Try Again
|
|
21
|
+
</button>
|
|
22
|
+
{/* TODO [D]: add help link after url is confirmed */}
|
|
23
|
+
{/* <p className="ck-timeout-help">
|
|
24
|
+
Need more help?
|
|
25
|
+
<a href="#" className="ck-timeout-help-link">
|
|
26
|
+
Click here
|
|
27
|
+
</a>
|
|
28
|
+
</p> */}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
@use '../../../style/mixin.scss' as *;
|
|
2
|
+
|
|
3
|
+
.ck-timeout {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
flex: 1;
|
|
9
|
+
gap: 24px;
|
|
10
|
+
text-align: center;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ck-timeout-text {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 6px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ck-timeout-title {
|
|
20
|
+
@include textToken('title3-bold');
|
|
21
|
+
color: var(--scale-gray-900);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ck-timeout-desc {
|
|
25
|
+
@include textToken('body4-l1-regular');
|
|
26
|
+
color: var(--scale-gray-600);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ck-root .ck-timeout-retry-btn {
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: center;
|
|
33
|
+
padding: 12px 28px;
|
|
34
|
+
border: 1px solid var(--semantic-divider-divider-4);
|
|
35
|
+
border-radius: 500px;
|
|
36
|
+
@include textToken('label4-bold');
|
|
37
|
+
color: var(--scale-gray-900);
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: background 0.15s ease;
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
background: var(--scale-gray-100);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ck-timeout-help {
|
|
47
|
+
@include textToken('body4-l1-regular');
|
|
48
|
+
color: var(--scale-gray-500);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ck-root .ck-timeout-help-link {
|
|
52
|
+
color: var(--semantic-brand-trugi-primary);
|
|
53
|
+
text-decoration: none;
|
|
54
|
+
|
|
55
|
+
&:hover {
|
|
56
|
+
text-decoration: underline;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@include tablet {
|
|
61
|
+
.ck-timeout {
|
|
62
|
+
min-height: 491px;
|
|
63
|
+
}
|
|
64
|
+
}
|