LaX+TJpnv`z>h~dbTK*dDD)Mt5{>a%Hbz2 zIgdX!CHkHOvI^Rq7#JB>Ch=tau;d%I%JLwCFMz7H#jz(!$+MWYLIR2oGmHq^(5-t& zHQ(`ID7jd~q*Kdx{Dwa-$bj69iV9^$);FSm7ydObH@8JUqD|n##S;X-^?Rd|uJ2=R7l(7_ zy(>{q)1q$FnvVT(EE`GNs2~ KKibB%Uy(|JesT`ddvkVAlg(G!RO6oIeu{ zg%IwJIj)^Ujh)QO-JkW?UmZd)gIM~tpC@p?+L|HVEI7Sr>=F`o_H_Sp>zpLCkg}$_ zXt|022(@U*Tnl2P*+PKR&ZrP0RIfME59Ysrqx$WPLdM~t9R8L-$+>4|hejNITxE `-az)f;LIM}jpZog ^DH?0ZsQ4K7^C WT{vin!%N5YjJM?Y1ehQdET_$c2d zs_`FEsT*@uSE*_Fmh@iBcXYlWR^JO}IHEFr-~@{mxkTvIx+V0#D_Tw`N1<$5>H2}T zH pZCPtr?HdeR6^7{)ad|k}F%chL$6PMB5RSiMA9A&Y`OJvs9R9QN7@@&k zetB-jTcmEYpxRLK*B~+1!{rp`^B!!Q(A=ZsmV9fUfuSmuPcLrdTA@#vtxDd%#~3L; zgTO?>2s=glzRwj`Ya*1|3#t#}D_i1=e~7vVl|!H~_w0+%+}s|thR~|HSE8*M&{4A5 z-Qz57gj*?oiADMRp0>`*UqZx1%|FigT1?#BG`8HO4u4lvxHmLX$CSRVyPyc9=@mOf z>suP>NVos8t5n{y!k>25jYJnz`&21S>6#Fnc1@>x-HGuYj2`186^04qlO1KePzaQ+ zf!@YBS^0t1-;TI7UkdiMu|2H|*L>^cb6RP$m_`nLnZG1SG%8Qe@Q^x{FYj^tCNQ01 zP8R9jpnvU>@lEq!+-_apcS(~D1rEU*Ot-*1;OjV*5W2=>_*bPA?iY}D3|->CER3)) zk<77-EYr%kLWkM63>392{V1+n!n+$TdN6k6H(JNhu}AU32Y;`8u>w{(@q83fuN9f4 zJyW5>^zaXI<>)Z!SQb-jAED1XMU^E7g)(MNp(rO;eqc8^TPnG@8>(5&bjAcM_`k1& z@i$nT&%4e-r_wRB(#rKOHMzWnd5MRw)lt)}&->)j _Ja-vl{Mu{jVaH{@nbr||LKE5xVr*O-q^y$##& zVUA^Y@K;KEt&Oy#OA=wRm)n WITLScSkKaTd4t=>wJ{Q+8r8b^yvIB a|ivT}a7#>_H)R(_m{KM7`@w&%!WY oEy-zMo0HEw6ic>#Lr4>dA1Hp*{J4C_sQeemILeyNMf%ll zSe+Nn>RAXiU|h@LU%Um`d-`rRZQE&xnA!Y_lp Lr2fr!dg~`Ku<<(~Z)77+4m`Wpt1Eo^E!?2Z)%Th75YH&$6wCC$ zcwr;_e=%j|YL!fQm5bKIil(8-t7j73j}JqqMvc}wSRj%8`hO_jGvoKMD1XTUI)L&{ z_F($#3ht ^)Gn4hYEMcgbDaxC5!kVk29U=qE9EmYz?Q_;a0=goZj)=hYiwUN&0c+#YIG>)5{1 zOL@1u*S1vu%C53u*WWUXeCQmXxBzzAfh_rD{pr2L!?&}f$ksjyv%OeRWro*qY#ml* z`ZzQr)$p1isBCMeNaS-YUUqWU|01&BN7yY^v7Do=ISvt-qMNiDG(DARIPyq23N{+d zZ7sI?*sR9XtzUxk5T5_p`Hqq(#Ss &&1~8LH)3c?(7Ug9;2_KL%RG*Kw(_z_)2-fo zB1P`)8L}UObnc8f>bX%uw!a<+jqE|Z!CcmL7E-d^JE47c&uQQ6p^rcWYR<|3{=iiK zeiwaA%;(WomrKGMm~G+G+iuEoWFCi|{O7WLSnF;Xntc5{vHU8W R7D8HgXV?vDZLiU{dvsvvyE0hjGRRGnq{92eY7T(cDc zsV1OC9njL~p!~MAd>j9TCB0efZJk!ezRDff$|eM9o#+^7%|Qwl;C2u2-NCB#U?}#H zr@zhjHyi_2M=_#LHhS%i`OS~$(!b0etn4f?7%^y2m4=bd)Y{z62j(?dqYs;!Of?vo z=Q*0p0jPZ;l_wY~@08P1{PHUwXz1tAgGyI}z=SNdSGok?Y`H4H@-S4eb)xlghj$=# z_*)(Az3P<*14eKj4h#M!2P;O=V!kDlwDpidHTuR1q*yPV_^0{RG!J4f6EQ6JVA0jS z@)-D5D)3SYz{z#~x1Wv|;jkB0HHWaOT9pjzf^SQm4Hp59lN2$XWu<}m@_5@gF%tj0 zW*~OWm?mer2qPVV#$<)!PUY^-eZ}K2oe%hyBH?`dK?Dmy%Uxbyk4;SYe?=<~m)Nu5 zZK}}=BzO&-G5*LJb^gB9&Lu`fT@leF9=K@?2nRrNdpqGimpV~?gISK3T@7+Geq|{& zJ7>v%0GucUDD6k`%Y1a;a~Im~U7KGTn=B{wlVQLFJlEKT-5#k^>&d1Eom&Z}@OABs z 5Ji{>EA@vMCJkq8q~va>KBCSvqsY#hb#{=fa a9GlhPR`8^NP|ZY%k3JK(@J eaR3}|~j{?T~p6f%0n?}RuP9?YR;S}o4Df^k$OVChP-c+Tb^?A&t z)NFq>A#P>woR)g*k5p?SwdeegctE&Z8#T-aixQ1$C;ZO@w8BnN9!UTzDNJ%lJK^Ht z`SSmHJ?c&5McZ(C^OOM~ceJB~;5@hyBNnHjB*>nAMb}n&wy*%lM@VizRMei&lU<>L z^K*Y ycsB)Cs|n4b}0?7w51bBQO)Ma}q$ir%2>-*iE2Svk9|RY5QS>1dY=Vjxi7{tNKtP zMBSEK4I&%jHb!o|0)&OETpz!))u=tZ!;D_IML*)euT3w3S_JiT*fr|+eaDZlXN_*D zB;4ti`C*vcMbt@y1Kn&{eo}-Zo)nzo=QzYvgY;(n;s*0nGzT!az>nLsg~7Yb`MqzL zr+5%8b~urlcQctWP%C2=A$dT<{!fLfb1i9 N%vICV^e&?!9@r0AC3lQ&zaAHzG564gS1w(K~Z9_m^~5wlg4m;!Etx_e>w0 z)kHL!@qcGR2_~{>Os;Au%N>w~(pLmh{7}&`uwMe)U1e42Oh25UTw)g;8}&Nd8&F~h z&lkHbleC2q!9TJrkuoj@E)%Yz(P$9m!k&r+qPssOKXI7w=cM>Th~4Hsv9x!s0R4mq zoDdG}65v+PD8i0z`0U`DLTI86Gxa0!_CI-tk>G+*T#wB(8-e@}2o4^VDVy9H3Wr?M zZASRZr%emjVF6b-xQqvvVtXCPJ@kz^EGi~cQfaA5Apd{#^lbk%0q)-=%tO$28n4hk z%6Kq6hgratU05HQq(9>@I=9=Rb!)wmxvjmm4+DFlHOnYZGAp-Gp0EXEI+$l8)UV&O zI*{$ZF=ju3uD-XplTZ83y!)LCMD^S|Vl4vo6%U|g>-XNiwE%I9b-a_S?4}Psg*xRb z4&tuQH`lYa1y5#;x~5w0p=lSLN4i|jM@@Os1CSj!)c5S8v-h8UXxA}Fm^57?|H#@1 z_YwkJzKOe(P6rhstm)al_|(O4!Z8n`aALML@JE4x1o$T;DkO}R@hAE%)P`I(Yh=WL zSAXkUpXw}3x(0DXDLmv7ymJPqcKpi KtFXX +kq!lh)uvk`#Od$gPNC8}S2bgQP7vk! z_>3S40~t)d`Tq>&`N^m-<_n}C$QWwP?RN=*>KLmct~M2+3=g6%6H%=_3@m$To??A~ zaJFgrRxN_(qY=?Z{65!JO-9D9n7U&fb0&ActeD^6jopV*g=)KRl-uF}?Asl8t(MmR zQW-oSbjE$}oP}2J4ag|y4irY25sLxl>(Di(CpE8GVJUF1tao9~^=%!IJIzuQlwK7= z{`Zgo!0@XhYvd(1UoRea-cj$Fk7O->7Y-PZIaXD&wrv>(tUkC4B2@v{uBmMeES ?){13Nc?2|pZP0hq@YGJYLl6KGC!W#}L#w^qaMCdh5)k-Twq;%b zGA9xS^p7bshM;=_`=R*^VBy3P6!0Qo0FWMESz>_^!l8vzE-N!CpnC!ch^6#-T@@Ay ze2$<1IJmsM6%4Gv-YigarYZ(-IhZXhd%jAUVgdSxES4GrtNfr#5msgk^#@+rmH@DN zu;G^HE; oi1j^yrtzcK(Qv~O~^nK+$CwG99`HM4t`VV6C%0!XJ}z^1O=;Jq^A z*}AjR8C4K*+8=;=W9qh|$nTrwj#nZCTVVEAiT<;C26T2wK=Kn1V|z+lgBSzsFtUa1 z0$+>(@X;1wHUyrp8BgN90?u1Ql=YS?v_FeeJuU|)0=Wn+2|5z<*I>ZCVR2%m6)Q;c zS2o%8`ck~&Qj3IH@zh=oE@(Y7Zpj5F!B2|<5Ny?y?-m6(x)LyLdADF7b@Bp~iwCQ- zl8>4ZJttmF;A!?q0Jr;D>74RqaI33Q>C_267~xeM24Lj(QKMc1vJF1xaN^6{Aze#A zvijUP!j1&{$mcPH6Gg)#vY~axi#Ju^AgJvipxBrFuG5&h*R?xD0HbmDi)#jdYAE$V z1Hu N6kuBSJf}Vw4*1i=d<)KhiL4vZwE>4TwE^L8 z#A-egPU0XR1!Rfo)%< FV7uhq|K8QF^o z$<1e>OhV2sUxB)^ZG?ty2Myj*Dm>PJoW}_RvfIG4{rs2mdrq4uiC2L9r%1K!zw6B} zcl-bW782PI=;)l*o 1}FCH?xRT)5yofev@^|*U=x&d&`FUK$T%jB2z+_#j^~2`X*D7k6`iFS z0~k1 +p#n{>_O3sS1`&0W!f|h2Ff&W;N0sVtPga; z+KNH~&E;ilfCSURen5Y}h_4OwJVFNn6HFkRVCn_Y_%qPexvQtof(c5!!RmIEK;z6s zWm-Mt_79W&NCm41C)-P4{YA4+J=A3WaAOZcE`A;Sb{4p|3Y7Q8O JjY zEHv=JyIuLpQ(z1>eE6WdkuOq|K~uYY)!EgDQudpx%PCq&aH2Ww=6~W>3gMmxB>kh7 zyFCiGMP3JPzM0fCK>_`;Q^k53V&Cq`nZUO5c8XnZe|m?N#sSjJ$wK(#vvm#%=Rv34 z>wLPa#b^l9C? ehBnD=&` &URKD04ll6LO{f4nY>>(CRSJ>LGomZuJYRC2!>Zd~E3b`0gWS`0;(>XAfK zb=qFiuVIVPfB;5y7xhQ}ghR-&w18*ze0&)@w)kWB!|6Hi zzwseaq4sDowf2*^P{N;Nw$~6UM`d)oXTbb*{o$|1+k^WN+*}%xhmV?j{kE0Z$ 620p+5^PWAm@ETA;=|ze}qrJmIgk;jHbCd%UOqR4Mdq;?E>RVFwUF zr)PS82JNm^MXUUw+Bb~2GUS{VKFy32iqq}eBeX>xD_uMq>R34p+oBT@luHQ$X9fKL P^iTWpHTAqpR{sA7!= = ({ const transactionInfo = useFetchIpfsJson(transactionUri); const { isList } = useIsList(); const nativeTokenSymbol = useNativeTokenSymbol(); - const { erc20TokenSymbol } = useERC20TokenSymbol(token); + const { tokenMetadata } = useTokenMetadata(token); + const erc20TokenSymbol = tokenMetadata?.symbol; const title = transactionInfo?.title; const navigateAndScrollTop = useNavigateAndScrollTop(); diff --git a/web/src/hooks/useFilteredTokens.ts b/web/src/hooks/useFilteredTokens.ts new file mode 100644 index 0000000..7e03a07 --- /dev/null +++ b/web/src/hooks/useFilteredTokens.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from "react"; +import { useTokenMetadata } from "./useTokenMetadata"; +import EthTokenIcon from "svgs/icons/eth-token-icon.png"; + +export const useFilteredTokens = (searchQuery: string, tokens, setTokens, sendingToken) => { + const { tokenMetadata } = useTokenMetadata( + searchQuery.startsWith("0x") && searchQuery.length === 42 ? searchQuery : null + ); + const [filteredTokens, setFilteredTokens] = useState([]); + + useEffect(() => { + const handleSearch = async () => { + let filtered = []; + + if (!searchQuery) { + filtered = tokens; + if (sendingToken) { + filtered = [sendingToken, ...tokens.filter((token) => token.address !== sendingToken.address)]; + } + } else if (tokenMetadata) { + const resultToken = { + symbol: tokenMetadata.symbol, + address: searchQuery.toLowerCase(), + logo: tokenMetadata.logo || EthTokenIcon, + }; + + const updatedTokens = [...tokens, resultToken]; + const uniqueTokens = Array.from(new Set(updatedTokens.map((a) => a.address))).map((address) => { + return updatedTokens.find((a) => a.address === address); + }); + + filtered = [resultToken]; + setTokens(uniqueTokens); + localStorage.setItem("tokens", JSON.stringify(uniqueTokens)); + } else { + filtered = tokens.filter((token) => token.symbol.toLowerCase().includes(searchQuery.toLowerCase())); + } + + setFilteredTokens(filtered); + }; + + handleSearch(); + }, [searchQuery, tokens, setTokens, sendingToken, tokenMetadata]); + + return { filteredTokens }; +}; diff --git a/web/src/hooks/useERC20TokenSymbol.ts b/web/src/hooks/useTokenMetadata.ts similarity index 56% rename from web/src/hooks/useERC20TokenSymbol.ts rename to web/src/hooks/useTokenMetadata.ts index deca518..878d882 100644 --- a/web/src/hooks/useERC20TokenSymbol.ts +++ b/web/src/hooks/useTokenMetadata.ts @@ -3,25 +3,25 @@ import { Alchemy } from "alchemy-sdk"; import { useNetwork } from "wagmi"; import { alchemyConfig } from "utils/alchemyConfig"; -export const useERC20TokenSymbol = (tokenAddress: string) => { +export const useTokenMetadata = (tokenAddress: string) => { const { chain } = useNetwork(); - const [erc20TokenSymbol, setErc20TokenSymbol] = useState (null); + const [tokenMetadata, setTokenMetadata] = useState(null); useEffect(() => { - const fetchTokenSymbol = async () => { + const fetchTokenMetadata = async () => { if (!tokenAddress || tokenAddress === "native") return; const alchemy = new Alchemy(alchemyConfig(chain?.id)); try { const metadata = await alchemy.core.getTokenMetadata(tokenAddress); - setErc20TokenSymbol(metadata.symbol || null); + setTokenMetadata(metadata); } catch (error) { - console.error("Error fetching token symbol:", error); - setErc20TokenSymbol(null); + console.error("Error fetching token metadata:", error); + setTokenMetadata(null); } }; - fetchTokenSymbol(); + fetchTokenMetadata(); }, [tokenAddress, chain?.id]); - return { erc20TokenSymbol }; + return { tokenMetadata }; }; diff --git a/web/src/pages/MyTransactions/TransactionDetails/index.tsx b/web/src/pages/MyTransactions/TransactionDetails/index.tsx index 5a2b751..f718fe5 100644 --- a/web/src/pages/MyTransactions/TransactionDetails/index.tsx +++ b/web/src/pages/MyTransactions/TransactionDetails/index.tsx @@ -12,8 +12,8 @@ import { useEscrowParametersQuery } from "queries/useEscrowParametersQuery"; import { useTransactionDetailsQuery } from "queries/useTransactionsQuery"; import { useArbitrationCost } from "queries/useArbitrationCostFromKlerosCore"; import { useNativeTokenSymbol } from "hooks/useNativeTokenSymbol"; -import { useERC20TokenSymbol } from "hooks/useERC20TokenSymbol"; import useFetchIpfsJson from "hooks/useFetchIpfsJson"; +import { useTokenMetadata } from "hooks/useTokenMetadata"; const Container = styled.div``; @@ -33,7 +33,8 @@ const TransactionDetails: React.FC = () => { const { data: escrowParameters } = useEscrowParametersQuery(); const { arbitrationCost } = useArbitrationCost(escrowParameters?.escrowParameters?.arbitratorExtraData); const nativeTokenSymbol = useNativeTokenSymbol(); - const { erc20TokenSymbol } = useERC20TokenSymbol(transactionDetails?.escrow?.token); + const { tokenMetadata } = useTokenMetadata(transactionDetails?.escrow?.token); + const erc20TokenSymbol = tokenMetadata?.symbol; const { setTransactionDetails } = useTransactionDetailsContext(); const { diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/DropdownButton.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/DropdownButton.tsx new file mode 100644 index 0000000..1b4e654 --- /dev/null +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/DropdownButton.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import styled from "styled-components"; +import Skeleton from "react-loading-skeleton"; + +const Container = styled.div` + border: 1px solid ${({ theme }) => theme.stroke}; + border-radius: 3px; + width: 186px; + height: 45px; + position: relative; + padding: 9.5px 14px; + cursor: pointer; + background: ${({ theme }) => theme.whiteBackground}; + color: ${({ theme }) => theme.primaryText}; + display: flex; + align-items: center; + justify-content: space-between; +`; + +const DropdownArrow = styled.span` + border: solid ${({ theme }) => theme.stroke}; + border-width: 0 1px 1px 0; + display: inline-block; + padding: 3px; + transform: rotate(45deg); + margin-left: 8px; +`; + +const TokenLogo = styled.img` + width: 24px; + height: 24px; +`; + +const DropdownContent = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const LogoSkeleton = styled(Skeleton)` + width: 24px; + height: 24px; + border-radius: 50%; + margin-bottom: 2px; +`; + +const SymbolSkeleton = styled(Skeleton)` + width: 40px; + height: 16px; +`; + +export const DropdownButton = ({ loading, sendingToken, onClick }) => ( + + +); diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx new file mode 100644 index 0000000..2eefaca --- /dev/null +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import styled from "styled-components"; + +const Container = styled.div<{ selected: boolean }>` + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + cursor: pointer; + background: ${({ theme, selected }) => (selected ? theme.mediumBlue : "transparent")}; + border-left: ${({ theme, selected }) => (selected ? `3px solid ${theme.primaryBlue}` : "none")}; + padding-left: ${({ selected }) => (selected ? "13px" : "16px")}; + + &:hover { + background: ${({ theme }) => theme.lightBlue}; + } +`; + +const TokenLogo = styled.img` + width: 24px; + height: 24px; +`; + +const TokenLabel = styled.span` + color: ${({ theme }) => theme.primaryText}; +`; + +const TokenItem = ({ token, selected, onSelect }) => ( ++ {loading ? ( + ++ ) : ( + sendingToken && + )} + {loading ? : sendingToken?.symbol} + + onSelect(token)}> + +); + +export default TokenItem; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx new file mode 100644 index 0000000..e1cd412 --- /dev/null +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx @@ -0,0 +1,68 @@ +import React, { useRef, useState } from "react"; +import styled from "styled-components"; +import { useClickAway } from "react-use"; +import { Searchbar } from "@kleros/ui-components-library"; +import { useNewTransactionContext } from "context/NewTransactionContext"; +import { Overlay } from "components/Overlay"; +import TokenItem from "./TokenItem"; +import { StyledModal } from "pages/MyTransactions/Modal/StyledModal"; +import { useFilteredTokens } from "hooks/useFilteredTokens"; + +const ReStyledModal = styled(StyledModal)` + display: flex; + width: 500px; +`; + +const StyledSearchbar = styled(Searchbar)` + width: 100%; + input { + font-size: 16px; + } +`; + +const ItemsContainer = styled.div` + display: flex; + width: 100%; + flex-direction: column; + margin-top: 24px; +`; + +const StyledP = styled.p` + display: flex; + align-self: flex-start; + font-weight: 600; + margin: 0; + margin-bottom: 28px; +`; + +export const TokenListModal = ({ setIsOpen, tokens, setTokens, handleSelectToken }) => { + const [searchQuery, setSearchQuery] = useState(""); + const { sendingToken } = useNewTransactionContext(); + const { filteredTokens } = useFilteredTokens(searchQuery, tokens, setTokens, sendingToken); + const containerRef = useRef(null); + useClickAway(containerRef, () => setIsOpen(false)); + + return ( + <> ++ {token.symbol} ++ + + > + ); +}; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx index a799195..feae307 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx @@ -1,129 +1,28 @@ import React, { useState, useEffect, useRef } from "react"; import styled from "styled-components"; -import Skeleton from "react-loading-skeleton"; import { useClickAway } from "react-use"; -import { Searchbar } from "@kleros/ui-components-library"; import { Alchemy } from "alchemy-sdk"; import { useAccount, useNetwork } from "wagmi"; import { useNewTransactionContext } from "context/NewTransactionContext"; import { initializeTokens } from "utils/initializeTokens"; import { alchemyConfig } from "utils/alchemyConfig"; -import { Overlay } from "components/Overlay"; -import { StyledModal } from "pages/MyTransactions/Modal/StyledModal"; import { useLocalStorage } from "hooks/useLocalStorage"; +import { DropdownButton } from "./DropdownButton"; +import { TokenListModal } from "./TokenListModal"; const Container = styled.div` - position: relative; - width: 186px; - height: 45px; -`; - -const TokenSelectorWrapper = styled.div` display: flex; flex-direction: column; gap: 8px; position: relative; `; -const DropdownButton = styled.div` - border: 1px solid ${({ theme }) => theme.stroke}; - border-radius: 3px; - padding: 9.5px 8px; - cursor: pointer; - background: ${({ theme }) => theme.whiteBackground}; - color: ${({ theme }) => theme.primaryText}; - display: flex; - align-items: center; - justify-content: space-between; -`; - -const DropdownArrow = styled.span` - border: solid ${({ theme }) => theme.stroke}; - border-width: 0 1px 1px 0; - display: inline-block; - padding: 3px; - transform: rotate(45deg); - margin-left: 8px; -`; - -export const Item = styled.div<{ selected: boolean }>` - display: flex; - align-items: center; - gap: 8px; - padding: 10px 16px; - cursor: pointer; - &:hover { - background: ${({ theme }) => theme.lightBlue}; - } - ${({ selected, theme }) => - selected && - ` - background: ${theme.mediumBlue}; - border-left: 3px solid ${theme.primaryBlue}; - padding-left: 13px; - &:hover { - background: ${theme.mediumBlue}; - } - `} -`; - -export const TokenLogo = styled.img` - width: 24px; - height: 24px; -`; - -export const TokenLabel = styled.span` - color: ${({ theme }) => theme.primaryText}; -`; - -const DropdownContent = styled.div` - display: flex; - align-items: center; - gap: 8px; -`; - -const StyledLogoSkeleton = styled(Skeleton)` - width: 22.5px; - height: 22.5px; - margin-left: 2px; - border-radius: 50%; -`; - -const ReStyledModal = styled(StyledModal)` - display: flex; - width: 500px; -`; - -const StyledSearchbar = styled(Searchbar)` - width: 100%; - input { - font-size: 16px; - } -`; - -const ItemsContainer = styled.div` - display: flex; - width: 100%; - flex-direction: column; - margin-top: 24px; -`; - -const StyledP = styled.p` - display: flex; - align-self: flex-start; - font-weight: 600; - margin: 0; - margin-bottom: 28px; -`; - const TokenSelector = () => { const { address } = useAccount(); const { chain } = useNetwork(); const { sendingToken, setSendingToken } = useNewTransactionContext(); const [tokens, setTokens] = useLocalStorage("tokens", []); - const [filteredTokens, setFilteredTokens] = useState([]); const [isOpen, setIsOpen] = useState(false); - const [searchQuery, setSearchQuery] = useState(""); const containerRef = useRef(null); const [loading, setLoading] = useState(true); const alchemyInstance = new Alchemy(alchemyConfig(chain?.id)); @@ -148,86 +47,11 @@ const TokenSelector = () => { setIsOpen(false); }; - const handleSearch = async (query) => { - setSearchQuery(query); - - if (!query) { - setFilteredTokens(tokens); - return; - } - - const isAddress = query.startsWith("0x") && query.length === 42; - if (isAddress) { - try { - const metadata = await alchemyInstance.core.getTokenMetadata(query.toLowerCase()); - const resultToken = { - symbol: metadata.symbol, - address: query.toLowerCase(), - logo: metadata.logo || "https://via.placeholder.com/24", - }; - - const updatedTokens = [...tokens, resultToken]; - const uniqueTokens = Array.from(new Set(updatedTokens.map((a) => a.address))).map((address) => { - return updatedTokens.find((a) => a.address === address); - }); - - setFilteredTokens([resultToken]); - setTokens(uniqueTokens); - localStorage.setItem("tokens", JSON.stringify(uniqueTokens)); - } catch (error) { - console.error("Error fetching token info:", error); - } - } else { - const filteredTokens = tokens.filter((token) => token.symbol.toLowerCase().includes(query.toLowerCase())); - setFilteredTokens(filteredTokens); - } - }; - - const tokensToDisplay = searchQuery - ? filteredTokens - : [sendingToken, ...tokens.filter((token) => token.address !== sendingToken?.address)]; - return ( -Select a token +setSearchQuery(e.target.value)} + /> + + {filteredTokens.map((token) => ( + ++ ))} + - +- -setIsOpen(!isOpen)}> - - {isOpen && ( - <> -- {loading ? ( - -- ) : ( - sendingToken && - )} - {loading ? : sendingToken?.symbol} - - - - - > - )} -Select a token -handleSearch(e.target.value)} - /> - - {tokensToDisplay?.map((token) => ( - -- handleSelectToken(token)} - selected={sendingToken?.address === token.address} - > -
- ))} -- {token.symbol} -+ ); }; diff --git a/web/src/utils/fetchNativeToken.ts b/web/src/utils/fetchNativeToken.ts index 379bc46..fea5078 100644 --- a/web/src/utils/fetchNativeToken.ts +++ b/web/src/utils/fetchNativeToken.ts @@ -1,7 +1,9 @@ +import EthTokenIcon from "svgs/icons/eth-token-icon.png"; + export const fetchNativeToken = (chain) => { return { - symbol: chain?.nativeCurrency?.symbol || "Native Token", + symbol: chain?.nativeCurrency?.symbol, address: "native", - logo: "https://assets.coingecko.com/coins/images/279/thumb/ethereum.png", + logo: EthTokenIcon, }; }; diff --git a/web/src/utils/fetchTokenInfo.ts b/web/src/utils/fetchTokenInfo.ts index 9ae0643..47a31d4 100644 --- a/web/src/utils/fetchTokenInfo.ts +++ b/web/src/utils/fetchTokenInfo.ts @@ -1,14 +1,15 @@ import { Alchemy } from "alchemy-sdk"; +import EthTokenIcon from "svgs/icons/eth-token-icon.png"; export const fetchTokenInfo = async (address: string, alchemyInstance: Alchemy) => { try { const metadata = await alchemyInstance.core.getTokenMetadata(address); return { symbol: metadata.symbol?.toUpperCase() || "Unknown", - logo: metadata.logo || "https://via.placeholder.com/24", + logo: metadata.logo || EthTokenIcon, }; } catch (error) { console.error("Error fetching token info:", error); - return { symbol: "Unknown", logo: "https://via.placeholder.com/24" }; + return { symbol: "Unknown", logo: EthTokenIcon }; } }; From 5c6de4adf29021ba30f278ee1755048c8b0aeaab Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Mon, 27 May 2024 17:51:13 +0200 Subject: [PATCH 15/16] feat: add token balances in popup, styling, skeletons, maxbalance, itoken type --- web/src/context/NewTransactionContext.tsx | 18 +++-- web/src/hooks/useFilteredTokens.ts | 8 ++- .../TokenAndAmount/AmountField.tsx | 2 +- .../TokenAndAmount/MaxBalance.tsx | 68 +++++++++++++++++++ .../TokenSelector/TokenItem/Balance.tsx | 41 +++++++++++ .../{TokenItem.tsx => TokenItem/index.tsx} | 27 ++++++-- .../TokenAndAmount/TokenSelector/index.tsx | 2 +- .../TokenAndAmount/index.tsx | 38 +++++++++-- web/src/utils/fetchTokenInfo.ts | 4 +- web/src/utils/format.ts | 4 +- web/src/utils/getFormattedBalance.ts | 7 ++ web/src/utils/initializeTokens.ts | 5 +- 12 files changed, 196 insertions(+), 28 deletions(-) create mode 100644 web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/MaxBalance.tsx create mode 100644 web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/Balance.tsx rename web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/{TokenItem.tsx => TokenItem/index.tsx} (57%) create mode 100644 web/src/utils/getFormattedBalance.ts diff --git a/web/src/context/NewTransactionContext.tsx b/web/src/context/NewTransactionContext.tsx index 50a7579..81ae11d 100644 --- a/web/src/context/NewTransactionContext.tsx +++ b/web/src/context/NewTransactionContext.tsx @@ -1,5 +1,11 @@ import React, { createContext, useState, useContext, useEffect } from "react"; +export interface IToken { + symbol: string; + address: string; + logo: string; +} + interface INewTransactionContext { escrowType: string; setEscrowType: (type: string) => void; @@ -23,8 +29,8 @@ interface INewTransactionContext { setSellerAddress: (address: string) => void; sendingQuantity: string; setSendingQuantity: (quantity: string) => void; - sendingToken: { address: string; symbol: string }; - setSendingToken: (token: { address: string; symbol: string }) => void; + sendingToken: IToken; + setSendingToken: (token: IToken) => void; buyerAddress: string; setBuyerAddress: (address: string) => void; deadline: string; @@ -61,7 +67,7 @@ const NewTransactionContext = createContextsetIsOpen(!isOpen)} /> + {isOpen && } + ({ setSellerAddress: () => {}, sendingQuantity: "", setSendingQuantity: () => {}, - sendingToken: { address: "", symbol: "" }, + sendingToken: { address: "native", symbol: "", logo: "" }, setSendingToken: () => {}, buyerAddress: "", setBuyerAddress: () => {}, @@ -93,8 +99,8 @@ export const NewTransactionProvider: React.FC<{ children: React.ReactNode }> = ( const [receivingToken, setReceivingToken] = useState (localStorage.getItem("receivingToken") || ""); const [sellerAddress, setSellerAddress] = useState (localStorage.getItem("sellerAddress") || ""); const [sendingQuantity, setSendingQuantity] = useState (localStorage.getItem("sendingQuantity") || ""); - const [sendingToken, setSendingToken] = useState<{ address: string; symbol: string }>( - JSON.parse(localStorage.getItem("sendingToken")) || { address: "", symbol: "" } + const [sendingToken, setSendingToken] = useState ( + JSON.parse(localStorage.getItem("sendingToken")) || { address: "native", symbol: "", logo: "" } ); const [buyerAddress, setBuyerAddress] = useState (localStorage.getItem("buyerAddress") || ""); const [isRecipientAddressResolved, setIsRecipientAddressResolved] = useState(false); @@ -113,7 +119,7 @@ export const NewTransactionProvider: React.FC<{ children: React.ReactNode }> = ( setReceivingToken(""); setSellerAddress(""); setSendingQuantity(""); - setSendingToken({ address: "", symbol: "" }); + setSendingToken({ address: "native", symbol: "", logo: "" }); setBuyerAddress(""); setDeadline(""); setNotificationEmail(""); diff --git a/web/src/hooks/useFilteredTokens.ts b/web/src/hooks/useFilteredTokens.ts index 7e03a07..a7cf974 100644 --- a/web/src/hooks/useFilteredTokens.ts +++ b/web/src/hooks/useFilteredTokens.ts @@ -1,8 +1,14 @@ import { useEffect, useState } from "react"; import { useTokenMetadata } from "./useTokenMetadata"; +import { IToken } from "context/NewTransactionContext"; import EthTokenIcon from "svgs/icons/eth-token-icon.png"; -export const useFilteredTokens = (searchQuery: string, tokens, setTokens, sendingToken) => { +export const useFilteredTokens = ( + searchQuery: string, + tokens: IToken[], + setTokens: (tokens: IToken) => void, + sendingToken: IToken +) => { const { tokenMetadata } = useTokenMetadata( searchQuery.startsWith("0x") && searchQuery.length === 42 ? searchQuery : null ); diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx index d3902fe..b1bd0ec 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx @@ -34,7 +34,7 @@ const AmountField: React.FC = ({ quantity, setQuantity, error }) = value={quantity} onChange={handleWrite} type="number" - placeholder="eg. 3.6" + placeholder="Amount" variant={error ? "error" : undefined} message={error} /> diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/MaxBalance.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/MaxBalance.tsx new file mode 100644 index 0000000..bed62d3 --- /dev/null +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/MaxBalance.tsx @@ -0,0 +1,68 @@ +import React from "react"; +import { landscapeStyle } from "styles/landscapeStyle"; +import styled, { css } from "styled-components"; +import Skeleton from "react-loading-skeleton"; +import { isUndefined } from "utils/index"; + +const Container = styled.div` + display: flex; + flex-direction: row; + align-self: center; + gap: 4px; + + ${landscapeStyle( + () => css` + align-self: flex-end; + ` + )} +`; + +const LabelAndBalance = styled.div` + display: flex; + flex-direction: row; + gap: 4px; +`; + +const Label = styled.p` + margin: 0; + color: ${({ theme }) => theme.secondaryText}; + font-size: 12px; +`; + +const Balance = styled.p` + margin: 0; + font-size: 12px; +`; + +const BalanceSkeleton = styled(Skeleton)` + width: 63px; + height: 16px; +`; + +const MaxButton = styled.p` + margin: 0; + color: ${({ theme }) => theme.primaryBlue}; + font-size: 12px; + cursor: pointer; +`; + +interface IMaxBalance { + formattedBalance: string; + rawBalance: number; + setQuantity: (value: string) => void; +} + +const MaxBalance: React.FC = ({ formattedBalance, rawBalance, setQuantity }) => { + return ( + + + ); +}; +export default MaxBalance; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/Balance.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/Balance.tsx new file mode 100644 index 0000000..5f7d4b0 --- /dev/null +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/Balance.tsx @@ -0,0 +1,41 @@ +import React, { useMemo } from "react"; +import styled from "styled-components"; +import Skeleton from "react-loading-skeleton"; +import { useAccount, useBalance } from "wagmi"; +import { IToken } from "context/NewTransactionContext"; +import { isUndefined } from "utils/index"; +import { getFormattedBalance } from "utils/getFormattedBalance"; + +const Container = styled.p` + color: ${({ theme }) => theme.primaryText}; + margin: 0; +`; + +const StyledAmountSkeleton = styled(Skeleton)` + width: 52px; + height: 20px; +`; + +interface IBalance { + token: IToken; +} + +const Balance: React.FC+ + {isUndefined(formattedBalance) ? + {!isUndefined(formattedBalance) ? ( +: {formattedBalance} } +setQuantity(String(rawBalance))}>Max + ) : null} += ({ token }) => { + const { address } = useAccount(); + + const { data: balanceData } = useBalance({ + address: address, + token: token?.address === "native" ? undefined : token?.address, + }); + + const formattedBalance = useMemo(() => getFormattedBalance(balanceData, token), [balanceData, token]); + + return ( + + {isUndefined(formattedBalance) ? + ); +}; + +export default Balance; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/index.tsx similarity index 57% rename from web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx rename to web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/index.tsx index 2eefaca..05124bf 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenItem/index.tsx @@ -1,12 +1,13 @@ import React from "react"; import styled from "styled-components"; +import Balance from "./Balance"; const Container = styled.div<{ selected: boolean }>` display: flex; align-items: center; - gap: 8px; padding: 10px 16px; cursor: pointer; + justify-content: space-between; background: ${({ theme, selected }) => (selected ? theme.mediumBlue : "transparent")}; border-left: ${({ theme, selected }) => (selected ? `3px solid ${theme.primaryBlue}` : "none")}; padding-left: ${({ selected }) => (selected ? "13px" : "16px")}; @@ -16,6 +17,13 @@ const Container = styled.div<{ selected: boolean }>` } `; +const LogoAndLabel = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + const TokenLogo = styled.img` width: 24px; height: 24px; @@ -25,11 +33,16 @@ const TokenLabel = styled.span` color: ${({ theme }) => theme.primaryText}; `; -const TokenItem = ({ token, selected, onSelect }) => ( -: null} + {!isUndefined(formattedBalance) && formattedBalance !== "0" ? formattedBalance : null} + onSelect(token)}> - -); +const TokenItem = ({ token, selected, onSelect }) => { + return ( +- {token.symbol} -onSelect(token)}> + + ); +}; export default TokenItem; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx index feae307..89b4983 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/index.tsx @@ -17,7 +17,7 @@ const Container = styled.div` position: relative; `; -const TokenSelector = () => { +const TokenSelector: React.FC = () => { const { address } = useAccount(); const { chain } = useNetwork(); const { sendingToken, setSendingToken } = useNewTransactionContext(); diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx index c95f9f0..4d9d558 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx @@ -1,18 +1,33 @@ -import React, { useState, useEffect } from "react"; -import styled from "styled-components"; +import React, { useMemo, useState, useEffect } from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; import { useBalance, useAccount } from "wagmi"; import { useNewTransactionContext } from "context/NewTransactionContext"; -import { responsiveSize } from "styles/responsiveSize"; +import { getFormattedBalance } from "utils/getFormattedBalance"; import AmountField from "./AmountField"; import TokenSelector from "./TokenSelector"; +import MaxBalance from "./MaxBalance"; const Container = styled.div` display: flex; flex-direction: row; + justify-content: center; gap: 24px; - align-items: center; - margin-bottom: ${responsiveSize(24, 18)}; - margin-left: 36px; + margin-bottom: ${responsiveSize(16, 0)}; + flex-wrap: wrap; + + ${landscapeStyle( + () => css` + margin-left: 24px; + ` + )} +`; + +const TokenSelectorAndMaxBalance = styled.div` + display: flex; + flex-direction: column; + gap: 4px; `; interface ITokenAndAmount { @@ -45,10 +60,19 @@ const TokenAndAmount: React.FC+ ++ {token.symbol} ++ = ({ quantity, setQuantity }) => } }, [balanceData, quantity, setHasSufficientNativeBalance]); + const formattedBalance = useMemo(() => getFormattedBalance(balanceData, sendingToken), [balanceData, sendingToken]); + return ( ); }; diff --git a/web/src/utils/fetchTokenInfo.ts b/web/src/utils/fetchTokenInfo.ts index 47a31d4..5278485 100644 --- a/web/src/utils/fetchTokenInfo.ts +++ b/web/src/utils/fetchTokenInfo.ts @@ -1,4 +1,5 @@ import { Alchemy } from "alchemy-sdk"; +import { IToken } from "context/NewTransactionContext"; import EthTokenIcon from "svgs/icons/eth-token-icon.png"; export const fetchTokenInfo = async (address: string, alchemyInstance: Alchemy) => { @@ -7,7 +8,8 @@ export const fetchTokenInfo = async (address: string, alchemyInstance: Alchemy) return { symbol: metadata.symbol?.toUpperCase() || "Unknown", logo: metadata.logo || EthTokenIcon, - }; + address, + } as IToken; } catch (error) { console.error("Error fetching token info:", error); return { symbol: "Unknown", logo: EthTokenIcon }; diff --git a/web/src/utils/format.ts b/web/src/utils/format.ts index 5b07d7f..409e991 100644 --- a/web/src/utils/format.ts +++ b/web/src/utils/format.ts @@ -14,10 +14,10 @@ export const formatValue = (value: string, fractionDigits, roundDown) => { return commify(units.toFixed(fractionDigits)); }; -export const formatPNK = (value: bigint, fractionDigits = 0, roundDown = false) => +export const formatPNK = (value: bigint, fractionDigits = 0, roundDown = true) => formatValue(formatUnitsWei(value), fractionDigits, roundDown); -export const formatETH = (value: bigint, fractionDigits = 4, roundDown = false) => +export const formatETH = (value: bigint, fractionDigits = 4, roundDown = true) => formatValue(formatEther(value), fractionDigits, roundDown); export const formatUSD = (value: number, fractionDigits = 2) => "$" + commify(Number(value).toFixed(fractionDigits)); diff --git a/web/src/utils/getFormattedBalance.ts b/web/src/utils/getFormattedBalance.ts new file mode 100644 index 0000000..27126c9 --- /dev/null +++ b/web/src/utils/getFormattedBalance.ts @@ -0,0 +1,7 @@ +import { formatETH, formatPNK } from "./format"; + +export const getFormattedBalance = (balanceData: any, token: any) => { + if (!balanceData) return undefined; + if (token?.symbol === "PNK") return formatPNK(balanceData.value); + return formatETH(balanceData.value); +}; diff --git a/web/src/utils/initializeTokens.ts b/web/src/utils/initializeTokens.ts index 34a8325..f19a154 100644 --- a/web/src/utils/initializeTokens.ts +++ b/web/src/utils/initializeTokens.ts @@ -1,4 +1,5 @@ import { Alchemy } from "alchemy-sdk"; +import { IToken } from "context/NewTransactionContext"; import { fetchNativeToken } from "./fetchNativeToken"; import { fetchTokenInfo } from "./fetchTokenInfo"; @@ -12,9 +13,9 @@ export const initializeTokens = async (address: string, setTokens, setLoading, c const tokenInfo = await fetchTokenInfo(token.contractAddress, alchemyInstance); return { symbol: tokenInfo.symbol, - address: token.contractAddress, + address: tokenInfo.address, logo: tokenInfo.logo, - }; + } as IToken; }) ); const allTokens = [nativeToken, ...tokenList]; From 82dc13db8fef9cfcc7e12635d469a46b81b6266a Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich - + + + + Date: Tue, 28 May 2024 13:59:52 +0530 Subject: [PATCH 16/16] fix(web): styling-of-payment-input-and-token-selector --- .../GeneralTransaction/TokenAndAmount/AmountField.tsx | 3 ++- .../TokenAndAmount/TokenSelector/TokenListModal.tsx | 11 +++++++++-- .../GeneralTransaction/TokenAndAmount/index.tsx | 6 ------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx index b1bd0ec..99b63f1 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/AmountField.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import { Field } from "@kleros/ui-components-library"; const StyledField = styled(Field)` - width: 132px; + width: 186px; input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; @@ -15,6 +15,7 @@ const StyledField = styled(Field)` input { font-size: 16px; + padding-right: ${({ variant }) => (variant ? "40px" : "16px")}; } `; diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx index e1cd412..0f6d1a8 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/TokenSelector/TokenListModal.tsx @@ -1,5 +1,5 @@ import React, { useRef, useState } from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; import { useClickAway } from "react-use"; import { Searchbar } from "@kleros/ui-components-library"; import { useNewTransactionContext } from "context/NewTransactionContext"; @@ -7,10 +7,17 @@ import { Overlay } from "components/Overlay"; import TokenItem from "./TokenItem"; import { StyledModal } from "pages/MyTransactions/Modal/StyledModal"; import { useFilteredTokens } from "hooks/useFilteredTokens"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; const ReStyledModal = styled(StyledModal)` display: flex; - width: 500px; + width: ${responsiveSize(320, 500)}; + ${landscapeStyle( + () => css` + width: 500px; + ` + )} `; const StyledSearchbar = styled(Searchbar)` diff --git a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx index 4d9d558..317435b 100644 --- a/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx +++ b/web/src/pages/NewTransaction/Terms/Payment/GeneralTransaction/TokenAndAmount/index.tsx @@ -16,12 +16,6 @@ const Container = styled.div` gap: 24px; margin-bottom: ${responsiveSize(16, 0)}; flex-wrap: wrap; - - ${landscapeStyle( - () => css` - margin-left: 24px; - ` - )} `; const TokenSelectorAndMaxBalance = styled.div`