1
- import React , { useEffect , useState } from 'react' ;
1
+ import React , { useCallback , useEffect , useState } from 'react' ;
2
2
import clsx from 'clsx' ;
3
3
import { Field , FieldProps , useFormikContext } from 'formik' ;
4
4
import { InferType } from 'yup' ;
5
5
import { StandaloneArrowDownBoldIcon } from '@deriv/quill-icons' ;
6
- import { Input , useDevice } from '@deriv-com/ui' ;
6
+ import { Input , Text , useDevice } from '@deriv-com/ui' ;
7
+ import { PercentageSelector } from '../PercentageSelector' ;
7
8
import { getCryptoFiatConverterValidationSchema , TGetCryptoFiatConverterValidationSchema } from './utils' ;
8
9
import styles from './CryptoFiatConverter.module.scss' ;
9
10
11
+ type TContext = InferType < ReturnType < typeof getCryptoFiatConverterValidationSchema > > ;
12
+
10
13
type TGetConvertedAmountParams =
11
14
| TGetCryptoFiatConverterValidationSchema [ 'fromAccount' ]
12
15
| TGetCryptoFiatConverterValidationSchema [ 'toAccount' ] ;
@@ -27,29 +30,46 @@ const getConvertedAmount = (amount: string, source: TGetConvertedAmountParams, t
27
30
return convertedValue ;
28
31
} ;
29
32
30
- type TContext = InferType < ReturnType < typeof getCryptoFiatConverterValidationSchema > > ;
31
-
32
33
const CryptoFiatConverter : React . FC < TGetCryptoFiatConverterValidationSchema > = ( { fromAccount, toAccount } ) => {
33
34
const { isMobile } = useDevice ( ) ;
34
- const [ isFromInputActive , setIsFromInputActive ] = useState ( true ) ;
35
+ const [ isFromInputField , setIsFromInputField ] = useState < boolean > ( true ) ;
36
+ const { errors, setFieldValue, setValues, values } = useFormikContext < TContext > ( ) ;
37
+ const percentage =
38
+ fromAccount . balance && Number ( values . fromAmount ) && ! errors . fromAmount
39
+ ? Math . round ( ( Number ( values . fromAmount ) * 100 ) / fromAccount . balance )
40
+ : 0 ;
35
41
36
- const { errors , setFieldValue , setValues } = useFormikContext < TContext > ( ) ;
42
+ const isDifferentCurrency = fromAccount . currency !== toAccount . currency ;
37
43
38
44
useEffect ( ( ) => {
39
- if ( errors . toAmount && ! isFromInputActive ) {
45
+ if ( isDifferentCurrency && errors . toAmount && ! isFromInputField ) {
40
46
setFieldValue ( 'fromAmount' , '' ) ;
41
47
}
42
- } , [ errors . toAmount , isFromInputActive , setFieldValue ] ) ;
48
+ } , [ isDifferentCurrency , errors . toAmount , isFromInputField , setFieldValue ] ) ;
43
49
44
50
useEffect ( ( ) => {
45
- if ( errors . fromAmount && isFromInputActive ) {
51
+ if ( isDifferentCurrency && errors . fromAmount && isFromInputField ) {
46
52
setFieldValue ( 'toAmount' , '' ) ;
47
53
}
48
- } , [ errors . fromAmount , isFromInputActive , setFieldValue ] ) ;
54
+ } , [ isDifferentCurrency , errors . fromAmount , isFromInputField , setFieldValue ] ) ;
55
+
56
+ const handlePercentageChange = useCallback (
57
+ ( per : number ) => {
58
+ const computedAmount =
59
+ ( ( Number ( fromAccount . balance ) * per ) / 100 ) . toFixed ( fromAccount . fractionalDigits ) ?? 0 ;
60
+ const convertedAmount = getConvertedAmount ( computedAmount , fromAccount , toAccount ) ;
61
+
62
+ setValues ( currentValues => ( {
63
+ ...currentValues ,
64
+ fromAmount : computedAmount ,
65
+ toAmount : convertedAmount ,
66
+ } ) ) ;
67
+ } ,
68
+ [ fromAccount , setValues , toAccount ]
69
+ ) ;
49
70
50
71
const handleFromAmountChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
51
72
const convertedValue = getConvertedAmount ( e . target . value , fromAccount , toAccount ) ;
52
-
53
73
setValues ( currentValues => ( {
54
74
...currentValues ,
55
75
fromAmount : e . target . value ,
@@ -69,52 +89,71 @@ const CryptoFiatConverter: React.FC<TGetCryptoFiatConverterValidationSchema> = (
69
89
70
90
return (
71
91
< div className = { styles . container } >
72
- < Field name = 'fromAmount' >
73
- { ( { field } : FieldProps ) => (
74
- < Input
75
- { ...field }
76
- autoComplete = 'off'
77
- data-testid = 'dt_crypto_fiat_converter_from_amount_field'
78
- error = { Boolean ( errors . fromAmount ) }
79
- isFullWidth = { fromAccount . currency !== toAccount . currency }
80
- label = { `Amount (${ fromAccount . currency } )` }
81
- message = { errors . fromAmount }
82
- onChange = { handleFromAmountChange }
83
- onFocus = { ( ) => {
84
- setIsFromInputActive ( true ) ;
85
- } }
86
- type = 'text'
92
+ { isDifferentCurrency && (
93
+ < div
94
+ className = { styles [ 'percentage-selector-container' ] }
95
+ data-testid = 'dt_crypto_fiat_converter_percentage_selector'
96
+ >
97
+ < PercentageSelector
98
+ amount = { ! errors . fromAmount ? Number ( values . fromAmount ) : 0 }
99
+ balance = { Number ( fromAccount . balance ) }
100
+ onChangePercentage = { handlePercentageChange }
87
101
/>
88
- ) }
89
- </ Field >
90
- { fromAccount . currency !== toAccount . currency && (
91
- < >
92
- < div className = { styles [ 'arrow-container' ] } >
93
- < StandaloneArrowDownBoldIcon
94
- className = { clsx ( styles [ 'arrow-icon' ] , { [ styles [ 'arrow-icon--rtl' ] ] : isFromInputActive } ) }
95
- iconSize = { isMobile ? 'sm' : 'md' }
102
+ < Text as = 'div' color = 'less-prominent' size = 'xs' >
103
+ { `${ percentage } % of available balance (${ fromAccount . displayBalance } )` }
104
+ </ Text >
105
+ </ div >
106
+ ) }
107
+ < div className = { styles [ 'input-container' ] } >
108
+ < Field name = 'fromAmount' >
109
+ { ( { field } : FieldProps ) => (
110
+ < Input
111
+ { ...field }
112
+ autoComplete = 'off'
113
+ data-testid = 'dt_crypto_fiat_converter_from_amount_field'
114
+ error = { Boolean ( errors . fromAmount ) }
115
+ isFullWidth = { fromAccount . currency !== toAccount . currency }
116
+ label = { `Amount (${ fromAccount . currency } )` }
117
+ message = { errors . fromAmount }
118
+ onChange = { handleFromAmountChange }
119
+ onFocus = { ( ) => {
120
+ setIsFromInputField ( true ) ;
121
+ } }
122
+ type = 'text'
96
123
/>
97
- </ div >
98
- < Field name = 'toAmount' >
99
- { ( { field } : FieldProps ) => (
100
- < Input
101
- { ...field }
102
- autoComplete = 'off'
103
- data-testid = 'dt_crypto_fiat_converter_to_amount_field'
104
- error = { Boolean ( errors . toAmount ) }
105
- isFullWidth
106
- label = { `Amount (${ toAccount . currency } )` }
107
- message = { errors . toAmount }
108
- onChange = { handleToAmountChange }
109
- onFocus = { ( ) => {
110
- setIsFromInputActive ( false ) ;
111
- } }
112
- type = 'text'
124
+ ) }
125
+ </ Field >
126
+ { isDifferentCurrency && (
127
+ < >
128
+ < div className = { styles [ 'arrow-container' ] } data-testid = 'dt_crypto_fiat_converter_arrow_icon' >
129
+ < StandaloneArrowDownBoldIcon
130
+ className = { clsx ( styles [ 'arrow-icon' ] , {
131
+ [ styles [ 'arrow-icon--rtl' ] ] : isFromInputField ,
132
+ } ) }
133
+ iconSize = { isMobile ? 'sm' : 'md' }
113
134
/>
114
- ) }
115
- </ Field >
116
- </ >
117
- ) }
135
+ </ div >
136
+ < Field name = 'toAmount' >
137
+ { ( { field } : FieldProps ) => (
138
+ < Input
139
+ { ...field }
140
+ autoComplete = 'off'
141
+ data-testid = 'dt_crypto_fiat_converter_to_amount_field'
142
+ error = { Boolean ( errors . toAmount ) }
143
+ isFullWidth
144
+ label = { `Amount (${ toAccount . currency } )` }
145
+ message = { errors . toAmount }
146
+ onChange = { handleToAmountChange }
147
+ onFocus = { ( ) => {
148
+ setIsFromInputField ( false ) ;
149
+ } }
150
+ type = 'text'
151
+ />
152
+ ) }
153
+ </ Field >
154
+ </ >
155
+ ) }
156
+ </ div >
118
157
</ div >
119
158
) ;
120
159
} ;
0 commit comments