@gluwa/connect-kit 0.1.0-next.3 → 0.2.0-next.10
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 +68 -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/penguinwallet.png +0 -0
- package/assets/wc-logo.svg +3 -0
- package/assets/wc-search.png +0 -0
- package/dist/chunk-VE5LB7BN.js +405 -0
- package/dist/credit-connect.js +2 -394
- package/dist/index.css +2075 -0
- package/dist/index.d.ts +58 -33
- package/dist/index.js +1748 -1503
- package/dist/package.json +16 -10
- package/package.json +14 -11
- package/src/ConnectKitProvider.tsx +133 -75
- package/src/api/account.ts +21 -0
- package/src/api/asset.ts +10 -0
- package/src/api/balance.ts +10 -0
- package/src/api/chain.ts +17 -0
- package/src/api/contract.ts +33 -0
- package/src/api/transaction.ts +12 -0
- 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 +72 -0
- package/src/components/qrArea/style.scss +122 -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/toast/index.tsx +41 -0
- package/src/components/toast/style.scss +108 -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 +16 -11
- package/src/core/config.ts +102 -0
- package/src/creditConnectConnector.ts +198 -184
- package/src/events/account.ts +76 -0
- package/src/hooks/useConnectBanner.ts +79 -0
- package/src/hooks/useConnectTimeout.ts +92 -0
- package/src/hooks/useMetaMaskExtension.ts +29 -0
- package/src/hooks/useWCState.ts +45 -14
- package/src/hooks/useWagmiConnect.ts +42 -11
- package/src/index.ts +38 -2
- package/src/layout/allWallets/index.tsx +52 -0
- package/src/layout/allWallets/style.scss +57 -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/asset/warningIcon.tsx +21 -0
- package/src/layout/connectDialog/idle/assets/banner-placeholder.png +0 -0
- package/src/layout/connectDialog/idle/index.tsx +55 -0
- package/src/layout/connectDialog/idle/style.scss +52 -0
- package/src/layout/connectDialog/index.tsx +432 -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 +48 -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 +175 -0
- package/src/layout/detailPanel/style.scss +70 -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 +40 -7
- package/src/utils/platform.ts +6 -7
- package/src/wallet-display-override.ts +15 -0
- package/tsconfig.json +5 -1
- package/tsup.config.ts +32 -2
- package/assets/creditwallet.png +0 -0
- package/dist/chunk-DDA6FP6U.js +0 -7
- package/src/ConnectModal.scss +0 -665
- package/src/ConnectModal.tsx +0 -559
- package/src/components/QRFrame.tsx +0 -43
- package/src/views/CreditWalletView.tsx +0 -59
- package/src/views/IdleView.tsx +0 -10
- package/src/views/MetaMaskView.tsx +0 -64
- package/src/views/SwitchChainView.tsx +0 -67
- package/src/views/WalletConnectView.tsx +0 -206
package/src/ConnectModal.tsx
DELETED
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useMemo, type FC, type ReactNode } from 'react';
|
|
2
|
-
import './ConnectModal.scss';
|
|
3
|
-
import { useAccount, useConfig, useDisconnect } from 'wagmi';
|
|
4
|
-
import {
|
|
5
|
-
type ConnectModalProps,
|
|
6
|
-
type ConnectErrorContext,
|
|
7
|
-
type ConnectErrorReason,
|
|
8
|
-
type Connectors,
|
|
9
|
-
type ConnectorId,
|
|
10
|
-
type WCWallet,
|
|
11
|
-
type WCSubView,
|
|
12
|
-
} from './types';
|
|
13
|
-
import { CONNECTOR_META } from './connector-meta';
|
|
14
|
-
import { detectMetaMaskExtension } from './utils/platform';
|
|
15
|
-
import { useWagmiConnect, resolveWagmiConnector } from './hooks/useWagmiConnect';
|
|
16
|
-
import { useWCState, type WCState } from './hooks/useWCState';
|
|
17
|
-
import { IdleView } from './views/IdleView';
|
|
18
|
-
import { CreditWalletView } from './views/CreditWalletView';
|
|
19
|
-
import { MetaMaskView } from './views/MetaMaskView';
|
|
20
|
-
import { WalletConnectView } from './views/WalletConnectView';
|
|
21
|
-
import { SwitchChainView } from './views/SwitchChainView';
|
|
22
|
-
|
|
23
|
-
const RECENT_KEY = 'connect-kit:recent';
|
|
24
|
-
const readRecentConnector = (): ConnectorId | null => {
|
|
25
|
-
try {
|
|
26
|
-
return localStorage.getItem(RECENT_KEY) as ConnectorId | null;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const writeRecentConnector = (id: ConnectorId): void => {
|
|
33
|
-
try {
|
|
34
|
-
localStorage.setItem(RECENT_KEY, id);
|
|
35
|
-
} catch {}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const classifyError = (error: Error): ConnectErrorReason => {
|
|
39
|
-
const code =
|
|
40
|
-
typeof error === 'object' && error !== null && 'code' in error
|
|
41
|
-
? (error as unknown as { code: unknown }).code
|
|
42
|
-
: undefined;
|
|
43
|
-
if (code === 4001) return 'rejected';
|
|
44
|
-
const message = error.message?.toLowerCase() ?? '';
|
|
45
|
-
if (message.includes('user rejected') || message.includes('user denied')) return 'rejected';
|
|
46
|
-
if (message.includes('unsupported') || message.includes('not installed')) return 'unsupported';
|
|
47
|
-
return 'unknown';
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
interface SelectorPaneProps {
|
|
51
|
-
connectors: ConnectorId[];
|
|
52
|
-
selected: ConnectorId | null;
|
|
53
|
-
recentConnector: ConnectorId | null;
|
|
54
|
-
onSelect: (id: ConnectorId) => void;
|
|
55
|
-
onClose: () => void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const SelectorPane: FC<SelectorPaneProps> = ({
|
|
59
|
-
connectors,
|
|
60
|
-
selected,
|
|
61
|
-
recentConnector,
|
|
62
|
-
onSelect,
|
|
63
|
-
onClose,
|
|
64
|
-
}) => {
|
|
65
|
-
const hasRecent = recentConnector !== null && connectors.includes(recentConnector);
|
|
66
|
-
const recentConnectors = hasRecent ? [recentConnector] : [];
|
|
67
|
-
const otherConnectors = connectors.filter((id) => id !== recentConnector);
|
|
68
|
-
|
|
69
|
-
const downloadUrl = connectors
|
|
70
|
-
.map((id) => CONNECTOR_META[id].downloadUrl)
|
|
71
|
-
.find((url) => url != null);
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="ck-pane ck-pane--selector">
|
|
75
|
-
<div className="ck-pane__header">
|
|
76
|
-
<h3 className="ck-pane__title">Connect Wallet</h3>
|
|
77
|
-
<button
|
|
78
|
-
type="button"
|
|
79
|
-
className="ck-btn-close ck-mobile-only"
|
|
80
|
-
onClick={onClose}
|
|
81
|
-
aria-label="Close"
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div className="ck-connector-list">
|
|
86
|
-
{hasRecent && (
|
|
87
|
-
<>
|
|
88
|
-
{recentConnectors.map((id) => (
|
|
89
|
-
<ConnectorItem
|
|
90
|
-
key={id}
|
|
91
|
-
id={id}
|
|
92
|
-
isSelected={selected === id}
|
|
93
|
-
isRecent
|
|
94
|
-
onSelect={onSelect}
|
|
95
|
-
/>
|
|
96
|
-
))}
|
|
97
|
-
{otherConnectors.length > 0 && (
|
|
98
|
-
<div className="ck-divider">
|
|
99
|
-
<span>OR</span>
|
|
100
|
-
</div>
|
|
101
|
-
)}
|
|
102
|
-
</>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{otherConnectors.map((id) => (
|
|
106
|
-
<ConnectorItem
|
|
107
|
-
key={id}
|
|
108
|
-
id={id}
|
|
109
|
-
isSelected={selected === id}
|
|
110
|
-
isRecent={false}
|
|
111
|
-
onSelect={onSelect}
|
|
112
|
-
/>
|
|
113
|
-
))}
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
{downloadUrl && (
|
|
117
|
-
<div className="ck-pane__footer">
|
|
118
|
-
<a
|
|
119
|
-
href={downloadUrl}
|
|
120
|
-
target="_blank"
|
|
121
|
-
rel="nofollow noopener noreferrer"
|
|
122
|
-
className="ck-link-no-wallet"
|
|
123
|
-
>
|
|
124
|
-
I don't have a wallet >
|
|
125
|
-
</a>
|
|
126
|
-
</div>
|
|
127
|
-
)}
|
|
128
|
-
</div>
|
|
129
|
-
);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
interface ConnectorItemProps {
|
|
133
|
-
id: ConnectorId;
|
|
134
|
-
isSelected: boolean;
|
|
135
|
-
isRecent: boolean;
|
|
136
|
-
onSelect: (id: ConnectorId) => void;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const ConnectorItem: FC<ConnectorItemProps> = ({ id, isSelected, isRecent, onSelect }) => {
|
|
140
|
-
const meta = CONNECTOR_META[id];
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<button
|
|
144
|
-
type="button"
|
|
145
|
-
className={`ck-connector-item ck-connector-item--${id.toLowerCase().replaceAll('_', '-')} ${isSelected ? 'is-active' : ''}`}
|
|
146
|
-
onClick={() => {
|
|
147
|
-
onSelect(id);
|
|
148
|
-
}}
|
|
149
|
-
>
|
|
150
|
-
{meta.logoUrl ? (
|
|
151
|
-
<img className="ck-connector-item__icon" src={meta.logoUrl} alt="" aria-hidden="true" />
|
|
152
|
-
) : (
|
|
153
|
-
<span className="ck-connector-item__icon" aria-hidden="true" />
|
|
154
|
-
)}
|
|
155
|
-
<span className="ck-connector-item__label">{meta.label}</span>
|
|
156
|
-
{isRecent && <span className="ck-connector-item__badge">Recent</span>}
|
|
157
|
-
</button>
|
|
158
|
-
);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
interface DetailPaneProps {
|
|
162
|
-
selectedConnector: ConnectorId | null;
|
|
163
|
-
qrUri: string | null;
|
|
164
|
-
hasMetaMaskExtension: boolean;
|
|
165
|
-
connectorLogoMap: Partial<Record<ConnectorId, string>>;
|
|
166
|
-
wc: WCState;
|
|
167
|
-
errorOverride: ReactNode | null;
|
|
168
|
-
onBack: () => void;
|
|
169
|
-
onClose: () => void;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const DetailPane: FC<DetailPaneProps> = ({
|
|
173
|
-
selectedConnector,
|
|
174
|
-
qrUri,
|
|
175
|
-
hasMetaMaskExtension,
|
|
176
|
-
connectorLogoMap,
|
|
177
|
-
wc,
|
|
178
|
-
errorOverride,
|
|
179
|
-
onBack,
|
|
180
|
-
onClose,
|
|
181
|
-
}) => {
|
|
182
|
-
const title = getDetailTitle(
|
|
183
|
-
selectedConnector,
|
|
184
|
-
hasMetaMaskExtension,
|
|
185
|
-
wc.subView,
|
|
186
|
-
wc.selectedWallet,
|
|
187
|
-
);
|
|
188
|
-
const showBack = selectedConnector !== null;
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<div className="ck-pane ck-pane--detail">
|
|
192
|
-
<div className="ck-pane__header">
|
|
193
|
-
{showBack && (
|
|
194
|
-
<button type="button" className="ck-btn-back" onClick={onBack} aria-label="Back" />
|
|
195
|
-
)}
|
|
196
|
-
<h3 className="ck-pane__title">{title}</h3>
|
|
197
|
-
<button type="button" className="ck-btn-close" onClick={onClose} aria-label="Close" />
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
<div className="ck-pane__body">
|
|
201
|
-
{errorOverride || (
|
|
202
|
-
<>
|
|
203
|
-
{!selectedConnector && <IdleView />}
|
|
204
|
-
|
|
205
|
-
{(selectedConnector === 'CREDIT_CONNECT' || selectedConnector === 'CREDIT_WALLET') && (
|
|
206
|
-
<CreditWalletView connectorId={selectedConnector} qrUri={qrUri} />
|
|
207
|
-
)}
|
|
208
|
-
|
|
209
|
-
{selectedConnector === 'METAMASK' && (
|
|
210
|
-
<MetaMaskView
|
|
211
|
-
qrUri={qrUri}
|
|
212
|
-
hasExtension={hasMetaMaskExtension}
|
|
213
|
-
logoUrl={connectorLogoMap.METAMASK}
|
|
214
|
-
/>
|
|
215
|
-
)}
|
|
216
|
-
|
|
217
|
-
{selectedConnector === 'WALLET_CONNECT' && (
|
|
218
|
-
<WalletConnectView
|
|
219
|
-
subView={wc.subView}
|
|
220
|
-
qrUri={qrUri}
|
|
221
|
-
logoUrl={connectorLogoMap.WALLET_CONNECT}
|
|
222
|
-
walletList={wc.walletList}
|
|
223
|
-
walletListLoading={wc.walletListLoading}
|
|
224
|
-
walletListSearch={wc.walletListSearch}
|
|
225
|
-
walletListFilterActive={wc.filterActive}
|
|
226
|
-
selectedWallet={wc.selectedWallet}
|
|
227
|
-
onShowList={wc.onShowList}
|
|
228
|
-
onSelectWallet={wc.onSelectWallet}
|
|
229
|
-
onSearchChange={wc.onSearchChange}
|
|
230
|
-
onFilterToggle={wc.onFilterToggle}
|
|
231
|
-
/>
|
|
232
|
-
)}
|
|
233
|
-
</>
|
|
234
|
-
)}
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const getDetailTitle = (
|
|
241
|
-
connector: ConnectorId | null,
|
|
242
|
-
hasMetaMaskExtension: boolean,
|
|
243
|
-
wcSubView: WCSubView,
|
|
244
|
-
wcSelectedWallet: WCWallet | null,
|
|
245
|
-
): string => {
|
|
246
|
-
if (!connector) return '';
|
|
247
|
-
|
|
248
|
-
if (connector === 'WALLET_CONNECT') {
|
|
249
|
-
if (wcSubView === 'list') return 'All Wallets';
|
|
250
|
-
if (wcSubView === 'wallet' && wcSelectedWallet) return `Scan with ${wcSelectedWallet.name}`;
|
|
251
|
-
return 'Scan with WalletConnect';
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (connector === 'METAMASK' && hasMetaMaskExtension) return 'MetaMask';
|
|
255
|
-
|
|
256
|
-
return CONNECTOR_META[connector].detailTitle;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const resolveConnectorsFromConfig = (connectorsConfig?: Connectors): ConnectorId[] => {
|
|
260
|
-
if (!connectorsConfig) return [];
|
|
261
|
-
|
|
262
|
-
const connectors: ConnectorId[] = [];
|
|
263
|
-
|
|
264
|
-
if (connectorsConfig.creditWallet === 'walletConnect') connectors.push('CREDIT_WALLET');
|
|
265
|
-
if (connectorsConfig.creditWallet === 'creditConnect') connectors.push('CREDIT_CONNECT');
|
|
266
|
-
if (connectorsConfig.metamask) connectors.push('METAMASK');
|
|
267
|
-
if (connectorsConfig.walletConnect) connectors.push('WALLET_CONNECT');
|
|
268
|
-
|
|
269
|
-
return connectors;
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
export const ConnectModal: FC<ConnectModalProps> = (props) => {
|
|
273
|
-
// open=false면 내부 컴포넌트를 마운트하지 않음
|
|
274
|
-
// → useConfig 등 wagmi hooks가 실행되지 않아 WagmiProvider 없이도 렌더링 가능
|
|
275
|
-
if (!props.open) return null;
|
|
276
|
-
|
|
277
|
-
const resolvedConnectors = resolveConnectorsFromConfig(props.connectors);
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
resolvedConnectors.includes('CREDIT_WALLET') &&
|
|
281
|
-
resolvedConnectors.includes('CREDIT_CONNECT')
|
|
282
|
-
) {
|
|
283
|
-
throw new Error('CREDIT_WALLET and CREDIT_CONNECT cannot be used together');
|
|
284
|
-
}
|
|
285
|
-
if (resolvedConnectors.length === 0) {
|
|
286
|
-
throw new Error('At least one connector must be configured');
|
|
287
|
-
}
|
|
288
|
-
return <ConnectModalInner {...props} connectors={resolvedConnectors} />;
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
type ConnectModalInnerProps = Omit<ConnectModalProps, 'connectors'> & {
|
|
292
|
-
connectors: ConnectorId[];
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const ConnectModalInner: FC<ConnectModalInnerProps> = ({
|
|
296
|
-
open,
|
|
297
|
-
connectors,
|
|
298
|
-
onConnect,
|
|
299
|
-
onClose,
|
|
300
|
-
onLog,
|
|
301
|
-
wcProjectId,
|
|
302
|
-
requiredChainId,
|
|
303
|
-
renderConnectError,
|
|
304
|
-
}) => {
|
|
305
|
-
const [selectedConnector, setSelectedConnector] = useState<ConnectorId | null>(null);
|
|
306
|
-
const [qrUri, setQrUri] = useState<string | null>(null);
|
|
307
|
-
const [recentConnector, setRecentConnector] = useState<ConnectorId | null>(null);
|
|
308
|
-
const [hasMetaMaskExtension, setHasMetaMaskExtension] = useState<boolean>(false);
|
|
309
|
-
const [errorState, setErrorState] = useState<{ connectorId: ConnectorId; error: Error } | null>(
|
|
310
|
-
null,
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const wc = useWCState();
|
|
314
|
-
|
|
315
|
-
// wagmi 상태 — chain mismatch invariant 판정용
|
|
316
|
-
const account = useAccount();
|
|
317
|
-
const { disconnectAsync } = useDisconnect();
|
|
318
|
-
const wagmiConfig = useConfig();
|
|
319
|
-
|
|
320
|
-
const chainMismatch =
|
|
321
|
-
requiredChainId != null &&
|
|
322
|
-
account.status === 'connected' &&
|
|
323
|
-
account.chainId != null &&
|
|
324
|
-
account.chainId !== requiredChainId;
|
|
325
|
-
|
|
326
|
-
const requiredChainName = useMemo<string | undefined>(() => {
|
|
327
|
-
if (requiredChainId == null) return undefined;
|
|
328
|
-
return wagmiConfig.chains.find((chain) => chain.id === requiredChainId)?.name;
|
|
329
|
-
}, [wagmiConfig.chains, requiredChainId]);
|
|
330
|
-
|
|
331
|
-
// QR URI 수신
|
|
332
|
-
const handleQrUri = useCallback((uri: string | null) => {
|
|
333
|
-
setQrUri(uri);
|
|
334
|
-
}, []);
|
|
335
|
-
|
|
336
|
-
// 연결 완료
|
|
337
|
-
const handleConnect = useCallback(
|
|
338
|
-
(result: Parameters<typeof onConnect>[0]) => {
|
|
339
|
-
setErrorState(null);
|
|
340
|
-
onConnect(result);
|
|
341
|
-
// chain mismatch가 있으면 모달은 SwitchChainView로 자동 전환됨 (닫지 않음)
|
|
342
|
-
// Provider 측에서 requiredChainId가 매칭될 때까지 force-open 유지
|
|
343
|
-
onClose();
|
|
344
|
-
},
|
|
345
|
-
[onConnect, onClose],
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
// 연결 실패
|
|
349
|
-
const handleError = useCallback(
|
|
350
|
-
(error: unknown, connectorId: ConnectorId): void => {
|
|
351
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
352
|
-
onLog?.(
|
|
353
|
-
`[connect-kit] connection failed (${connectorId}): ${normalizedError.message}`,
|
|
354
|
-
normalizedError,
|
|
355
|
-
);
|
|
356
|
-
setQrUri(null);
|
|
357
|
-
wc.resetView();
|
|
358
|
-
if (renderConnectError) {
|
|
359
|
-
setErrorState({ connectorId, error: normalizedError });
|
|
360
|
-
} else {
|
|
361
|
-
setSelectedConnector(null);
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
[onLog, wc.resetView, renderConnectError],
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
// 연결 시도
|
|
368
|
-
const { triggerConnect, cancelConnect } = useWagmiConnect({
|
|
369
|
-
onConnect: handleConnect,
|
|
370
|
-
onError: handleError,
|
|
371
|
-
onQrUri: handleQrUri,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// wagmi connector icon 매핑
|
|
375
|
-
const connectorLogoMap = useMemo(() => {
|
|
376
|
-
const map: Partial<Record<ConnectorId, string>> = {};
|
|
377
|
-
const connectorIds: ConnectorId[] = [
|
|
378
|
-
'CREDIT_CONNECT',
|
|
379
|
-
'CREDIT_WALLET',
|
|
380
|
-
'METAMASK',
|
|
381
|
-
'WALLET_CONNECT',
|
|
382
|
-
];
|
|
383
|
-
for (const connectorId of connectorIds) {
|
|
384
|
-
const wagmiConnector = resolveWagmiConnector(wagmiConfig.connectors, connectorId);
|
|
385
|
-
if (wagmiConnector?.icon) map[connectorId] = wagmiConnector.icon;
|
|
386
|
-
}
|
|
387
|
-
return map;
|
|
388
|
-
}, [wagmiConfig.connectors]);
|
|
389
|
-
|
|
390
|
-
// 최근 연결 기록 로드
|
|
391
|
-
useEffect(() => {
|
|
392
|
-
setRecentConnector(readRecentConnector());
|
|
393
|
-
setHasMetaMaskExtension(detectMetaMaskExtension());
|
|
394
|
-
}, []);
|
|
395
|
-
|
|
396
|
-
// 모달 닫힐 때 상태 초기화
|
|
397
|
-
useEffect(() => {
|
|
398
|
-
if (!open) {
|
|
399
|
-
setSelectedConnector(null);
|
|
400
|
-
setQrUri(null);
|
|
401
|
-
setErrorState(null);
|
|
402
|
-
wc.reset();
|
|
403
|
-
cancelConnect();
|
|
404
|
-
}
|
|
405
|
-
}, [open, cancelConnect, wc.reset]);
|
|
406
|
-
|
|
407
|
-
// 커넥터 선택 — 선택할 때마다 QR을 초기화해 새 세션 URI가 표시되도록 함
|
|
408
|
-
const handleSelectConnector = useCallback(
|
|
409
|
-
(id: ConnectorId): void => {
|
|
410
|
-
if (id === selectedConnector && !errorState) return;
|
|
411
|
-
setSelectedConnector(id);
|
|
412
|
-
setQrUri(null);
|
|
413
|
-
setErrorState(null);
|
|
414
|
-
wc.resetView();
|
|
415
|
-
writeRecentConnector(id);
|
|
416
|
-
setRecentConnector(id);
|
|
417
|
-
|
|
418
|
-
if (id === 'WALLET_CONNECT' && wcProjectId) {
|
|
419
|
-
wc.loadWalletList(wcProjectId);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
cancelConnect();
|
|
423
|
-
triggerConnect(id);
|
|
424
|
-
},
|
|
425
|
-
[
|
|
426
|
-
selectedConnector,
|
|
427
|
-
errorState,
|
|
428
|
-
triggerConnect,
|
|
429
|
-
cancelConnect,
|
|
430
|
-
wcProjectId,
|
|
431
|
-
wc.resetView,
|
|
432
|
-
wc.loadWalletList,
|
|
433
|
-
],
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
// 뒤로가기
|
|
437
|
-
const handleBack = useCallback((): void => {
|
|
438
|
-
if (errorState) {
|
|
439
|
-
setErrorState(null);
|
|
440
|
-
setSelectedConnector(null);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (selectedConnector === 'WALLET_CONNECT' && wc.handleBack()) return;
|
|
444
|
-
setSelectedConnector(null);
|
|
445
|
-
setQrUri(null);
|
|
446
|
-
cancelConnect();
|
|
447
|
-
}, [selectedConnector, errorState, wc.handleBack, cancelConnect]);
|
|
448
|
-
|
|
449
|
-
// chain mismatch 상태에서의 disconnect (Switch chain pane의 cancel)
|
|
450
|
-
const handleSwitchChainDisconnect = useCallback((): void => {
|
|
451
|
-
disconnectAsync().catch((err: unknown) => {
|
|
452
|
-
const normalized = err instanceof Error ? err : new Error(String(err));
|
|
453
|
-
onLog?.(`[connect-kit] disconnect failed: ${normalized.message}`, normalized);
|
|
454
|
-
});
|
|
455
|
-
}, [disconnectAsync, onLog]);
|
|
456
|
-
|
|
457
|
-
// overlay 클릭 — chain mismatch 상태에선 무시 (실수 클릭 방어)
|
|
458
|
-
const handleOverlayClick = useCallback((): void => {
|
|
459
|
-
if (chainMismatch) return;
|
|
460
|
-
onClose();
|
|
461
|
-
}, [chainMismatch, onClose]);
|
|
462
|
-
|
|
463
|
-
// close 버튼 — chain mismatch 상태에선 disconnect
|
|
464
|
-
const handleCloseClick = useCallback((): void => {
|
|
465
|
-
if (chainMismatch) {
|
|
466
|
-
handleSwitchChainDisconnect();
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
onClose();
|
|
470
|
-
}, [chainMismatch, handleSwitchChainDisconnect, onClose]);
|
|
471
|
-
|
|
472
|
-
const renderedError: ReactNode = useMemo(() => {
|
|
473
|
-
if (!errorState || !renderConnectError) return null;
|
|
474
|
-
const ctx: ConnectErrorContext = {
|
|
475
|
-
connectorId: errorState.connectorId,
|
|
476
|
-
reason: classifyError(errorState.error),
|
|
477
|
-
error: errorState.error,
|
|
478
|
-
retry: () => {
|
|
479
|
-
const id = errorState.connectorId;
|
|
480
|
-
setErrorState(null);
|
|
481
|
-
setQrUri(null);
|
|
482
|
-
cancelConnect();
|
|
483
|
-
triggerConnect(id);
|
|
484
|
-
},
|
|
485
|
-
dismiss: () => {
|
|
486
|
-
setErrorState(null);
|
|
487
|
-
setSelectedConnector(null);
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
return renderConnectError(ctx);
|
|
491
|
-
}, [errorState, renderConnectError, triggerConnect, cancelConnect]);
|
|
492
|
-
|
|
493
|
-
// chain mismatch면 단일 pane (selector/detail 숨김)
|
|
494
|
-
// requiredChainId != null 재확인은 TS narrowing용 — chainMismatch true면 이미 보장됨
|
|
495
|
-
if (chainMismatch && requiredChainId != null) {
|
|
496
|
-
return (
|
|
497
|
-
<div className="ck-root">
|
|
498
|
-
<div className="ck-overlay" onClick={handleOverlayClick} />
|
|
499
|
-
<div
|
|
500
|
-
className="ck-modal ck-modal--switch-chain"
|
|
501
|
-
role="dialog"
|
|
502
|
-
aria-modal="true"
|
|
503
|
-
aria-label="Switch network"
|
|
504
|
-
>
|
|
505
|
-
<div className="ck-pane">
|
|
506
|
-
<div className="ck-pane__header">
|
|
507
|
-
<h3 className="ck-pane__title">Switch network</h3>
|
|
508
|
-
<button
|
|
509
|
-
type="button"
|
|
510
|
-
className="ck-btn-close"
|
|
511
|
-
onClick={handleCloseClick}
|
|
512
|
-
aria-label="Cancel and disconnect"
|
|
513
|
-
/>
|
|
514
|
-
</div>
|
|
515
|
-
<div className="ck-pane__body">
|
|
516
|
-
<SwitchChainView
|
|
517
|
-
currentChainId={account.chainId}
|
|
518
|
-
requiredChainId={requiredChainId}
|
|
519
|
-
requiredChainName={requiredChainName}
|
|
520
|
-
onDisconnect={handleSwitchChainDisconnect}
|
|
521
|
-
onLog={onLog}
|
|
522
|
-
/>
|
|
523
|
-
</div>
|
|
524
|
-
</div>
|
|
525
|
-
</div>
|
|
526
|
-
</div>
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return (
|
|
531
|
-
<div className="ck-root">
|
|
532
|
-
<div className="ck-overlay" onClick={handleOverlayClick} />
|
|
533
|
-
<div
|
|
534
|
-
className={`ck-modal${(selectedConnector ?? errorState) ? ' ck-modal--has-detail' : ''}`}
|
|
535
|
-
role="dialog"
|
|
536
|
-
aria-modal="true"
|
|
537
|
-
aria-label="Connect Wallet"
|
|
538
|
-
>
|
|
539
|
-
<SelectorPane
|
|
540
|
-
connectors={connectors}
|
|
541
|
-
selected={selectedConnector}
|
|
542
|
-
recentConnector={recentConnector}
|
|
543
|
-
onSelect={handleSelectConnector}
|
|
544
|
-
onClose={handleCloseClick}
|
|
545
|
-
/>
|
|
546
|
-
<DetailPane
|
|
547
|
-
selectedConnector={selectedConnector}
|
|
548
|
-
qrUri={qrUri}
|
|
549
|
-
hasMetaMaskExtension={hasMetaMaskExtension}
|
|
550
|
-
connectorLogoMap={connectorLogoMap}
|
|
551
|
-
wc={wc}
|
|
552
|
-
errorOverride={renderedError}
|
|
553
|
-
onBack={handleBack}
|
|
554
|
-
onClose={handleCloseClick}
|
|
555
|
-
/>
|
|
556
|
-
</div>
|
|
557
|
-
</div>
|
|
558
|
-
);
|
|
559
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { type FC } from 'react';
|
|
2
|
-
import { QRCodeSVG } from 'qrcode.react';
|
|
3
|
-
|
|
4
|
-
interface QRFrameProps {
|
|
5
|
-
qrUri: string | null;
|
|
6
|
-
logoUrl?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const QRFrame: FC<QRFrameProps> = ({ qrUri, logoUrl }) => (
|
|
10
|
-
<div className={`ck-qr-frame ${!qrUri ? 'ck-qr-frame--loading' : ''}`}>
|
|
11
|
-
{qrUri ? (
|
|
12
|
-
<QRCodeSVG
|
|
13
|
-
value={qrUri}
|
|
14
|
-
size={196}
|
|
15
|
-
level="H"
|
|
16
|
-
imageSettings={
|
|
17
|
-
logoUrl ? { src: logoUrl, width: 40, height: 40, excavate: true } : undefined
|
|
18
|
-
}
|
|
19
|
-
/>
|
|
20
|
-
) : (
|
|
21
|
-
<div className="ck-qr-spinner" aria-label="Loading QR code" />
|
|
22
|
-
)}
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
interface CopyLinkButtonProps {
|
|
27
|
-
qrUri: string | null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const CopyLinkButton: FC<CopyLinkButtonProps> = ({ qrUri }) => {
|
|
31
|
-
if (!qrUri) return null;
|
|
32
|
-
return (
|
|
33
|
-
<button
|
|
34
|
-
type="button"
|
|
35
|
-
className="ck-copy-link"
|
|
36
|
-
onClick={() => {
|
|
37
|
-
navigator.clipboard.writeText(qrUri);
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
Copy link
|
|
41
|
-
</button>
|
|
42
|
-
);
|
|
43
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { type FC, useEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { isMobileDevice, tryOpenDeepLink } from '../utils/platform';
|
|
3
|
-
import { CONNECTOR_META } from '../connector-meta';
|
|
4
|
-
import { type ConnectorId } from '../types';
|
|
5
|
-
import { QRFrame } from '../components/QRFrame';
|
|
6
|
-
|
|
7
|
-
interface CreditWalletViewProps {
|
|
8
|
-
connectorId: Extract<ConnectorId, 'CREDIT_CONNECT' | 'CREDIT_WALLET'>;
|
|
9
|
-
qrUri: string | null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const CreditWalletView: FC<CreditWalletViewProps> = ({ connectorId, qrUri }) => {
|
|
13
|
-
const { downloadUrl, logoUrl, deepLinkBase } = CONNECTOR_META[connectorId];
|
|
14
|
-
const isMobile = useMemo(() => isMobileDevice(), []);
|
|
15
|
-
// WC 세션 만료로 qrUri가 재발급되더라도 딥링크는 최초 1회만 실행
|
|
16
|
-
const hasRedirectedRef = useRef(false);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (isMobile && qrUri && deepLinkBase && !hasRedirectedRef.current) {
|
|
20
|
-
hasRedirectedRef.current = true;
|
|
21
|
-
tryOpenDeepLink(qrUri, deepLinkBase);
|
|
22
|
-
}
|
|
23
|
-
}, [isMobile, qrUri, deepLinkBase]);
|
|
24
|
-
|
|
25
|
-
if (isMobile && deepLinkBase) {
|
|
26
|
-
return (
|
|
27
|
-
<div className="ck-view ck-view--mobile-redirect">
|
|
28
|
-
<p>Opening Credit Wallet...</p>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div className="ck-view ck-view--qr">
|
|
35
|
-
<QRFrame qrUri={qrUri} logoUrl={logoUrl} />
|
|
36
|
-
|
|
37
|
-
<p className="ck-view__caption">Open the Credit Wallet app and scan the QR</p>
|
|
38
|
-
|
|
39
|
-
{downloadUrl && (
|
|
40
|
-
<div className="ck-download-card">
|
|
41
|
-
<div className="ck-download-card__text">
|
|
42
|
-
<p className="ck-download-card__title">Get Credit Wallet app</p>
|
|
43
|
-
<p className="ck-download-card__sub">
|
|
44
|
-
Once connected, you can log in with it next time
|
|
45
|
-
</p>
|
|
46
|
-
</div>
|
|
47
|
-
<a
|
|
48
|
-
href={downloadUrl}
|
|
49
|
-
target="_blank"
|
|
50
|
-
rel="nofollow noopener noreferrer"
|
|
51
|
-
className="ck-btn-primary"
|
|
52
|
-
>
|
|
53
|
-
Download
|
|
54
|
-
</a>
|
|
55
|
-
</div>
|
|
56
|
-
)}
|
|
57
|
-
</div>
|
|
58
|
-
);
|
|
59
|
-
};
|
package/src/views/IdleView.tsx
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type FC } from 'react';
|
|
2
|
-
import graphic from '../../assets/graphic.png';
|
|
3
|
-
|
|
4
|
-
export const IdleView: FC = () => (
|
|
5
|
-
<div className="ck-view ck-view--idle">
|
|
6
|
-
<img className="ck-view__illustration" src={graphic} alt="" aria-hidden="true" />
|
|
7
|
-
<h4 className="ck-view__title">Connect anytime, anywhere</h4>
|
|
8
|
-
<p className="ck-view__description">Choose a wallet on the left to start connecting.</p>
|
|
9
|
-
</div>
|
|
10
|
-
);
|