@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/assets/fonts/Inter-Medium.ttf +0 -0
  3. package/assets/fonts/Inter-Medium.woff2 +0 -0
  4. package/assets/fonts/Inter-Regular.ttf +0 -0
  5. package/assets/fonts/Inter-Regular.woff2 +0 -0
  6. package/assets/fonts/Inter-SemiBold.ttf +0 -0
  7. package/assets/fonts/Inter-SemiBold.woff2 +0 -0
  8. package/assets/graphic.png +0 -0
  9. package/assets/metamask.png +0 -0
  10. package/assets/penguinwallet.png +0 -0
  11. package/assets/wc-logo.svg +3 -0
  12. package/assets/wc-search.png +0 -0
  13. package/dist/chunk-VE5LB7BN.js +405 -0
  14. package/dist/credit-connect.js +2 -394
  15. package/dist/index.css +2075 -0
  16. package/dist/index.d.ts +58 -33
  17. package/dist/index.js +1748 -1503
  18. package/dist/package.json +16 -10
  19. package/package.json +14 -11
  20. package/src/ConnectKitProvider.tsx +133 -75
  21. package/src/api/account.ts +21 -0
  22. package/src/api/asset.ts +10 -0
  23. package/src/api/balance.ts +10 -0
  24. package/src/api/chain.ts +17 -0
  25. package/src/api/contract.ts +33 -0
  26. package/src/api/transaction.ts +12 -0
  27. package/src/assets.d.ts +5 -0
  28. package/src/components/actionItem/index.tsx +50 -0
  29. package/src/components/actionItem/style.scss +47 -0
  30. package/src/components/copyLink/index.tsx +16 -0
  31. package/src/components/copyLink/style.scss +16 -0
  32. package/src/components/qrArea/index.tsx +72 -0
  33. package/src/components/qrArea/style.scss +122 -0
  34. package/src/components/searchBar/asset/Search.tsx +20 -0
  35. package/src/components/searchBar/asset/XMarkCircle.tsx +24 -0
  36. package/src/components/searchBar/index.tsx +44 -0
  37. package/src/components/searchBar/style.scss +58 -0
  38. package/src/components/toast/index.tsx +41 -0
  39. package/src/components/toast/style.scss +108 -0
  40. package/src/components/toggle/index.tsx +27 -0
  41. package/src/components/toggle/style.scss +53 -0
  42. package/src/components/walletGrid/index.tsx +64 -0
  43. package/src/components/walletGrid/style.scss +108 -0
  44. package/src/components/walletItem/index.tsx +49 -0
  45. package/src/components/walletItem/style.scss +70 -0
  46. package/src/connector-meta.ts +16 -11
  47. package/src/core/config.ts +102 -0
  48. package/src/creditConnectConnector.ts +198 -184
  49. package/src/events/account.ts +76 -0
  50. package/src/hooks/useConnectBanner.ts +79 -0
  51. package/src/hooks/useConnectTimeout.ts +92 -0
  52. package/src/hooks/useMetaMaskExtension.ts +29 -0
  53. package/src/hooks/useWCState.ts +45 -14
  54. package/src/hooks/useWagmiConnect.ts +42 -11
  55. package/src/index.ts +38 -2
  56. package/src/layout/allWallets/index.tsx +52 -0
  57. package/src/layout/allWallets/style.scss +57 -0
  58. package/src/layout/connectDialog/asset/backIcon.tsx +24 -0
  59. package/src/layout/connectDialog/asset/checkIcon.tsx +28 -0
  60. package/src/layout/connectDialog/asset/closeIcon.tsx +21 -0
  61. package/src/layout/connectDialog/asset/copyIcon.tsx +28 -0
  62. package/src/layout/connectDialog/asset/linkIcon.tsx +24 -0
  63. package/src/layout/connectDialog/asset/spinner.tsx +21 -0
  64. package/src/layout/connectDialog/asset/warningIcon.tsx +21 -0
  65. package/src/layout/connectDialog/idle/assets/banner-placeholder.png +0 -0
  66. package/src/layout/connectDialog/idle/index.tsx +55 -0
  67. package/src/layout/connectDialog/idle/style.scss +52 -0
  68. package/src/layout/connectDialog/index.tsx +432 -0
  69. package/src/layout/connectDialog/loading/index.tsx +13 -0
  70. package/src/layout/connectDialog/loading/style.scss +19 -0
  71. package/src/layout/connectDialog/metaMask/index.tsx +61 -0
  72. package/src/layout/connectDialog/metaMask/style.scss +65 -0
  73. package/src/layout/connectDialog/qr/index.tsx +33 -0
  74. package/src/layout/connectDialog/style.scss +48 -0
  75. package/src/layout/connectDialog/timeout/index.tsx +31 -0
  76. package/src/layout/connectDialog/timeout/style.scss +64 -0
  77. package/src/layout/connectDialog/walletConnect/index.tsx +45 -0
  78. package/src/layout/connectDialog/walletConnect/style.scss +48 -0
  79. package/src/layout/connectDialog/walletQR/index.tsx +37 -0
  80. package/src/layout/detailPanel/index.tsx +175 -0
  81. package/src/layout/detailPanel/style.scss +70 -0
  82. package/src/layout/selectorPanel/index.tsx +100 -0
  83. package/src/layout/selectorPanel/style.scss +113 -0
  84. package/src/style/color.scss +417 -0
  85. package/src/style/font.scss +28 -0
  86. package/src/style/index.scss +5 -0
  87. package/src/style/mixin.scss +217 -0
  88. package/src/style/reset.scss +110 -0
  89. package/src/style/variables.scss +13 -0
  90. package/src/types.ts +40 -7
  91. package/src/utils/platform.ts +6 -7
  92. package/src/wallet-display-override.ts +15 -0
  93. package/tsconfig.json +5 -1
  94. package/tsup.config.ts +32 -2
  95. package/assets/creditwallet.png +0 -0
  96. package/dist/chunk-DDA6FP6U.js +0 -7
  97. package/src/ConnectModal.scss +0 -665
  98. package/src/ConnectModal.tsx +0 -559
  99. package/src/components/QRFrame.tsx +0 -43
  100. package/src/views/CreditWalletView.tsx +0 -59
  101. package/src/views/IdleView.tsx +0 -10
  102. package/src/views/MetaMaskView.tsx +0 -64
  103. package/src/views/SwitchChainView.tsx +0 -67
  104. package/src/views/WalletConnectView.tsx +0 -206
@@ -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&apos;t have a wallet &gt;
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
- };
@@ -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
- );