1- import { useEffect , useRef } from "react" ;
1+ import { useCallback , useEffect , useRef , useState } from "react" ;
22import { useTranslation } from "react-i18next" ;
3+ import { useAccount } from "~/app/context/AccountContext" ;
4+ import { useSettings } from "~/app/context/SettingsContext" ;
35import { classNames } from "~/app/utils" ;
4-
56import { RangeLabel } from "./rangeLabel" ;
67
8+ export type DualCurrencyFieldChangeEvent =
9+ React . ChangeEvent < HTMLInputElement > & {
10+ target : HTMLInputElement & {
11+ valueInFiat : number ;
12+ formattedValueInFiat : string ;
13+ valueInSats : number ;
14+ formattedValueInSats : string ;
15+ } ;
16+ } ;
17+
718export type Props = {
819 suffix ?: string ;
920 endAdornment ?: React . ReactNode ;
10- fiatValue : string ;
1121 label : string ;
1222 hint ?: string ;
1323 amountExceeded ?: boolean ;
1424 rangeExceeded ?: boolean ;
25+ baseToAltRate ?: number ;
26+ showFiat ?: boolean ;
27+ onChange ?: ( e : DualCurrencyFieldChangeEvent ) => void ;
1528} ;
1629
1730export default function DualCurrencyField ( {
1831 label,
19- fiatValue ,
32+ showFiat = true ,
2033 id,
2134 placeholder,
2235 required = false ,
@@ -38,10 +51,140 @@ export default function DualCurrencyField({
3851 rangeExceeded,
3952} : React . InputHTMLAttributes < HTMLInputElement > & Props ) {
4053 const { t : tCommon } = useTranslation ( "common" ) ;
54+ const { getFormattedInCurrency, getCurrencyRate, settings } = useSettings ( ) ;
55+ const { account } = useAccount ( ) ;
56+
4157 const inputEl = useRef < HTMLInputElement > ( null ) ;
4258 const outerStyles =
4359 "rounded-md border border-gray-300 dark:border-gray-800 bg-white dark:bg-black transition duration-300" ;
4460
61+ const initialized = useRef ( false ) ;
62+ const [ useFiatAsMain , _setUseFiatAsMain ] = useState ( false ) ;
63+ const [ altFormattedValue , setAltFormattedValue ] = useState ( "" ) ;
64+ const [ minValue , setMinValue ] = useState ( min ) ;
65+ const [ maxValue , setMaxValue ] = useState ( max ) ;
66+ const [ inputValue , setInputValue ] = useState ( value || 0 ) ;
67+
68+ const getValues = useCallback (
69+ async ( value : number , useFiatAsMain : boolean ) => {
70+ let valueInSats = Number ( value ) ;
71+ let valueInFiat = 0 ;
72+
73+ if ( showFiat ) {
74+ valueInFiat = Number ( value ) ;
75+ const rate = await getCurrencyRate ( ) ;
76+ if ( useFiatAsMain ) {
77+ valueInSats = Math . round ( valueInSats / rate ) ;
78+ } else {
79+ valueInFiat = Math . round ( valueInFiat * rate * 100 ) / 100.0 ;
80+ }
81+ }
82+
83+ const formattedSats = getFormattedInCurrency ( valueInSats , "BTC" ) ;
84+ let formattedFiat = "" ;
85+
86+ if ( showFiat && valueInFiat ) {
87+ formattedFiat = getFormattedInCurrency ( valueInFiat , settings . currency ) ;
88+ }
89+
90+ return {
91+ valueInSats,
92+ formattedSats,
93+ valueInFiat,
94+ formattedFiat,
95+ } ;
96+ } ,
97+ [ getCurrencyRate , getFormattedInCurrency , showFiat , settings . currency ]
98+ ) ;
99+
100+ useEffect ( ( ) => {
101+ ( async ( ) => {
102+ if ( showFiat ) {
103+ const { formattedSats, formattedFiat } = await getValues (
104+ Number ( inputValue ) ,
105+ useFiatAsMain
106+ ) ;
107+ setAltFormattedValue ( useFiatAsMain ? formattedSats : formattedFiat ) ;
108+ }
109+ } ) ( ) ;
110+ } , [ useFiatAsMain , inputValue , getValues , showFiat ] ) ;
111+
112+ const setUseFiatAsMain = useCallback (
113+ async ( v : boolean ) => {
114+ if ( ! showFiat ) v = false ;
115+
116+ const rate = showFiat ? await getCurrencyRate ( ) : 1 ;
117+ if ( min ) {
118+ let minV ;
119+ if ( v ) {
120+ minV = ( Math . round ( Number ( min ) * rate * 100 ) / 100.0 ) . toString ( ) ;
121+ } else {
122+ minV = min ;
123+ }
124+
125+ setMinValue ( minV ) ;
126+ }
127+ if ( max ) {
128+ let maxV ;
129+ if ( v ) {
130+ maxV = ( Math . round ( Number ( max ) * rate * 100 ) / 100.0 ) . toString ( ) ;
131+ } else {
132+ maxV = max ;
133+ }
134+
135+ setMaxValue ( maxV ) ;
136+ }
137+
138+ let newValue ;
139+ if ( v ) {
140+ newValue = Math . round ( Number ( inputValue ) * rate * 100 ) / 100.0 ;
141+ } else {
142+ newValue = Math . round ( Number ( inputValue ) / rate ) ;
143+ }
144+
145+ _setUseFiatAsMain ( v ) ;
146+ setInputValue ( newValue ) ;
147+ } ,
148+ [ showFiat , getCurrencyRate , inputValue , min , max ]
149+ ) ;
150+
151+ const swapCurrencies = ( ) => {
152+ setUseFiatAsMain ( ! useFiatAsMain ) ;
153+ } ;
154+
155+ const onChangeWrapper = useCallback (
156+ async ( e : React . ChangeEvent < HTMLInputElement > ) => {
157+ setInputValue ( e . target . value ) ;
158+
159+ if ( onChange ) {
160+ const value = Number ( e . target . value ) ;
161+ const { valueInSats, formattedSats, valueInFiat, formattedFiat } =
162+ await getValues ( value , useFiatAsMain ) ;
163+ const newEvent : DualCurrencyFieldChangeEvent = {
164+ ...e ,
165+ target : {
166+ ...e . target ,
167+ value : valueInSats . toString ( ) ,
168+ valueInFiat,
169+ formattedValueInFiat : formattedFiat ,
170+ valueInSats,
171+ formattedValueInSats : formattedSats ,
172+ } ,
173+ } ;
174+ onChange ( newEvent ) ;
175+ }
176+ } ,
177+ [ onChange , useFiatAsMain , getValues ]
178+ ) ;
179+
180+ // default to fiat when account currency is set to anything other than BTC
181+ useEffect ( ( ) => {
182+ if ( ! initialized . current ) {
183+ setUseFiatAsMain ( ! ! ( account ?. currency && account ?. currency !== "BTC" ) ) ;
184+ initialized . current = true ;
185+ }
186+ } , [ account ?. currency , setUseFiatAsMain ] ) ;
187+
45188 const inputNode = (
46189 < input
47190 ref = { inputEl }
@@ -57,15 +200,16 @@ export default function DualCurrencyField({
57200 required = { required }
58201 pattern = { pattern }
59202 title = { title }
60- onChange = { onChange }
203+ onChange = { onChangeWrapper }
61204 onFocus = { onFocus }
62205 onBlur = { onBlur }
63- value = { value }
206+ value = { inputValue }
64207 autoFocus = { autoFocus }
65208 autoComplete = { autoComplete }
66209 disabled = { disabled }
67- min = { min }
68- max = { max }
210+ min = { minValue }
211+ max = { maxValue }
212+ step = { useFiatAsMain ? "0.01" : "1" }
69213 />
70214 ) ;
71215
@@ -90,14 +234,15 @@ export default function DualCurrencyField({
90234 >
91235 { label }
92236 </ label >
93- { ( min || max ) && (
237+ { ( minValue || maxValue ) && (
94238 < span
95239 className = { classNames (
96240 "text-xs text-gray-700 dark:text-neutral-400" ,
97241 ! ! rangeExceeded && "text-red-500 dark:text-red-500"
98242 ) }
99243 >
100- < RangeLabel min = { min } max = { max } /> { tCommon ( "sats_other" ) }
244+ < RangeLabel min = { minValue } max = { maxValue } /> { " " }
245+ { useFiatAsMain ? "" : tCommon ( "sats_other" ) }
101246 </ span >
102247 ) }
103248 </ div >
@@ -114,9 +259,9 @@ export default function DualCurrencyField({
114259 >
115260 { inputNode }
116261
117- { ! ! fiatValue && (
118- < p className = "helper text-gray-500 z-1 pointer-events-none" >
119- ~{ fiatValue }
262+ { ! ! altFormattedValue && (
263+ < p className = "helper text-gray-500 z-1" onClick = { swapCurrencies } >
264+ ~{ altFormattedValue }
120265 </ p >
121266 ) }
122267
0 commit comments