1
- import { useQuery } from '@tanstack/react-query'
2
1
import * as React from 'react'
3
- import { Image , View } from 'react-native'
2
+ import { ActivityIndicator , Image , View } from 'react-native'
4
3
import { sprintf } from 'sprintf-js'
5
4
6
5
// TradeOptionSelectScene - Updated layout for design requirements
7
6
import paymentTypeLogoApplePay from '../../assets/images/paymentTypes/paymentTypeLogoApplePay.png'
7
+ import { useRampQuotes } from '../../hooks/useRampQuotes'
8
8
import { lstrings } from '../../locales/strings'
9
9
import type {
10
10
RampQuoteRequest ,
@@ -13,11 +13,11 @@ import type {
13
13
} from '../../plugins/ramps/rampPluginTypes'
14
14
import { useSelector } from '../../types/reactRedux'
15
15
import type { BuyTabSceneProps } from '../../types/routerTypes'
16
- import type { Result } from '../../types/types'
17
16
import { getPaymentTypeIcon } from '../../util/paymentTypeIcons'
18
17
import { getPaymentTypeDisplayName } from '../../util/paymentTypeUtils'
19
18
import { AlertCardUi4 } from '../cards/AlertCard'
20
19
import { PaymentOptionCard } from '../cards/PaymentOptionCard'
20
+ import { EdgeAnim } from '../common/EdgeAnim'
21
21
import { SceneWrapper } from '../common/SceneWrapper'
22
22
import { SectionHeader } from '../common/SectionHeader'
23
23
import { styled } from '../hoc/styled'
@@ -34,61 +34,24 @@ export interface RampSelectOptionParams {
34
34
35
35
interface Props extends BuyTabSceneProps < 'rampSelectOption' > { }
36
36
37
- // Define error type for failed quotes
38
- interface QuoteError {
39
- pluginId : string
40
- pluginDisplayName : string
41
- error : unknown
42
- }
43
-
44
37
export const TradeOptionSelectScene = ( props : Props ) : React . JSX . Element => {
45
38
const { route } = props
46
39
const { rampQuoteRequest, quotes : precomputedQuotes } = route . params
47
40
41
+ const theme = useTheme ( )
48
42
const rampPlugins = useSelector ( state => state . rampPlugins . plugins )
49
43
const isPluginsLoading = useSelector ( state => state . rampPlugins . isLoading )
50
44
51
- // Use TanStack Query to fetch quotes only if not provided
52
- const { data : quoteResults = [ ] , isLoading : isLoadingQuotes } = useQuery <
53
- Array < Result < RampQuoteResult [ ] , QuoteError > >
54
- > ( {
55
- queryKey : [ 'rampQuotes' , rampQuoteRequest ] ,
56
- queryFn : async ( ) => {
57
- // Skip fetching if we already have quotes
58
- if ( precomputedQuotes && precomputedQuotes . length > 0 ) {
59
- return [ ]
60
- }
61
-
62
- if ( Object . keys ( rampPlugins ) . length === 0 ) {
63
- return [ ]
64
- }
65
-
66
- const quotePromises = Object . values ( rampPlugins ) . map (
67
- async ( plugin ) : Promise < Result < RampQuoteResult [ ] , QuoteError > > => {
68
- try {
69
- const quotes = await plugin . fetchQuote ( rampQuoteRequest )
70
- return { ok : true , value : quotes }
71
- } catch ( error ) {
72
- console . warn ( `Failed to get quote from ${ plugin . pluginId } :` , error )
73
- return {
74
- ok : false ,
75
- error : {
76
- pluginId : plugin . pluginId ,
77
- pluginDisplayName : plugin . rampInfo . pluginDisplayName ,
78
- error
79
- }
80
- }
81
- }
82
- }
83
- )
84
-
85
- return await Promise . all ( quotePromises )
86
- } ,
87
- refetchOnMount : 'always' ,
88
- refetchInterval : 60000 ,
89
- enabled : ! precomputedQuotes || precomputedQuotes . length === 0 ,
90
- staleTime : 30000 , // Consider data stale after 30 seconds
91
- gcTime : 300000 // Keep in cache for 5 minutes
45
+ // Use the new hook with precomputed quotes
46
+ const {
47
+ quotes : allQuotes ,
48
+ isLoading : isLoadingQuotes ,
49
+ isFetching : isFetchingQuotes ,
50
+ errors : failedQuotes
51
+ } = useRampQuotes ( {
52
+ rampQuoteRequest,
53
+ plugins : rampPlugins ,
54
+ precomputedQuotes
92
55
} )
93
56
94
57
const handleQuotePress = async ( quote : RampQuoteResult ) => {
@@ -101,24 +64,6 @@ export const TradeOptionSelectScene = (props: Props): React.JSX.Element => {
101
64
}
102
65
}
103
66
104
- // Use precomputed quotes if available, otherwise use fetched quotes
105
- const allQuotes : RampQuoteResult [ ] = React . useMemo ( ( ) => {
106
- if ( precomputedQuotes && precomputedQuotes . length > 0 ) {
107
- return precomputedQuotes
108
- }
109
-
110
- return quoteResults
111
- . filter (
112
- ( result ) : result is { ok : true ; value : RampQuoteResult [ ] } => result . ok
113
- )
114
- . flatMap ( result => result . value )
115
- . sort ( ( a , b ) => {
116
- const rateA = parseFloat ( a . fiatAmount ) / parseFloat ( a . cryptoAmount )
117
- const rateB = parseFloat ( b . fiatAmount ) / parseFloat ( b . cryptoAmount )
118
- return rateA - rateB
119
- } )
120
- } , [ precomputedQuotes , quoteResults ] )
121
-
122
67
// Get the best quote overall
123
68
const bestQuoteOverall = allQuotes [ 0 ]
124
69
@@ -133,7 +78,7 @@ export const TradeOptionSelectScene = (props: Props): React.JSX.Element => {
133
78
} )
134
79
135
80
// Sort quotes within each payment type group
136
- grouped . forEach ( ( quotes , paymentType ) => {
81
+ grouped . forEach ( quotes => {
137
82
quotes . sort ( ( a , b ) => {
138
83
const rateA = parseFloat ( a . fiatAmount ) / parseFloat ( a . cryptoAmount )
139
84
const rateB = parseFloat ( b . fiatAmount ) / parseFloat ( b . cryptoAmount )
@@ -144,18 +89,24 @@ export const TradeOptionSelectScene = (props: Props): React.JSX.Element => {
144
89
return grouped
145
90
} , [ allQuotes ] )
146
91
147
- // Get failed quotes for error display
148
- const failedQuotes = quoteResults . filter (
149
- ( result ) : result is { ok : false ; error : QuoteError } => ! result . ok
150
- )
92
+ // Only show loading state if we have no quotes to display
93
+ const showLoadingState =
94
+ isPluginsLoading || ( isLoadingQuotes && allQuotes . length === 0 )
151
95
152
96
return (
153
97
< SceneWrapper scroll hasTabs >
154
98
< SceneContainer headerTitle = { lstrings . trade_option_buy_title } >
155
99
< SectionHeader
156
100
leftTitle = { lstrings . trade_option_select_payment_method }
101
+ rightNode = {
102
+ isFetchingQuotes ? (
103
+ < EdgeAnim enter = { { type : 'fadeIn' , delay : 200 } } >
104
+ < ActivityIndicator size = "small" color = { theme . primaryText } />
105
+ </ EdgeAnim >
106
+ ) : undefined
107
+ }
157
108
/>
158
- { isPluginsLoading || isLoadingQuotes ? (
109
+ { showLoadingState ? (
159
110
< >
160
111
< ShimmerCard >
161
112
< Shimmer />
@@ -192,19 +143,19 @@ export const TradeOptionSelectScene = (props: Props): React.JSX.Element => {
192
143
/>
193
144
)
194
145
) }
195
- { failedQuotes . map ( result => {
146
+ { failedQuotes . map ( error => {
196
147
const errorMessage =
197
- result . error . error instanceof Error
198
- ? result . error . error . message
199
- : String ( result . error . error )
148
+ error . error instanceof Error
149
+ ? error . error . message
150
+ : String ( error . error )
200
151
201
152
return (
202
153
< AlertCardUi4
203
- key = { `error-${ result . error . pluginId } ` }
154
+ key = { `error-${ error . pluginId } ` }
204
155
type = "error"
205
156
title = { sprintf (
206
157
lstrings . trade_option_provider_failed_s ,
207
- result . error . pluginDisplayName
158
+ error . pluginDisplayName
208
159
) }
209
160
body = { errorMessage }
210
161
marginRem = { [ 0.5 , 0.5 ] }
@@ -247,9 +198,8 @@ const QuoteResult: React.FC<{
247
198
const icon = paymentTypeIcon ?? { uri : selectedQuote . partnerIcon }
248
199
249
200
// Determine custom title rendering
250
- const paymentType = selectedQuote . paymentType
251
- const customTitleKey = paymentTypeToCustomTitleKey [ paymentType ]
252
- const defaultTitle = getPaymentTypeDisplayName ( paymentType )
201
+ const customTitleKey = paymentTypeToCustomTitleKey [ selectedQuote . paymentType ]
202
+ const defaultTitle = getPaymentTypeDisplayName ( selectedQuote . paymentType )
253
203
254
204
// Render custom title based on payment type
255
205
let titleComponent : React . ReactNode
0 commit comments