-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(ui-ux): added liquidity overview screen (#794)
* feature(ui-ux): added liquidity overview screen * added Link component * added Disclosure for mobile view * removed env variables * removed unused api endpoint * lint fix * fixed width in small screen * renamed router * added tool tip and added fetch balances on network change * minor ui updates * hide navigation when bridge is down * desc update * minor fix * minor updates * updated hotWalletAddress to HotWalletAddress * chore: update dfi label on ethereum Refactor TokenInfo prop and rename component --------- Co-authored-by: Keng Ye <[email protected]> Co-authored-by: Keng Ye <[email protected]>
- Loading branch information
1 parent
2397387
commit 4a17aea
Showing
14 changed files
with
510 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* eslint-disable no-restricted-imports */ | ||
import { LinkProps as NextLinkProps } from "next/dist/client/link"; | ||
import { useRouter } from "next/router"; | ||
import NextLink from "next/link"; | ||
import { PropsWithChildren } from "react"; | ||
import { UrlObject } from "url"; | ||
import { useNetworkEnvironmentContext } from "@contexts/NetworkEnvironmentContext"; | ||
|
||
export interface LinkUrlObject extends UrlObject { | ||
query?: Record<string, string>; | ||
} | ||
|
||
interface LinkProps extends NextLinkProps { | ||
href: LinkUrlObject; | ||
} | ||
|
||
/** | ||
* Overrides the default next/link to provide ability to 'keep ?network= query string'. | ||
* This allows `<Link>` usage to be network agnostic where ?network= are automatically appended. | ||
* | ||
* To keep implementation simple, LinkProps enforce href to be strictly a `UrlObject` object | ||
* where query is a `Record<string, string>`. Hence only use this for internal linking. | ||
* | ||
* @param {PropsWithChildren<LinkProps>} props | ||
*/ | ||
export function Link(props: PropsWithChildren<LinkProps>): JSX.Element { | ||
const { children, href } = props; | ||
const router = useRouter(); | ||
const networkQuery = router.query.network; | ||
|
||
const { networkEnv } = useNetworkEnvironmentContext(); | ||
if (networkQuery) { | ||
href.query = { | ||
...(href.query ?? {}), | ||
network: networkEnv, | ||
}; | ||
} | ||
|
||
return ( | ||
<NextLink passHref {...props} legacyBehavior> | ||
{children} | ||
</NextLink> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import clsx from "clsx"; | ||
import { useRouter } from "next/router"; | ||
import { Link } from "./Link"; | ||
|
||
export default function Navigation() { | ||
const router = useRouter(); | ||
const isRoot = router.pathname === "/"; | ||
|
||
return ( | ||
<div className="flex flex-row rounded-[40px] bg-dark-100 p-1 border-[0.5px] border-dark-300/50"> | ||
<Link href={{ pathname: "/" }}> | ||
<a | ||
className={clsx( | ||
"w-1/2 text-xs md:text-sm py-3 min-w-[136px] text-center font-semibold rounded-[40px]", | ||
isRoot ? "bg-dark-1000 text-dark-00" : "text-dark-1000" | ||
)} | ||
> | ||
Bridge | ||
</a> | ||
</Link> | ||
<Link href={{ pathname: "/liquidity" }}> | ||
<a | ||
className={clsx( | ||
"w-1/2 text-xs md:text-sm py-3 min-w-[136px] text-center font-semibold rounded-[40px]", | ||
!isRoot ? "bg-dark-1000 text-dark-00" : "text-dark-1000" | ||
)} | ||
> | ||
Liquidity | ||
</a> | ||
</Link> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
import { PropsWithChildren } from "react"; | ||
import { useDeFiScanContext } from "@contexts/DeFiScanContext"; | ||
import { NetworkI, networks } from "@contexts/NetworkContext"; | ||
import Image from "next/image"; | ||
import NumericFormat from "@components/commons/NumericFormat"; | ||
import BigNumber from "bignumber.js"; | ||
import { FiArrowUpRight, FiChevronDown, FiChevronUp } from "react-icons/fi"; | ||
import { useContractContext } from "@contexts/ContractContext"; | ||
import { Network, TokenDetailI, TokensI } from "types"; | ||
import clsx from "clsx"; | ||
import useResponsive from "@hooks/useResponsive"; | ||
import { Disclosure } from "@headlessui/react"; | ||
import IconTooltip from "./commons/IconTooltip"; | ||
|
||
function TokenOrNetworkInfo({ | ||
token, | ||
iconClass, | ||
nameClass, | ||
onClose, | ||
}: { | ||
token: TokenDetailI<string> | NetworkI<string>; | ||
iconClass?: string; | ||
nameClass?: string; | ||
onClose?: () => void; | ||
}) { | ||
return ( | ||
<div className="flex flex-row items-center justify-between"> | ||
<div className="flex flex-row items-center"> | ||
<Image | ||
width={100} | ||
height={100} | ||
src={token.icon} | ||
alt={token.name} | ||
data-testid={token.name} | ||
className={iconClass ?? "h-8 w-8"} | ||
/> | ||
<span | ||
className={clsx( | ||
"ml-2 lg:ml-3 block truncate text-dark-1000 text-base text-left", | ||
nameClass | ||
)} | ||
> | ||
{token.name} | ||
{(token as TokenDetailI<string>).subtitle && ( | ||
<span className="block text-xs text-dark-700"> | ||
{(token as TokenDetailI<string>).subtitle} | ||
</span> | ||
)} | ||
</span> | ||
</div> | ||
{onClose !== undefined && ( | ||
<FiChevronUp | ||
onClick={onClose} | ||
className={clsx("h-6 w-6 text-dark-1000 transition-[transform]")} | ||
/> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
function AddressComponent({ | ||
address, | ||
isDeFiAddress, | ||
}: { | ||
address: string; | ||
isDeFiAddress: boolean; | ||
}) { | ||
const { ExplorerURL } = useContractContext(); | ||
const { getAddressUrl } = useDeFiScanContext(); | ||
const url = isDeFiAddress | ||
? getAddressUrl(address) | ||
: `${ExplorerURL}/address/${address}`; | ||
return ( | ||
<a | ||
className="flex flex-col lg:flex-row items-end lg:items-center justify-end lg:justify-start text-dark-1000 text-xs lg:text-base font-semibold text-right lg:text-left break-all flex-1" | ||
href={url} | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
{address} | ||
<div className="flex flex-row justify-between items-center mt-1 lg:mt-0 mb-1 lg:mb-0"> | ||
<FiArrowUpRight className="text-dark-1000 mr-1 lg:mr-0 lg:ml-1 h-4 w-4" /> | ||
<span className="lg:hidden font-semibold text-sm">View</span> | ||
</div> | ||
</a> | ||
); | ||
} | ||
|
||
function BorderDiv({ | ||
children, | ||
className, | ||
}: PropsWithChildren<{ className: string }>) { | ||
return ( | ||
<div | ||
className={clsx( | ||
"border-gradient-6 relative bg-dark-00/50 rounded-[15px]", | ||
"before:absolute before:content-[''] before:inset-0 before:p-px before:rounded-[15px] before:z-[-1]", | ||
className | ||
)} | ||
> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
function TokenDetails({ | ||
network, | ||
token, | ||
isDeFiAddress, | ||
amount, | ||
containerClass, | ||
onClose, | ||
}: { | ||
network: NetworkI<string>; | ||
token: TokenDetailI<string>; | ||
isDeFiAddress: boolean; | ||
amount: BigNumber; | ||
containerClass?: string; | ||
onClose?: () => void; | ||
}) { | ||
const { BridgeV1, HotWalletAddress } = useContractContext(); | ||
const address = isDeFiAddress ? HotWalletAddress : BridgeV1.address; | ||
return ( | ||
<div | ||
className={clsx( | ||
"flex flex-col md:w-1/2 lg:w-full lg:flex-row space-y-5 lg:space-y-0 justify-between items-center", | ||
containerClass | ||
)} | ||
> | ||
<div className="w-full lg:w-2/12"> | ||
<TokenOrNetworkInfo | ||
token={token} | ||
onClose={onClose} | ||
nameClass="font-semibold lg:font-normal" | ||
iconClass="h-6 w-6 md:h-8 md:w-8" | ||
/> | ||
</div> | ||
<div className="w-full flex flex-row items-center justify-between lg:w-2/12"> | ||
<span className="lg:hidden text-dark-700 text-sm w-5/12"> | ||
Blockchain | ||
</span> | ||
<TokenOrNetworkInfo token={network} iconClass="h-5 w-5 lg:h-8 lg:w-8" /> | ||
</div> | ||
<div className="w-full flex flex-row items-center justify-between lg:w-4/12"> | ||
<div className="flex flex-row items-center lg:hidden text-dark-700 w-5/12 space-x-1"> | ||
<span className="text-sm">Liquidity</span> | ||
<IconTooltip | ||
size={16} | ||
position="top" | ||
customIconColor="text-dark-700" | ||
content="The max amount available to bridge for a specific token." | ||
/> | ||
</div> | ||
<NumericFormat | ||
className="text-dark-1000 text-sm lg:text-base text-dark-1000 text-right lg:text-left flex-1" | ||
value={amount} | ||
decimalScale={8} | ||
thousandSeparator | ||
suffix={` ${token.name}`} | ||
/> | ||
</div> | ||
<div className="w-full flex flex-row items-start lg:items-center justify-between lg:w-4/12"> | ||
<span className="lg:hidden text-dark-700 text-sm w-5/12">Address</span> | ||
<AddressComponent address={address} isDeFiAddress={isDeFiAddress} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default function OverviewList({ balances }) { | ||
const [firstNetwork, secondNetwork] = networks; | ||
const { isMobile } = useResponsive(); | ||
|
||
const getAmount = (symbol: string, network): BigNumber => { | ||
if (network === Network.DeFiChain) { | ||
return new BigNumber(balances.DFC?.[symbol] ?? 0); | ||
} | ||
return new BigNumber(balances.EVM?.[symbol] ?? 0); | ||
}; | ||
|
||
function getTokenRow(item: TokensI, onClose?: () => void) { | ||
return ( | ||
<> | ||
<TokenDetails | ||
network={secondNetwork} | ||
token={item.tokenA} | ||
isDeFiAddress={secondNetwork.name === Network.DeFiChain} | ||
amount={getAmount(item.tokenA.symbol, secondNetwork.name)} | ||
onClose={onClose} | ||
containerClass="pb-4 md:pb-0 lg:pb-5 md:pr-5 lg:pr-0" | ||
/> | ||
<TokenDetails | ||
network={firstNetwork} | ||
token={item.tokenB} | ||
isDeFiAddress={firstNetwork.name === Network.DeFiChain} | ||
amount={getAmount(item.tokenB.symbol, firstNetwork.name)} | ||
containerClass={clsx( | ||
"border-t-[0.5px] md:border-t-0 md:border-l-[0.5px] lg:border-l-0 lg:border-t-[0.5px] border-dark-200", | ||
"pt-4 md:pt-0 lg:pt-5 md:pl-5 lg:pl-0" | ||
)} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
function getTokenCard(item: TokensI) { | ||
return ( | ||
<Disclosure> | ||
{({ open, close }) => ( | ||
<> | ||
{!open && ( | ||
<Disclosure.Button> | ||
<div className="flex flex-row justify-between items-center"> | ||
<div className="flex flex-row items-center"> | ||
<div className="mr-3"> | ||
<TokenOrNetworkInfo | ||
token={item.tokenA} | ||
iconClass="h-6 w-6" | ||
nameClass="font-semibold" | ||
/> | ||
</div> | ||
<div className="pl-3 border-l-[0.5px] border-dark-200"> | ||
<TokenOrNetworkInfo | ||
token={item.tokenB} | ||
iconClass="h-6 w-6" | ||
nameClass="font-semibold" | ||
/> | ||
</div> | ||
</div> | ||
<FiChevronDown className="h-6 w-6 text-dark-1000 transition-[transform]" /> | ||
</div> | ||
</Disclosure.Button> | ||
)} | ||
<Disclosure.Panel className="text-gray-500"> | ||
{getTokenRow(item, close)} | ||
</Disclosure.Panel> | ||
</> | ||
)} | ||
</Disclosure> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<div className="hidden lg:block mt-6 md:mt-8 lg:mt-12"> | ||
<div className="flex flex-row px-8 py-4"> | ||
<div className="text-dark-1000 text-sm font-semibold w-2/12"> | ||
Token | ||
</div> | ||
<div className="text-dark-1000 text-sm font-semibold w-2/12"> | ||
Blockchain | ||
</div> | ||
<div className="flex flex-row items-center text-dark-1000 text-sm w-4/12 space-x-1"> | ||
<span className="font-semibold">Liquidity </span> | ||
<IconTooltip | ||
position="top" | ||
size={12} | ||
customIconColor="text-dark-1000" | ||
content="The max amount available to bridge for a specific token." | ||
/> | ||
</div> | ||
<div className="text-dark-1000 text-sm font-semibold w-4/12"> | ||
Address | ||
</div> | ||
</div> | ||
</div> | ||
<div className="space-y-3 md:space-y-4 px-5 md:px-0"> | ||
{secondNetwork.tokens.map((item) => ( | ||
<BorderDiv | ||
key={item.tokenA.name} | ||
className="px-4 md:px-5 lg:px-8 py-5 md:py-6 lg:py-5 flex flex-col md:flex-row lg:flex-col" | ||
> | ||
{isMobile ? getTokenCard(item) : getTokenRow(item)} | ||
</BorderDiv> | ||
))} | ||
</div> | ||
</> | ||
); | ||
} |
Oops, something went wrong.