0xtrails 0.1.2 → 0.1.4

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 (158) hide show
  1. package/dist/address.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +86 -1
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{ccip-BmFTEOaB.js → ccip-dLSEJjCf.js} +55 -55
  7. package/dist/cctpqueue.d.ts +1 -1
  8. package/dist/cctpqueue.d.ts.map +1 -1
  9. package/dist/chains.d.ts +9 -3
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/constants.d.ts +1 -0
  12. package/dist/constants.d.ts.map +1 -1
  13. package/dist/decoders.d.ts +58 -0
  14. package/dist/decoders.d.ts.map +1 -0
  15. package/dist/ens.d.ts +13 -0
  16. package/dist/ens.d.ts.map +1 -0
  17. package/dist/error.d.ts +9 -0
  18. package/dist/error.d.ts.map +1 -1
  19. package/dist/{index-BPsVj7zK.js → index-BXbaLmtt.js} +28779 -25738
  20. package/dist/index.js +2 -2
  21. package/dist/intents.d.ts +4 -4
  22. package/dist/intents.d.ts.map +1 -1
  23. package/dist/lifi.d.ts +4 -0
  24. package/dist/lifi.d.ts.map +1 -0
  25. package/dist/metaTxns.d.ts +1 -1
  26. package/dist/metaTxns.d.ts.map +1 -1
  27. package/dist/mode.d.ts +1 -1
  28. package/dist/mode.d.ts.map +1 -1
  29. package/dist/preconditions.d.ts +1 -1
  30. package/dist/preconditions.d.ts.map +1 -1
  31. package/dist/prepareSend.d.ts +32 -24
  32. package/dist/prepareSend.d.ts.map +1 -1
  33. package/dist/prices.d.ts +3 -1
  34. package/dist/prices.d.ts.map +1 -1
  35. package/dist/proxyCaller.d.ts +0 -1
  36. package/dist/proxyCaller.d.ts.map +1 -1
  37. package/dist/relaySdk.d.ts.map +1 -1
  38. package/dist/relayer.d.ts.map +1 -1
  39. package/dist/tokenBalances.d.ts +1 -1
  40. package/dist/tokenBalances.d.ts.map +1 -1
  41. package/dist/tokens.d.ts +2 -1
  42. package/dist/tokens.d.ts.map +1 -1
  43. package/dist/trails.d.ts +4 -4
  44. package/dist/trails.d.ts.map +1 -1
  45. package/dist/transactions.d.ts +4 -0
  46. package/dist/transactions.d.ts.map +1 -1
  47. package/dist/utils.d.ts +6 -0
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/wallets.d.ts +247 -5
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/ChainFilterDropdown.d.ts +2 -0
  52. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  53. package/dist/widget/components/ConnectWallet.d.ts +1 -0
  54. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  55. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/ErrorDisplay.d.ts +9 -0
  57. package/dist/widget/components/ErrorDisplay.d.ts.map +1 -0
  58. package/dist/widget/components/FundSendForm.d.ts +2 -2
  59. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  60. package/dist/widget/components/OriginTransferInformation.d.ts +10 -0
  61. package/dist/widget/components/OriginTransferInformation.d.ts.map +1 -0
  62. package/dist/widget/components/PaySendForm.d.ts +2 -2
  63. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  64. package/dist/widget/components/QrCode.d.ts +1 -1
  65. package/dist/widget/components/QrCode.d.ts.map +1 -1
  66. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  67. package/dist/widget/components/Receipt.d.ts.map +1 -1
  68. package/dist/widget/components/Receive.d.ts +12 -0
  69. package/dist/widget/components/Receive.d.ts.map +1 -0
  70. package/dist/widget/components/RefundAddressInput.d.ts +13 -0
  71. package/dist/widget/components/RefundAddressInput.d.ts.map +1 -0
  72. package/dist/widget/components/Swap.d.ts +47 -0
  73. package/dist/widget/components/Swap.d.ts.map +1 -0
  74. package/dist/widget/components/SwapDisplay.d.ts +9 -0
  75. package/dist/widget/components/SwapDisplay.d.ts.map +1 -0
  76. package/dist/widget/components/TokenList.d.ts +0 -2
  77. package/dist/widget/components/TokenList.d.ts.map +1 -1
  78. package/dist/widget/components/TokenSelector.d.ts +26 -0
  79. package/dist/widget/components/TokenSelector.d.ts.map +1 -0
  80. package/dist/widget/components/TransferPendingVertical.d.ts +2 -0
  81. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  82. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  83. package/dist/widget/components/WalletConnectionPending.d.ts +12 -0
  84. package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -0
  85. package/dist/widget/components/WalletList.d.ts.map +1 -1
  86. package/dist/widget/components/YellowWarningAnimation.d.ts +2 -0
  87. package/dist/widget/components/YellowWarningAnimation.d.ts.map +1 -0
  88. package/dist/widget/hooks/useAmountUsd.d.ts +1 -3
  89. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
  90. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  91. package/dist/widget/hooks/useDebugScreens.d.ts +22 -0
  92. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -0
  93. package/dist/widget/hooks/useSendForm.d.ts +12 -6
  94. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  95. package/dist/widget/hooks/useTokenList.d.ts +2 -3
  96. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  97. package/dist/widget/index.js +1 -1
  98. package/dist/widget/widget.d.ts.map +1 -1
  99. package/package.json +19 -15
  100. package/src/aave.ts +13 -13
  101. package/src/address.ts +3 -0
  102. package/src/analytics.ts +192 -8
  103. package/src/apiClient.ts +1 -1
  104. package/src/cctpqueue.ts +1 -1
  105. package/src/chains.ts +45 -7
  106. package/src/constants.ts +7 -4
  107. package/src/decoders.ts +310 -0
  108. package/src/ens.ts +32 -0
  109. package/src/error.ts +101 -1
  110. package/src/intents.ts +10 -2
  111. package/src/lifi.ts +58 -0
  112. package/src/metaTxns.ts +1 -1
  113. package/src/mode.ts +1 -1
  114. package/src/morpho.ts +3 -3
  115. package/src/pools.ts +18 -18
  116. package/src/preconditions.ts +1 -1
  117. package/src/prepareSend.ts +463 -113
  118. package/src/prices.ts +26 -1
  119. package/src/proxyCaller.ts +2 -14
  120. package/src/relaySdk.ts +1 -0
  121. package/src/relayer.ts +8 -0
  122. package/src/tokenBalances.ts +24 -17
  123. package/src/tokens.ts +147 -22
  124. package/src/trails.ts +4 -4
  125. package/src/transactions.ts +35 -17
  126. package/src/utils.ts +28 -0
  127. package/src/wallets.ts +275 -35
  128. package/src/widget/compiled.css +2 -2
  129. package/src/widget/components/ChainFilterDropdown.tsx +42 -33
  130. package/src/widget/components/ChainImage.tsx +1 -1
  131. package/src/widget/components/ConnectWallet.tsx +92 -128
  132. package/src/widget/components/DebugScreensDropdown.tsx +6 -0
  133. package/src/widget/components/ErrorDisplay.tsx +150 -0
  134. package/src/widget/components/FundSendForm.tsx +78 -11
  135. package/src/widget/components/OriginTransferInformation.tsx +59 -0
  136. package/src/widget/components/PaySendForm.tsx +80 -13
  137. package/src/widget/components/QRCodeDeposit.tsx +6 -6
  138. package/src/widget/components/QrCode.tsx +278 -17
  139. package/src/widget/components/QuoteDetails.tsx +93 -25
  140. package/src/widget/components/Receipt.tsx +296 -103
  141. package/src/widget/components/Receive.tsx +146 -0
  142. package/src/widget/components/RecentTokens.tsx +1 -1
  143. package/src/widget/components/RefundAddressInput.tsx +149 -0
  144. package/src/widget/components/Swap.tsx +769 -0
  145. package/src/widget/components/SwapDisplay.tsx +68 -0
  146. package/src/widget/components/TokenList.tsx +27 -363
  147. package/src/widget/components/TokenSelector.tsx +405 -0
  148. package/src/widget/components/TransferPendingVertical.tsx +162 -112
  149. package/src/widget/components/WalletConnect.tsx +9 -7
  150. package/src/widget/components/WalletConnectionPending.tsx +157 -0
  151. package/src/widget/components/WalletList.tsx +6 -5
  152. package/src/widget/components/YellowWarningAnimation.tsx +146 -0
  153. package/src/widget/hooks/useAmountUsd.ts +3 -8
  154. package/src/widget/hooks/useCheckout.ts +3 -2
  155. package/src/widget/hooks/useDebugScreens.ts +583 -0
  156. package/src/widget/hooks/useSendForm.ts +111 -35
  157. package/src/widget/hooks/useTokenList.ts +155 -122
  158. package/src/widget/widget.tsx +503 -523
@@ -0,0 +1,405 @@
1
+ import { Search, ChevronLeft } from "lucide-react"
2
+ import type React from "react"
3
+ import { useEffect, useRef, useState, useMemo } from "react"
4
+ import { AnimatePresence, motion } from "framer-motion"
5
+ import type { Token, TokenFormatted } from "../hooks/useTokenList.js"
6
+ import { useTokenList } from "../hooks/useTokenList.js"
7
+ import { TokenImage } from "./TokenImage.js"
8
+ import type { Mode } from "../../mode.js"
9
+ import type { SupportedToken } from "../../tokens.js"
10
+ import { RecentTokens } from "./RecentTokens.js"
11
+ import { getChainInfo } from "../../chains.js"
12
+ import { ChainFilterDropdown } from "./ChainFilterDropdown.js"
13
+
14
+ interface TokenSelectorProps {
15
+ onTokenSelect: (selectedToken: Token) => void
16
+ targetAmountUsd?: number | null
17
+ targetAmountUsdFormatted?: string | null
18
+ onError: (error: Error | string | null) => void
19
+ mode?: Mode
20
+ recentTokens?: SupportedToken[]
21
+ onRecentTokenSelect?: (token: SupportedToken) => void
22
+ fundMethod?: string | null
23
+ onNavigateToFundMethods?: () => void
24
+ showContinueButton?: boolean
25
+ selectedToken?: Token | null
26
+ onContinue?: (selectedToken: Token) => void
27
+ showInsufficientBalance?: boolean
28
+ totalBalanceUsd?: number
29
+ totalBalanceUsdFormatted?: string
30
+ compactMode?: boolean
31
+ allSupportedTokens?: boolean
32
+ }
33
+
34
+ export const TokenSelector: React.FC<TokenSelectorProps> = ({
35
+ onTokenSelect,
36
+ targetAmountUsd,
37
+ onError,
38
+ recentTokens = [],
39
+ onRecentTokenSelect,
40
+ fundMethod,
41
+ onNavigateToFundMethods,
42
+ showContinueButton = false,
43
+ selectedToken,
44
+ onContinue,
45
+ showInsufficientBalance = false,
46
+ compactMode = false,
47
+ allSupportedTokens = false,
48
+ }) => {
49
+ const searchInputRef = useRef<HTMLInputElement>(null)
50
+ const [filterByChainId, setFilterByChainId] = useState<number | null>(null)
51
+
52
+ useEffect(() => {
53
+ // Auto-focus the search input when component mounts
54
+ if (searchInputRef.current) {
55
+ searchInputRef.current.focus()
56
+ }
57
+ }, [])
58
+
59
+ const {
60
+ searchQuery,
61
+ setSearchQuery,
62
+ handleTokenSelect,
63
+ filteredTokens,
64
+ isLoadingTokens,
65
+ isTokenSelected,
66
+ filteredTokensFormatted,
67
+ balanceError,
68
+ } = useTokenList({
69
+ onContinue: onTokenSelect, // Use onTokenSelect for the hook
70
+ targetAmountUsd,
71
+ onError,
72
+ fundMethod,
73
+ allSupportedTokens,
74
+ })
75
+
76
+ // Apply chain filter to tokens
77
+ const chainFilteredTokens = useMemo(() => {
78
+ if (filterByChainId === null) {
79
+ return filteredTokensFormatted
80
+ }
81
+ return filteredTokensFormatted.filter(
82
+ (token) => token.chainId === filterByChainId,
83
+ )
84
+ }, [filteredTokensFormatted, filterByChainId])
85
+
86
+ // Get unique chains from all tokens (not filtered by chain)
87
+ const uniqueChains = useMemo(() => {
88
+ const chainIds = new Set(
89
+ filteredTokensFormatted.map((token) => token.chainId),
90
+ )
91
+ return Array.from(chainIds)
92
+ .map((chainId) => {
93
+ const chainInfo = getChainInfo(chainId)
94
+ return {
95
+ chainId,
96
+ name: chainInfo?.name || `Chain ${chainId}`,
97
+ imageUrl: "", // We'll use TokenImage component for chain icons
98
+ }
99
+ })
100
+ .sort((a, b) => a.name.localeCompare(b.name))
101
+ }, [filteredTokensFormatted])
102
+
103
+ // Filter recent tokens to only show ones that exist in the current token list and match search
104
+ const filteredRecentTokens = recentTokens.filter((recentToken) => {
105
+ // First check if this recent token exists in the current token list
106
+ const existsInTokenList = chainFilteredTokens.some(
107
+ (token) =>
108
+ token.contractAddress.toLowerCase() ===
109
+ recentToken.contractAddress.toLowerCase() &&
110
+ token.chainId === recentToken.chainId,
111
+ )
112
+
113
+ if (!existsInTokenList) return false
114
+
115
+ // Then apply search filtering
116
+ if (!searchQuery.trim()) return true
117
+
118
+ const query = searchQuery.toLowerCase()
119
+ return (
120
+ recentToken.symbol.toLowerCase().includes(query) ||
121
+ recentToken.name.toLowerCase().includes(query) ||
122
+ recentToken.contractAddress.toLowerCase().includes(query)
123
+ )
124
+ })
125
+
126
+ // Handle recent token selection by finding the actual token from the list
127
+ const handleRecentTokenSelect = (recentToken: SupportedToken) => {
128
+ // Find the actual token from the filtered tokens list
129
+ const actualToken = chainFilteredTokens.find(
130
+ (token) =>
131
+ token.contractAddress.toLowerCase() ===
132
+ recentToken.contractAddress.toLowerCase() &&
133
+ token.chainId === recentToken.chainId,
134
+ )
135
+
136
+ if (actualToken) {
137
+ // Use the actual token with balance info
138
+ handleTokenSelect(actualToken)
139
+ } else if (onRecentTokenSelect) {
140
+ // Fallback to the original handler if token not found in current list
141
+ onRecentTokenSelect(recentToken)
142
+ }
143
+ }
144
+
145
+ return (
146
+ <div className="space-y-2">
147
+ {/* Search Field */}
148
+ <div className="relative mb-2">
149
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
150
+ <Search className={`h-5 w-5 ${"text-gray-500 dark:text-gray-500"}`} />
151
+ </div>
152
+ <input
153
+ ref={searchInputRef}
154
+ type="text"
155
+ value={searchQuery}
156
+ onChange={(e) => setSearchQuery(e.target.value)}
157
+ placeholder="Search tokens"
158
+ className="block w-full pl-10 pr-12 py-2 border border-solid border-gray-200 dark:border-gray-700 trails-border-radius-input focus:ring-2 focus:ring-blue-500 focus:border-blue-500 trails-input"
159
+ />
160
+
161
+ {/* Chain Filter Dropdown */}
162
+ <div className="absolute inset-y-0 right-0 flex items-center">
163
+ <ChainFilterDropdown
164
+ chains={uniqueChains}
165
+ selectedChainId={filterByChainId}
166
+ onChainSelect={setFilterByChainId}
167
+ compactMode={compactMode}
168
+ nestedMode={true}
169
+ />
170
+ </div>
171
+ </div>
172
+
173
+ {/* Recent Tokens */}
174
+ <AnimatePresence>
175
+ {!isLoadingTokens && filteredRecentTokens.length > 0 && (
176
+ <motion.div
177
+ initial={{ opacity: 0, height: 0 }}
178
+ animate={{ opacity: 1, height: "auto" }}
179
+ exit={{ opacity: 0, height: 0 }}
180
+ transition={{ duration: 0.05, ease: "easeOut" }}
181
+ >
182
+ <RecentTokens
183
+ recentTokens={filteredRecentTokens}
184
+ onTokenSelect={handleRecentTokenSelect}
185
+ />
186
+ </motion.div>
187
+ )}
188
+ </AnimatePresence>
189
+
190
+ {isLoadingTokens && (
191
+ <div className="text-center py-4">
192
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 mx-auto border-solid border-black dark:border-white"></div>
193
+ <p className="mt-2 text-gray-500 dark:text-gray-400">
194
+ Loading your token balances...
195
+ </p>
196
+ </div>
197
+ )}
198
+
199
+ {!isLoadingTokens && !balanceError && filteredTokens.length === 0 && (
200
+ <div className="text-center py-4 rounded-lg trails-bg-secondary">
201
+ <p className="text-gray-500 dark:text-gray-400">
202
+ {searchQuery.trim()
203
+ ? "No tokens found matching your search."
204
+ : fundMethod === "qr-code" || fundMethod === "exchange"
205
+ ? ""
206
+ : "No available tokens found"}
207
+ </p>
208
+ </div>
209
+ )}
210
+
211
+ {/* Token List */}
212
+ {chainFilteredTokens.length > 0 && (
213
+ <div
214
+ className={`${compactMode ? "max-h-48" : filteredRecentTokens.length > 0 ? "max-h-[17vh]" : "max-h-[24vh]"} overflow-y-auto trails-scrollbar space-y-1`}
215
+ >
216
+ <AnimatePresence mode="popLayout">
217
+ {chainFilteredTokens.map((token: TokenFormatted) => {
218
+ const {
219
+ symbol,
220
+ imageUrl,
221
+ chainId,
222
+ contractAddress,
223
+ balanceUsdFormatted,
224
+ tokenName,
225
+ priceUsd,
226
+ balanceFormatted,
227
+ isSufficientBalance,
228
+ } = token
229
+
230
+ return (
231
+ <motion.button
232
+ key={`${chainId}-${contractAddress}`}
233
+ layout
234
+ initial={{ opacity: 0, y: 20 }}
235
+ animate={{ opacity: 1, y: 0 }}
236
+ exit={{ opacity: 0, y: -20 }}
237
+ transition={{
238
+ duration: 0.05,
239
+ ease: "easeOut",
240
+ layout: { duration: 0.08, ease: "easeOut" },
241
+ }}
242
+ type="button"
243
+ onClick={() => handleTokenSelect(token)}
244
+ title={
245
+ !isSufficientBalance &&
246
+ fundMethod !== "qr-code" &&
247
+ fundMethod !== "exchange"
248
+ ? "Insufficient balance for this token"
249
+ : undefined
250
+ }
251
+ className={`w-full py-2 px-4 flex items-center space-x-3 transition-all duration-200 trails-bg-secondary trails-hover-bg trails-border-radius-list-button ${
252
+ isTokenSelected(token) ? "trails-bg-card" : ""
253
+ } ${!isSufficientBalance && fundMethod !== "qr-code" && fundMethod !== "exchange" ? "opacity-75 cursor-not-allowed" : "cursor-pointer"}`}
254
+ >
255
+ <div className="relative flex-shrink-0 mr-2">
256
+ <div
257
+ className={`rounded-full flex items-center justify-center bg-gray-100 dark:bg-gray-700 ${
258
+ !isSufficientBalance &&
259
+ fundMethod !== "qr-code" &&
260
+ fundMethod !== "exchange"
261
+ ? "opacity-80"
262
+ : ""
263
+ }`}
264
+ >
265
+ {contractAddress ? (
266
+ <TokenImage
267
+ symbol={symbol}
268
+ imageUrl={imageUrl}
269
+ chainId={chainId}
270
+ size={32}
271
+ />
272
+ ) : (
273
+ <span
274
+ className={`text-base font-medium text-gray-600 dark:text-gray-300 ${
275
+ !isSufficientBalance &&
276
+ fundMethod !== "qr-code" &&
277
+ fundMethod !== "exchange"
278
+ ? "opacity-70"
279
+ : ""
280
+ }`}
281
+ >
282
+ {symbol}
283
+ </span>
284
+ )}
285
+ </div>
286
+ </div>
287
+
288
+ <div className="flex-1 min-w-0 text-left">
289
+ <h3
290
+ className={`text-sm font-medium truncate text-gray-900 dark:text-white ${
291
+ !isSufficientBalance &&
292
+ fundMethod !== "qr-code" &&
293
+ fundMethod !== "exchange"
294
+ ? "opacity-70"
295
+ : ""
296
+ }`}
297
+ >
298
+ {tokenName}
299
+ </h3>
300
+ <p
301
+ className={`text-xs text-gray-500 dark:text-gray-400 ${
302
+ !isSufficientBalance &&
303
+ fundMethod !== "qr-code" &&
304
+ fundMethod !== "exchange"
305
+ ? "opacity-70"
306
+ : ""
307
+ }`}
308
+ >
309
+ {symbol}
310
+ </p>
311
+ </div>
312
+
313
+ {fundMethod !== "qr-code" && fundMethod !== "exchange" && (
314
+ <div className="text-right flex-shrink-0">
315
+ {priceUsd > 0 ? (
316
+ <>
317
+ <p
318
+ className={`text-sm font-medium text-gray-900 dark:text-white ${
319
+ !isSufficientBalance &&
320
+ fundMethod !== "qr-code" &&
321
+ fundMethod !== "exchange"
322
+ ? "opacity-70"
323
+ : ""
324
+ }`}
325
+ >
326
+ {balanceUsdFormatted}
327
+ </p>
328
+ <p
329
+ className={`text-xs text-gray-500 dark:text-gray-400 ${
330
+ !isSufficientBalance &&
331
+ fundMethod !== "qr-code" &&
332
+ fundMethod !== "exchange"
333
+ ? "opacity-70"
334
+ : ""
335
+ }`}
336
+ >
337
+ {balanceFormatted}
338
+ </p>
339
+ </>
340
+ ) : (
341
+ <p
342
+ className={`text-sm font-medium text-gray-900 dark:text-white ${
343
+ !isSufficientBalance &&
344
+ fundMethod !== "qr-code" &&
345
+ fundMethod !== "exchange"
346
+ ? "opacity-70"
347
+ : ""
348
+ }`}
349
+ >
350
+ {balanceFormatted}
351
+ </p>
352
+ )}
353
+ </div>
354
+ )}
355
+ </motion.button>
356
+ )
357
+ })}
358
+ </AnimatePresence>
359
+ </div>
360
+ )}
361
+
362
+ {showInsufficientBalance &&
363
+ fundMethod !== "qr-code" &&
364
+ fundMethod !== "exchange" && (
365
+ <div
366
+ className={`text-left py-3 px-4 rounded-lg ${"bg-amber-500/10 border border-solid border-amber-500/30"}`}
367
+ >
368
+ <p className={`text-xs font-medium ${"text-amber-400"}`}>
369
+ Insufficient balance
370
+ </p>
371
+ <p className={`text-xs mt-1 ${"text-amber-300"}`}>
372
+ You do not have enough funds to reach the target amount
373
+ </p>
374
+ </div>
375
+ )}
376
+
377
+ {showContinueButton && onContinue && selectedToken && (
378
+ <div className="space-y-4">
379
+ <button
380
+ type="button"
381
+ onClick={() => onContinue(selectedToken)}
382
+ disabled={!selectedToken}
383
+ className={`w-full font-semibold py-3 px-4 trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white disabled:text-gray-500 disabled:cursor-not-allowed cursor-pointer`}
384
+ >
385
+ Continue
386
+ </button>
387
+ </div>
388
+ )}
389
+
390
+ {/* Pay with another method button */}
391
+ {onNavigateToFundMethods && !isLoadingTokens && (
392
+ <div className="text-center pt-2 pb-1">
393
+ <button
394
+ type="button"
395
+ onClick={onNavigateToFundMethods}
396
+ className="inline-flex items-center justify-center space-x-2 px-4 py-2 text-sm font-medium trails-text-secondary trails-bg-secondary trails-hover-bg trails-border trails-border-radius-button transition-all duration-200 cursor-pointer"
397
+ >
398
+ <ChevronLeft className="h-4 w-4" />
399
+ <span>Pay with another method</span>
400
+ </button>
401
+ </div>
402
+ )}
403
+ </div>
404
+ )
405
+ }