1- import { Modal , SkeletonLoading } from "@namada/components" ;
2- import { chainAssetsMapAtom , nativeTokenAddressAtom } from "atoms/chain" ;
31import {
4- gasPriceForAllTokensAtom ,
5- storageGasTokenAtom ,
6- } from "atoms/fees/atoms" ;
2+ ActionButton ,
3+ AmountInput ,
4+ Modal ,
5+ StyledSelectBox ,
6+ } from "@namada/components" ;
7+ import { chainAssetsMapAtom , nativeTokenAddressAtom } from "atoms/chain" ;
8+ import { GasPriceTable , GasPriceTableItem } from "atoms/fees/atoms" ;
79import { tokenPricesFamily } from "atoms/prices/atoms" ;
810import BigNumber from "bignumber.js" ;
9- import { useAtomValue , useSetAtom } from "jotai" ;
11+ import { TransactionFeeProps } from "hooks/useTransactionFee" ;
12+ import { useAtomValue } from "jotai" ;
1013import { IoClose } from "react-icons/io5" ;
1114import { twMerge } from "tailwind-merge" ;
1215import { GasConfig } from "types" ;
@@ -15,22 +18,87 @@ import { getDisplayGasFee } from "utils/gas";
1518import { FiatCurrency } from "./FiatCurrency" ;
1619import { TokenCurrency } from "./TokenCurrency" ;
1720
18- export const GasFeeModal = ( {
21+ const useSortByNativeToken = ( ) => {
22+ const nativeToken = useAtomValue ( nativeTokenAddressAtom ) . data ;
23+ return ( a : GasPriceTableItem , b : GasPriceTableItem ) =>
24+ a . token === nativeToken ? - 1
25+ : b . token === nativeToken ? 1
26+ : 0 ;
27+ } ;
28+
29+ const useBuildGasOption = ( {
1930 gasConfig,
20- onClose ,
31+ gasPriceTable ,
2132} : {
2233 gasConfig : GasConfig ;
23- onClose : ( ) => void ;
24- } ) : JSX . Element => {
25- const setStorageGasToken = useSetAtom ( storageGasTokenAtom ) ;
26- const gasPriceForAllTokens = useAtomValue ( gasPriceForAllTokensAtom ) ;
34+ gasPriceTable : GasPriceTable | undefined ;
35+ } ) => {
2736 const chainAssetsMap = useAtomValue ( chainAssetsMapAtom ) ;
28- const nativeTokenAddress = useAtomValue ( nativeTokenAddressAtom ) . data ;
37+ const gasDollarMap =
38+ useAtomValue (
39+ tokenPricesFamily ( gasPriceTable ?. map ( ( item ) => item . token ) ?? [ ] )
40+ ) . data ?? { } ;
2941
30- const data = gasPriceForAllTokens . data ?? [ ] ;
42+ return (
43+ override : Partial < GasConfig >
44+ ) : {
45+ option : GasConfig ;
46+ selected : boolean ;
47+ disabled : boolean ;
48+ symbol : string ;
49+ displayAmount : BigNumber ;
50+ dollar ?: BigNumber ;
51+ } => {
52+ const option : GasConfig = {
53+ ...gasConfig ,
54+ ...override ,
55+ } ;
56+
57+ const displayAmount = getDisplayGasFee ( option ) ;
58+ const price = gasDollarMap [ option . gasToken ] ;
59+ const dollar = price ? price . multipliedBy ( displayAmount ) : undefined ;
60+
61+ const selected =
62+ ! gasConfig . gasLimit . isEqualTo ( 0 ) &&
63+ option . gasLimit . isEqualTo ( gasConfig . gasLimit ) &&
64+ option . gasPrice . isEqualTo ( gasConfig . gasPrice ) &&
65+ option . gasToken === gasConfig . gasToken ;
66+
67+ const disabled =
68+ gasConfig . gasLimit . isEqualTo ( 0 ) || gasConfig . gasPrice . isEqualTo ( 0 ) ;
3169
32- const tokenAddresses = data . map ( ( item ) => item . token ) ;
33- const gasDollarMap = useAtomValue ( tokenPricesFamily ( tokenAddresses ) ) ;
70+ const asset =
71+ chainAssetsMap [ option . gasToken ] ?? unknownAsset ( option . gasToken ) ;
72+ const symbol = asset . symbol ;
73+
74+ return {
75+ option,
76+ selected,
77+ disabled,
78+ symbol,
79+ displayAmount,
80+ dollar,
81+ } ;
82+ } ;
83+ } ;
84+
85+ export const GasFeeModal = ( {
86+ feeProps,
87+ onClose,
88+ } : {
89+ feeProps : TransactionFeeProps ;
90+ onClose : ( ) => void ;
91+ } ) : JSX . Element => {
92+ const {
93+ gasConfig,
94+ gasEstimate,
95+ gasPriceTable,
96+ onChangeGasLimit,
97+ onChangeGasToken,
98+ } = feeProps ;
99+
100+ const sortByNativeToken = useSortByNativeToken ( ) ;
101+ const buildGasOption = useBuildGasOption ( { gasConfig, gasPriceTable } ) ;
34102
35103 return (
36104 < Modal onClose = { onClose } >
@@ -49,61 +117,114 @@ export const GasFeeModal = ({
49117 >
50118 < IoClose />
51119 </ i >
52- < div className = "text-center" >
53- < h2 className = "font-medium" > Select Gas Token</ h2 >
54- < div className = "text-sm mt-1" >
55- Gas fees deducted from your Namada accounts
56- </ div >
120+
121+ < h2 className = "text-xl font-medium" > Fee Options</ h2 >
122+ < div className = "text-sm" >
123+ Gas fees deducted from your Namada accounts
57124 </ div >
58- < div className = "flex flex-col mt-4 max-h-[60vh] overflow-auto" >
59- { ! data . length ?
60- < SkeletonLoading height = "100px" width = "100%" />
61- : data
62- . sort ( ( a , b ) =>
63- a . token === nativeTokenAddress ? - 1
64- : b . token === nativeTokenAddress ? 1
65- : 0
66- )
67- . map ( ( { token, minDenomAmount } ) => {
68- const asset = chainAssetsMap [ token ] ?? unknownAsset ( token ) ;
69- const symbol = asset . symbol ;
70- const fee = getDisplayGasFee ( {
71- gasLimit : gasConfig . gasLimit ,
72- gasPrice : BigNumber ( minDenomAmount ) ,
73- gasToken : token ,
74- asset,
75- } ) ;
76- const price = gasDollarMap . data ?. [ token ] ;
77- const dollar = price ? fee . multipliedBy ( price ) : undefined ;
78-
79- const selected = token === gasConfig . gasToken ;
80-
81- return (
82- < button
83- key = { token }
84- className = { twMerge (
85- "flex justify-between items-center" ,
86- "bg-rblack rounded-sm px-5 min-h-[58px]" ,
87- "hover:text-yellow hover:border-yellow transition-colors duration-300" ,
88- selected ? "border border-white" : "m-px"
89- ) }
90- type = "button"
91- onClick = { ( ) => {
92- setStorageGasToken ( token ) ;
93- onClose ( ) ;
94- } }
95- >
96- < div > { symbol } </ div >
125+
126+ < div className = "text-sm mt-8 mb-1" > Fee</ div >
127+ < div className = "grid grid-cols-3 rounded-sm overflow-hidden" >
128+ { [
129+ { label : "Low" , amount : gasEstimate ?. min ?? 0 } ,
130+ { label : "Average" , amount : gasEstimate ?. avg ?? 0 } ,
131+ { label : "High" , amount : gasEstimate ?. max ?? 0 } ,
132+ ] . map ( ( item ) => {
133+ const { symbol, displayAmount, dollar, selected, disabled } =
134+ buildGasOption ( {
135+ gasLimit : BigNumber ( item . amount ) ,
136+ } ) ;
137+
138+ return (
139+ < button
140+ key = { item . label }
141+ type = "button"
142+ disabled = { disabled }
143+ className = { twMerge (
144+ "flex flex-col justify-center items-center flex-1 py-5 leading-4" ,
145+ "transition-colors duration-150 ease-out-quad" ,
146+ selected ?
147+ "cursor-auto bg-yellow text-black"
148+ : "cursor-pointer bg-neutral-800 hover:bg-neutral-700"
149+ ) }
150+ onClick = { ( ) => onChangeGasLimit ( BigNumber ( item . amount ) ) }
151+ >
152+ < div className = "font-semibold" > { item . label } </ div >
153+ { dollar && (
154+ < FiatCurrency
155+ amount = { dollar }
156+ className = "text-xs text-neutral-500 font-medium"
157+ />
158+ ) }
159+ < TokenCurrency
160+ amount = { displayAmount }
161+ symbol = { symbol }
162+ className = "font-semibold mt-1"
163+ />
164+ </ button >
165+ ) ;
166+ } ) }
167+ </ div >
168+
169+ < div className = "text-sm mt-4 mb-1" > Fee Token</ div >
170+ < StyledSelectBox
171+ id = "fee-token-select"
172+ value = { gasConfig . gasToken }
173+ containerProps = { {
174+ className : twMerge (
175+ "text-sm w-full flex-1 border border-white rounded-sm" ,
176+ "px-4 py-[9px]"
177+ ) ,
178+ } }
179+ arrowContainerProps = { { className : "right-4" } }
180+ listContainerProps = { { className : "w-full mt-2 border border-white" } }
181+ listItemProps = { { className : "border-0 px-2 -mx-2 rounded-sm" } }
182+ onChange = { ( e ) => onChangeGasToken ( e . target . value ) }
183+ options = {
184+ gasPriceTable ?. sort ( sortByNativeToken ) . map ( ( item ) => {
185+ const { symbol, displayAmount, dollar } = buildGasOption ( {
186+ gasPrice : item . gasPrice ,
187+ gasToken : item . token ,
188+ } ) ;
189+ return {
190+ id : item . token ,
191+ value : (
192+ < div className = "flex items-center justify-between w-full min-h-[42px] mr-5" >
193+ < div className = "text-base" > { symbol } </ div >
97194 < div className = "text-right" >
98195 { dollar && < FiatCurrency amount = { dollar } /> }
99196 < div className = "text-xs" >
100- < TokenCurrency amount = { fee } symbol = { symbol } />
197+ < TokenCurrency amount = { displayAmount } symbol = { symbol } />
101198 </ div >
102199 </ div >
103- </ button >
104- ) ;
105- } )
200+ </ div >
201+ ) ,
202+ ariaLabel : symbol ,
203+ } ;
204+ } ) ?? [ ]
106205 }
206+ />
207+
208+ < div className = "mt-4" >
209+ < AmountInput
210+ label = "Gas Amount"
211+ value = { gasConfig . gasLimit }
212+ onChange = { ( e ) => e . target . value && onChangeGasLimit ( e . target . value ) }
213+ />
214+ </ div >
215+
216+ < div className = "mt-8" >
217+ < ActionButton
218+ size = "sm"
219+ className = "max-w-[200px] mx-auto"
220+ backgroundColor = "gray"
221+ backgroundHoverColor = "yellow"
222+ textColor = "white"
223+ textHoverColor = "black"
224+ onClick = { onClose }
225+ >
226+ Close
227+ </ ActionButton >
107228 </ div >
108229 </ div >
109230 </ Modal >
0 commit comments