@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.
Files changed (89) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/assets/creditwallet.png +0 -0
  3. package/assets/fonts/Inter-Medium.ttf +0 -0
  4. package/assets/fonts/Inter-Medium.woff2 +0 -0
  5. package/assets/fonts/Inter-Regular.ttf +0 -0
  6. package/assets/fonts/Inter-Regular.woff2 +0 -0
  7. package/assets/fonts/Inter-SemiBold.ttf +0 -0
  8. package/assets/fonts/Inter-SemiBold.woff2 +0 -0
  9. package/assets/graphic.png +0 -0
  10. package/assets/metamask.png +0 -0
  11. package/assets/wc-logo.svg +3 -0
  12. package/assets/wc-search.png +0 -0
  13. package/dist/chunk-EDNRW47A.js +401 -0
  14. package/dist/credit-connect.js +2 -409
  15. package/dist/index.css +1903 -390
  16. package/dist/index.d.ts +16 -10
  17. package/dist/index.js +1110 -870
  18. package/dist/package.json +7 -6
  19. package/package.json +8 -8
  20. package/src/ConnectKitProvider.tsx +90 -65
  21. package/src/assets.d.ts +5 -0
  22. package/src/components/actionItem/index.tsx +50 -0
  23. package/src/components/actionItem/style.scss +47 -0
  24. package/src/components/copyLink/index.tsx +16 -0
  25. package/src/components/copyLink/style.scss +16 -0
  26. package/src/components/qrArea/index.tsx +70 -0
  27. package/src/components/qrArea/style.scss +130 -0
  28. package/src/components/searchBar/asset/Search.tsx +20 -0
  29. package/src/components/searchBar/asset/XMarkCircle.tsx +24 -0
  30. package/src/components/searchBar/index.tsx +44 -0
  31. package/src/components/searchBar/style.scss +58 -0
  32. package/src/components/snackbar/index.tsx +25 -0
  33. package/src/components/snackbar/style.scss +51 -0
  34. package/src/components/toggle/index.tsx +27 -0
  35. package/src/components/toggle/style.scss +53 -0
  36. package/src/components/walletGrid/index.tsx +64 -0
  37. package/src/components/walletGrid/style.scss +108 -0
  38. package/src/components/walletItem/index.tsx +49 -0
  39. package/src/components/walletItem/style.scss +70 -0
  40. package/src/connector-meta.ts +12 -7
  41. package/src/core/config.ts +20 -1
  42. package/src/creditConnectConnector.ts +17 -25
  43. package/src/hooks/useWCState.ts +22 -5
  44. package/src/hooks/useWagmiConnect.ts +0 -1
  45. package/src/index.ts +2 -2
  46. package/src/layout/allWallets/index.tsx +52 -0
  47. package/src/layout/allWallets/style.scss +62 -0
  48. package/src/layout/connectDialog/asset/backIcon.tsx +24 -0
  49. package/src/layout/connectDialog/asset/checkIcon.tsx +28 -0
  50. package/src/layout/connectDialog/asset/closeIcon.tsx +21 -0
  51. package/src/layout/connectDialog/asset/copyIcon.tsx +28 -0
  52. package/src/layout/connectDialog/asset/linkIcon.tsx +24 -0
  53. package/src/layout/connectDialog/asset/spinner.tsx +21 -0
  54. package/src/layout/connectDialog/idle/index.tsx +32 -0
  55. package/src/layout/connectDialog/idle/style.scss +61 -0
  56. package/src/layout/connectDialog/index.tsx +286 -0
  57. package/src/layout/connectDialog/loading/index.tsx +13 -0
  58. package/src/layout/connectDialog/loading/style.scss +19 -0
  59. package/src/layout/connectDialog/metaMask/index.tsx +61 -0
  60. package/src/layout/connectDialog/metaMask/style.scss +65 -0
  61. package/src/layout/connectDialog/qr/index.tsx +33 -0
  62. package/src/layout/connectDialog/style.scss +95 -0
  63. package/src/layout/connectDialog/timeout/index.tsx +31 -0
  64. package/src/layout/connectDialog/timeout/style.scss +64 -0
  65. package/src/layout/connectDialog/walletConnect/index.tsx +45 -0
  66. package/src/layout/connectDialog/walletConnect/style.scss +48 -0
  67. package/src/layout/connectDialog/walletQR/index.tsx +37 -0
  68. package/src/layout/detailPanel/index.tsx +147 -0
  69. package/src/layout/detailPanel/style.scss +64 -0
  70. package/src/layout/selectorPanel/index.tsx +100 -0
  71. package/src/layout/selectorPanel/style.scss +113 -0
  72. package/src/style/color.scss +417 -0
  73. package/src/style/font.scss +28 -0
  74. package/src/style/index.scss +5 -0
  75. package/src/style/mixin.scss +217 -0
  76. package/src/style/reset.scss +110 -0
  77. package/src/style/variables.scss +13 -0
  78. package/src/types.ts +11 -8
  79. package/src/utils/platform.ts +6 -0
  80. package/tsup.config.ts +32 -2
  81. package/dist/chunk-DDA6FP6U.js +0 -7
  82. package/src/ConnectModal.scss +0 -665
  83. package/src/ConnectModal.tsx +0 -573
  84. package/src/components/QRFrame.tsx +0 -43
  85. package/src/views/CreditWalletView.tsx +0 -58
  86. package/src/views/IdleView.tsx +0 -10
  87. package/src/views/MetaMaskView.tsx +0 -66
  88. package/src/views/SwitchChainView.tsx +0 -72
  89. package/src/views/WalletConnectView.tsx +0 -206
@@ -0,0 +1,70 @@
1
+ @use '../../style/mixin.scss' as *;
2
+
3
+ .ck-root .ck-wallet-item {
4
+ display: flex;
5
+ align-items: center;
6
+ gap: 12px;
7
+ width: 100%;
8
+ padding: 14px 20px;
9
+ border-radius: 12px;
10
+ background: var(--scale-gray-100);
11
+ cursor: pointer;
12
+ transition: background 0.15s ease;
13
+
14
+ &:hover {
15
+ background: var(--scale-gray-200);
16
+ }
17
+
18
+ &.is-selected {
19
+ background: var(--semantic-brand-trugi-primary);
20
+
21
+ .ck-wallet-item-label {
22
+ color: var(--static-white-main);
23
+ }
24
+
25
+ .ck-wallet-item-badge {
26
+ background: var(--static-white-alpha-200);
27
+ color: var(--static-white-main);
28
+ }
29
+ }
30
+ }
31
+
32
+ .ck-wallet-item-icon {
33
+ width: 36px;
34
+ height: 36px;
35
+ border-radius: 8px;
36
+ flex-shrink: 0;
37
+ overflow: hidden;
38
+ object-fit: cover;
39
+
40
+ &.has-bg {
41
+ background: var(--static-white-main);
42
+ padding: 2px;
43
+ }
44
+ }
45
+
46
+ .ck-wallet-item-txt {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 6px;
50
+ flex: 1;
51
+ min-width: 0;
52
+ }
53
+
54
+ .ck-wallet-item-label {
55
+ @include textToken('label3-bold');
56
+ color: var(--scale-gray-900);
57
+ overflow: hidden;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ flex-shrink: 1;
61
+ }
62
+
63
+ .ck-wallet-item-badge {
64
+ @include textToken('caption3-bold');
65
+ color: var(--semantic-brand-trugi-primary);
66
+ background: var(--scale-blue2-alpha-100);
67
+ padding: 4px 8px;
68
+ border-radius: 500px;
69
+ flex-shrink: 0;
70
+ }
@@ -17,13 +17,6 @@ export const CONNECTOR_META: Record<ConnectorId, ConnectorMeta> = {
17
17
  detailTitle: 'Scan with Credit Wallet',
18
18
  downloadUrl: 'https://creditwallet.onelink.me/Jv54',
19
19
  logoUrl: creditWalletIcon,
20
- // deepLinkBase: TODO — 앱 측 CC 딥링크 스킴 확정 후 추가
21
- },
22
- CREDIT_WALLET: {
23
- label: 'Credit Wallet',
24
- detailTitle: 'Scan with Credit Wallet',
25
- downloadUrl: 'https://creditwallet.onelink.me/Jv54',
26
- logoUrl: creditWalletIcon,
27
20
  deepLinkBase: 'creditwallet://',
28
21
  },
29
22
  METAMASK: {
@@ -39,3 +32,15 @@ export const CONNECTOR_META: Record<ConnectorId, ConnectorMeta> = {
39
32
  logoUrl: wcIcon,
40
33
  },
41
34
  };
35
+
36
+ // wagmi connector id (예: 'credit-connect' · 'metaMaskSDK' · 'walletConnect') → kit ConnectorId.
37
+ // wagmi storage 의 recentConnectorId 복원 · account.connector 식별에 공용 사용.
38
+ export const resolveConnectorId = (wagmiConnectorId: string | undefined): ConnectorId | null => {
39
+ if (!wagmiConnectorId) return null;
40
+ if (wagmiConnectorId === 'credit-connect') return 'CREDIT_CONNECT';
41
+ if (wagmiConnectorId === 'walletConnect') return 'WALLET_CONNECT';
42
+ const lower = wagmiConnectorId.toLowerCase();
43
+ if (lower.includes('metamask') || wagmiConnectorId === 'injected') return 'METAMASK';
44
+ if (lower.includes('walletconnect')) return 'WALLET_CONNECT';
45
+ return null;
46
+ };
@@ -2,6 +2,10 @@ import { createConfig, createStorage } from 'wagmi';
2
2
  import { metaMask, walletConnect, injected } from 'wagmi/connectors';
3
3
  import type { Config } from 'wagmi';
4
4
  import type { ConnectKitConfig } from '../types';
5
+ import { creditConnectConnector } from '../creditConnectConnector';
6
+ import { LocalStorageProvider } from '@gluwa/credit-connect-storage-local';
7
+ import { tweetNaclE2EE } from '@gluwa/credit-connect-e2ee-tweetnacl';
8
+ import { wsHubFactory } from '@gluwa/credit-connect-hub-node-ws';
5
9
 
6
10
  type KitSlot = {
7
11
  config: Config | null;
@@ -40,7 +44,22 @@ export const initConfig = (kitConfig: ConnectKitConfig): Config => {
40
44
  connectors.push(walletConnect({ projectId: kitConfig.wcProjectId, showQrModal: false }));
41
45
  }
42
46
 
43
- connectors.push(metaMask({ storage: { enabled: true } }), injected({ shimDisconnect: false }));
47
+ connectors.push(
48
+ metaMask({ headless: true, storage: { enabled: true } }),
49
+ injected({ shimDisconnect: false }),
50
+ );
51
+
52
+ if (kitConfig.creditConnect) {
53
+ connectors.push(
54
+ creditConnectConnector({
55
+ projectKey: kitConfig.creditConnect.projectId,
56
+ serverUrl: kitConfig.creditConnect.serverUrl,
57
+ storage: new LocalStorageProvider(),
58
+ e2ee: tweetNaclE2EE,
59
+ createHub: wsHubFactory.createHubFromDapp,
60
+ }),
61
+ );
62
+ }
44
63
 
45
64
  if (kitConfig.extraConnectors?.length) {
46
65
  connectors.push(...kitConfig.extraConnectors);
@@ -84,12 +84,23 @@ export const creditConnectConnector = (
84
84
  });
85
85
 
86
86
  manager.on('changeState', (state) => {
87
+ // eslint-disable-next-line no-console
88
+ console.log('[CC connector] manager state →', state.status, {
89
+ hasConnectingPayload: Boolean(state.connectingPayload),
90
+ hasSession: 'session' in state && Boolean((state as { session?: unknown }).session),
91
+ });
87
92
  if (state.status === 'CONNECTING' && state.connectingPayload) {
93
+ // eslint-disable-next-line no-console
94
+ console.log('[CC connector] emit display_uri', {
95
+ payloadPrefix: state.connectingPayload.slice(0, 80),
96
+ });
88
97
  options.onDisplayUri?.(state.connectingPayload);
89
98
  emitWagmiMessage?.('display_uri', state.connectingPayload);
90
99
  }
91
100
 
92
101
  if (state.status === 'DISCONNECTED' && !isLocalDisconnect) {
102
+ // eslint-disable-next-line no-console
103
+ console.log('[CC connector] DISCONNECTED (non-local) → resetState + emit');
93
104
  resetState();
94
105
  emitProviderEvent('disconnect');
95
106
  emitWagmiDisconnect?.();
@@ -343,32 +354,13 @@ export const creditConnectConnector = (
343
354
  });
344
355
  }
345
356
 
346
- if (m.state.status === 'CONNECTING' && m.state.connectingPayload) {
347
- options.onDisplayUri?.(m.state.connectingPayload);
348
-
349
- if (!connectPromise) {
350
- connectPromise = new Promise<boolean>((resolve) => {
351
- const unsub = m.on('changeState', (state) => {
352
- if (state.status === 'CONNECTED') {
353
- unsub();
354
- resolve(true);
355
- } else if (state.status === 'DISCONNECTED') {
356
- unsub();
357
- resolve(false);
358
- }
359
- });
360
- });
361
- }
362
- }
363
-
357
+ // connect 시도 stale state (CONNECTING/CONNECTING_WAITING_PAYLOAD)
358
+ // stale connectPromise 는 SDK 의 self-heal (cleanupHub + attempt-id) 에 위임.
359
+ // connector 는 단순히 새 connectNewSession() 을 호출 — 이전 attempt 는 자연 정리됨.
364
360
  if (m.state.status !== 'CONNECTED') {
365
- if (!connectPromise) {
366
- if (m.state.status !== 'DISCONNECTED') {
367
- throw new Error(`Cannot connect from ${m.state.status} state`);
368
- }
369
- connectPromise = m.connectNewSession();
370
- }
371
-
361
+ // 이전 attempt 의 await 가 살아있어도, SDK 의 attempt-id 패턴이
362
+ // 결과를 무시하도록 보장한다. connectPromise 는 무조건 새 promise 로.
363
+ connectPromise = m.connectNewSession();
372
364
  const connected = await connectPromise;
373
365
  connectPromise = null;
374
366
 
@@ -1,8 +1,22 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
2
  import type { WCWallet, WCSubView } from '../types';
3
3
 
4
+ // WalletConnect Explorer 에 등록된 Credit Wallet 의 정식 wallet name (API 응답 그대로).
5
+ // CONNECTOR_META.CREDIT_CONNECT.label 은 selector button 표시용 ('Continue with Credit Wallet')
6
+ // 이라 다름 — 별도 상수로 분리.
7
+ const WC_EXPLORER_CREDIT_WALLET_NAME = 'Credit Wallet';
8
+
9
+ const sortCreditWalletFirst = (list: WCWallet[]): WCWallet[] =>
10
+ [...list].sort((a, b) => {
11
+ if (a.name === WC_EXPLORER_CREDIT_WALLET_NAME) return -1;
12
+ if (b.name === WC_EXPLORER_CREDIT_WALLET_NAME) return 1;
13
+ return 0;
14
+ });
15
+
4
16
  const fetchWCWalletList = async (projectId: string, signal?: AbortSignal): Promise<WCWallet[]> => {
5
- const all: WCWallet[] = [];
17
+ // WC Explorer API 가 page 경계에서 같은 wallet 을 중복 emit 하는 케이스 관찰됨
18
+ // (paginate 사이 sort/dedup 보장 X). id 기준 first-write-wins dedup.
19
+ const byId = new Map<string, WCWallet>();
6
20
  let page = 1;
7
21
  const entries = 100;
8
22
 
@@ -34,7 +48,8 @@ const fetchWCWalletList = async (projectId: string, signal?: AbortSignal): Promi
34
48
 
35
49
  const listings = Object.values(json.listings ?? {});
36
50
  for (const w of listings) {
37
- all.push({
51
+ if (byId.has(w.id)) continue;
52
+ byId.set(w.id, {
38
53
  id: w.id,
39
54
  name: w.name,
40
55
  imageUrl: w.image_url?.md ?? w.image_url?.sm ?? '',
@@ -48,11 +63,11 @@ const fetchWCWalletList = async (projectId: string, signal?: AbortSignal): Promi
48
63
  });
49
64
  }
50
65
 
51
- if (listings.length < entries || all.length >= json.total) break;
66
+ if (listings.length < entries || byId.size >= json.total) break;
52
67
  page += 1;
53
68
  }
54
69
 
55
- return all;
70
+ return Array.from(byId.values());
56
71
  };
57
72
 
58
73
  export interface WCState {
@@ -100,7 +115,9 @@ export const useWCState = (): WCState => {
100
115
  setWalletListLoading(true);
101
116
  try {
102
117
  const list = await fetchWCWalletList(projectId, controller.signal);
103
- if (isMountedRef.current) setWalletList(list);
118
+ const sorted = sortCreditWalletFirst(list);
119
+ // Credit Wallet 을 최상단으로 정렬한 상태로 state 저장 — view 측은 정렬 신경 X.
120
+ if (isMountedRef.current) setWalletList(sorted);
104
121
  } catch (err) {
105
122
  const isAbort = err instanceof DOMException && err.name === 'AbortError';
106
123
  if (!isAbort) loadedRef.current = false;
@@ -49,7 +49,6 @@ export const resolveWagmiConnector = <T extends WagmiConnectorLike>(
49
49
  case 'METAMASK':
50
50
  return connectors.find(isMetaMaskConnector);
51
51
 
52
- case 'CREDIT_WALLET':
53
52
  case 'WALLET_CONNECT':
54
53
  return connectors.find(isWalletConnectConnector);
55
54
 
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ import { waitForTransactionReceipt } from './api/transaction';
5
5
  import { getAccount, signMessage, signTypedData } from './api/account';
6
6
  import { watchAccount, watchConnections, reconnect, disconnectAll } from './events/account';
7
7
 
8
- export { ConnectModal } from './ConnectModal';
9
8
  export { ConnectKitProvider, useConnectKit } from './ConnectKitProvider';
10
9
  export type { ConnectKitContextValue, ConnectKitProviderProps } from './ConnectKitProvider';
11
10
 
@@ -16,10 +15,11 @@ export type {
16
15
  ConnectErrorReason,
17
16
  ConnectModalProps,
18
17
  ConnectKitConfig,
18
+ ConnectKitTheme,
19
19
  Connectors,
20
+ ConnectorKey,
20
21
  ConnectResult,
21
22
  ConnectorId,
22
- CreditWalletStrategy,
23
23
  WCSubView,
24
24
  WCWallet,
25
25
  } from './types';
@@ -0,0 +1,52 @@
1
+ import { type FC, useState, useMemo } from 'react';
2
+ import './style.scss';
3
+ import { type WCWallet } from '../../types';
4
+ import { SearchBar } from '../../components/searchBar';
5
+ import { Toggle } from '../../components/toggle';
6
+ import { WalletGrid } from '../../components/walletGrid';
7
+ import wcLogo from '../../../assets/wc-search.png';
8
+
9
+ interface AllWalletsProps {
10
+ /** Credit Wallet 이 최상단에 오도록 정렬된 상태로 전달됨 — useWCState 의 loadWalletList 에서 처리. */
11
+ wallets: WCWallet[];
12
+ isLoading: boolean;
13
+ onSelectWallet: (wallet: WCWallet) => void;
14
+ }
15
+
16
+ export const AllWallets: FC<AllWalletsProps> = ({ wallets, isLoading, onSelectWallet }) => {
17
+ const [search, setSearch] = useState('');
18
+ const [filterInstalled, setFilterInstalled] = useState(false);
19
+
20
+ const filtered = useMemo(() => {
21
+ const q = search.trim().toLowerCase();
22
+ let list = wallets;
23
+ if (filterInstalled) {
24
+ list = list.filter(
25
+ (w) => w.mobileNative || w.mobileUniversal || w.desktopNative || w.desktopUniversal,
26
+ );
27
+ }
28
+ if (q) {
29
+ list = list.filter((w) => w.name.toLowerCase().includes(q));
30
+ }
31
+ return list;
32
+ }, [wallets, search, filterInstalled]);
33
+
34
+ return (
35
+ <div className="ck-all-wallets">
36
+ <div className="ck-all-wallets-controls">
37
+ <SearchBar value={search} onChange={setSearch} onClear={() => setSearch('')} />
38
+ <div className="ck-wc-filter-btn">
39
+ <img className="ck-wc-filter-logo" src={wcLogo} alt="" aria-hidden="true" />
40
+ <Toggle checked={filterInstalled} label="" onChange={setFilterInstalled} />
41
+ </div>
42
+ </div>
43
+ {!isLoading && filtered.length === 0 ? (
44
+ <div className="ck-all-wallets-empty" role="status">
45
+ <p>No wallets found</p>
46
+ </div>
47
+ ) : (
48
+ <WalletGrid wallets={filtered} isLoading={isLoading} onSelect={onSelectWallet} />
49
+ )}
50
+ </div>
51
+ );
52
+ };
@@ -0,0 +1,62 @@
1
+ @use '../../style/mixin.scss' as *;
2
+
3
+ .ck-all-wallets {
4
+ display: flex;
5
+ flex-direction: column;
6
+ flex: 1;
7
+ min-height: 0;
8
+ gap: 12px;
9
+
10
+ @include tablet {
11
+ min-height: min(420px, 55dvh);
12
+ max-height: calc(100dvh - 60px);
13
+ }
14
+ }
15
+
16
+ .ck-all-wallets-controls {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ flex-shrink: 0;
21
+
22
+ @include max-width(392px) {
23
+ flex-direction: column;
24
+ align-items: flex-end;
25
+ gap: 12px;
26
+ .ck-search {
27
+ width: 100%;
28
+ }
29
+ }
30
+ }
31
+
32
+ .ck-wc-filter-btn {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: 8px;
36
+ padding: 12px 16px;
37
+ border-radius: 100px;
38
+ background: var(--scale-gray-100);
39
+ flex-shrink: 0;
40
+ }
41
+
42
+ .ck-wc-filter-logo {
43
+ width: 24px;
44
+ height: 24px;
45
+ border-radius: 50%;
46
+ flex-shrink: 0;
47
+ object-fit: cover;
48
+ background: var(--static-white-main);
49
+ }
50
+
51
+ .ck-all-wallets-empty {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ flex: 1;
56
+
57
+ p {
58
+ @include textToken('body4-l2-regular');
59
+ color: var(--scale-gray-600);
60
+ text-align: center;
61
+ }
62
+ }
@@ -0,0 +1,24 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const BackIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={14}
7
+ height={20}
8
+ viewBox="0 0 14 20"
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="M10 4L4 10L10 16"
17
+ stroke="currentColor"
18
+ strokeWidth="1.5"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ </svg>
23
+ );
24
+ };
@@ -0,0 +1,28 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const CheckIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={24}
7
+ height={24}
8
+ viewBox="0 0 24 24"
9
+ fill="none"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ aria-hidden="true"
12
+ focusable="false"
13
+ {...props}
14
+ >
15
+ <g clipPath="url(#clip0_check)">
16
+ <path
17
+ d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM10.4512 15.6953C10.7879 15.973 11.2866 15.9546 11.6016 15.6396L16.7529 10.4883C17.0877 10.1536 17.0877 9.61013 16.7529 9.27539C16.4182 8.94114 15.8756 8.94091 15.541 9.27539L10.9961 13.8223L8.56836 11.3936C8.23374 11.0591 7.69116 11.0593 7.35645 11.3936C7.02171 11.7283 7.02171 12.2717 7.35645 12.6064L10.3867 15.6367C10.4075 15.6575 10.4289 15.6771 10.4512 15.6953Z"
18
+ fill="currentColor"
19
+ />
20
+ </g>
21
+ <defs>
22
+ <clipPath id="clip0_check">
23
+ <rect width="20" height="20" fill="white" transform="translate(2 2)" />
24
+ </clipPath>
25
+ </defs>
26
+ </svg>
27
+ );
28
+ };
@@ -0,0 +1,21 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const CloseIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={20}
7
+ height={20}
8
+ viewBox="0 0 20 20"
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="M15.7785 3.2787C16.0389 3.01835 16.4614 3.01835 16.7217 3.2787C16.9819 3.53899 16.9819 3.96077 16.7217 4.22108L10.9421 9.99989L16.7209 15.7787L16.7673 15.8292C16.9809 16.091 16.9658 16.4778 16.7217 16.7219C16.4776 16.9658 16.0908 16.981 15.829 16.7675L15.7785 16.7211L9.99971 10.9422L4.22172 16.7211L4.17126 16.7675C3.90947 16.981 3.52261 16.9658 3.27852 16.7219C3.03445 16.4778 3.0194 16.091 3.23295 15.8292L3.27934 15.7787L9.05733 9.99989L3.27852 4.22108C3.01842 3.96077 3.01839 3.53899 3.27852 3.2787C3.53887 3.01835 3.96137 3.01835 4.22172 3.2787L9.99971 9.05669L15.7785 3.2787Z"
17
+ fill="currentColor"
18
+ />
19
+ </svg>
20
+ );
21
+ };
@@ -0,0 +1,28 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const CopyIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={20}
7
+ height={20}
8
+ viewBox="0 0 20 20"
9
+ fill="none"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ aria-hidden="true"
12
+ focusable="false"
13
+ {...props}
14
+ >
15
+ <g clipPath="url(#clip0_copy)">
16
+ <path
17
+ d="M16.2247 6.89437C17.1245 6.89437 17.8539 7.6238 17.854 8.5236V16.2246C17.854 17.1244 17.1245 17.8538 16.2247 17.8538H8.52373C7.62388 17.8538 6.89449 17.1244 6.89449 16.2246V8.5236C6.89454 7.6238 7.62391 6.89437 8.52373 6.89437H16.2247ZM8.52373 8.22738C8.36029 8.22738 8.22755 8.36019 8.2275 8.5236V16.2246C8.2275 16.3881 8.36026 16.5208 8.52373 16.5208H16.2247C16.3882 16.5208 16.521 16.3881 16.521 16.2246V8.5236C16.5209 8.36019 16.3881 8.22738 16.2247 8.22738H8.52373ZM13.0225 2.57064C13.8439 2.65419 14.4848 3.34822 14.4848 4.19173V4.91357C14.4848 5.28173 14.1865 5.58002 13.8183 5.58008C13.4501 5.58008 13.1518 5.28176 13.1518 4.91357V4.19173C13.1518 4.04865 13.05 3.92956 12.915 3.90202L12.8556 3.8955H4.19185C4.02839 3.8955 3.89563 4.02827 3.89563 4.19173V12.8555L3.90214 12.9148C3.92968 13.0498 4.04878 13.1517 4.19185 13.1517H4.91369L4.98206 13.1549C5.31816 13.1892 5.5802 13.4731 5.5802 13.8182C5.58014 14.1633 5.31813 14.4473 4.98206 14.4814L4.91369 14.4847H4.19185C3.34833 14.4847 2.65432 13.8438 2.57076 13.0223L2.56262 12.8555V4.19173C2.56262 3.29189 3.29201 2.5625 4.19185 2.5625H12.8556L13.0225 2.57064Z"
18
+ fill="currentColor"
19
+ />
20
+ </g>
21
+ <defs>
22
+ <clipPath id="clip0_copy">
23
+ <rect width="15.8333" height="15.8333" fill="white" transform="translate(2.5 2.5)" />
24
+ </clipPath>
25
+ </defs>
26
+ </svg>
27
+ );
28
+ };
@@ -0,0 +1,24 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const LinkIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={16}
7
+ height={16}
8
+ viewBox="0 0 16 16"
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="M6 3L11 8L6 13"
17
+ stroke="currentColor"
18
+ strokeWidth="1.5"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ </svg>
23
+ );
24
+ };
@@ -0,0 +1,21 @@
1
+ import { type FC, type SVGProps } from 'react';
2
+
3
+ export const Spinner: FC<SVGProps<SVGSVGElement>> = (props) => {
4
+ return (
5
+ <svg
6
+ width={40}
7
+ height={40}
8
+ viewBox="0 0 40 40"
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="M19.995 3.34375C20.9155 3.34375 21.6617 4.08995 21.6617 5.01042V10.0104C21.6617 10.9309 20.9155 11.6771 19.995 11.6771C19.0745 11.6771 18.3284 10.9309 18.3284 10.0104V5.01042C18.3284 4.08995 19.0745 3.34375 19.995 3.34375ZM19.995 28.3438C20.9155 28.3438 21.6617 29.0899 21.6617 30.0104V35.0104C21.6617 35.9309 20.9155 36.6771 19.995 36.6771C19.0745 36.6771 18.3284 35.9309 18.3284 35.0104V30.0104C18.3284 29.0899 19.0745 28.3438 19.995 28.3438ZM36.6617 20.0104C36.6617 20.9309 35.9155 21.6771 34.995 21.6771H29.995C29.0745 21.6771 28.3284 20.9309 28.3284 20.0104C28.3284 19.0899 29.0745 18.3438 29.995 18.3438H34.995C35.9155 18.3438 36.6617 19.0899 36.6617 20.0104ZM11.6617 20.0104C11.6617 20.9309 10.9155 21.6771 9.99504 21.6771H4.99504C4.07457 21.6771 3.32837 20.9309 3.32837 20.0104C3.32837 19.0899 4.07457 18.3438 4.99504 18.3438H9.99504C10.9155 18.3438 11.6617 19.0899 11.6617 20.0104ZM31.7802 31.7956C31.1292 32.4464 30.074 32.4464 29.4232 31.7956L25.8875 28.2599C25.2367 27.6091 25.2367 26.5539 25.8875 25.9029C26.5385 25.2521 27.5937 25.2521 28.2445 25.9029L31.7802 29.4386C32.431 30.0894 32.431 31.1446 31.7802 31.7956ZM14.1025 14.1179C13.4516 14.7687 12.3963 14.7687 11.7455 14.1179L8.20992 10.5823C7.55905 9.93145 7.55905 8.87618 8.20992 8.2253C8.8608 7.57443 9.91607 7.57443 10.567 8.2253L14.1025 11.7608C14.7534 12.4117 14.7534 13.467 14.1025 14.1179ZM8.20992 31.7956C7.55905 31.1446 7.55905 30.0894 8.20992 29.4386L11.7455 25.9029C12.3963 25.2521 13.4516 25.2521 14.1025 25.9029C14.7534 26.5539 14.7534 27.6091 14.1025 28.2599L10.567 31.7956C9.91607 32.4464 8.8608 32.4464 8.20992 31.7956ZM25.8875 14.1179C25.2367 13.467 25.2367 12.4117 25.8875 11.7608L29.4232 8.2253C30.074 7.57443 31.1292 7.57443 31.7802 8.2253C32.431 8.87618 32.431 9.93145 31.7802 10.5823L28.2445 14.1179C27.5937 14.7687 26.5385 14.7687 25.8875 14.1179Z"
17
+ fill="currentColor"
18
+ />
19
+ </svg>
20
+ );
21
+ };
@@ -0,0 +1,32 @@
1
+ import { type FC } from 'react';
2
+ import './style.scss';
3
+ import graphic from '../../../../assets/graphic.png';
4
+ import { LinkIcon } from '../asset/linkIcon';
5
+
6
+ export const Idle: FC = () => {
7
+ return (
8
+ <div className="ck-idle">
9
+ <figure className="ck-idle-graphic" aria-hidden="true">
10
+ <img src={graphic} alt="" />
11
+ </figure>
12
+ <div className="ck-idle-text">
13
+ <h3 className="ck-idle-title">Swap anytime, anywhere</h3>
14
+ <p className="ck-idle-desc">
15
+ Swap, earn, and build on the leading decentralized
16
+ <br />
17
+ crypto trading protocol.
18
+ </p>
19
+ <a
20
+ className="ck-idle-link"
21
+ href="#"
22
+ target="_blank"
23
+ rel="noopener noreferrer"
24
+ aria-label="Learn more about Credit Connect"
25
+ >
26
+ <span>Learn more</span>
27
+ <LinkIcon />
28
+ </a>
29
+ </div>
30
+ </div>
31
+ );
32
+ };
@@ -0,0 +1,61 @@
1
+ @use '../../../style/mixin.scss' as *;
2
+
3
+ .ck-idle {
4
+ display: flex;
5
+ flex-direction: column;
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 {
22
+ width: 100%;
23
+ height: 100%;
24
+ object-fit: contain;
25
+ }
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
+
44
+ .ck-idle-desc {
45
+ @include textToken('body4-l1-regular');
46
+ color: var(--scale-gray-600);
47
+ text-align: center;
48
+ width: 100%;
49
+ }
50
+
51
+ .ck-root .ck-idle-link {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ color: var(--scale-gray-600);
55
+ span {
56
+ @include textToken('body4-l1-regular');
57
+ }
58
+ svg {
59
+ flex-shrink: 0;
60
+ }
61
+ }