Skip to content

Commit

Permalink
feature(ui-ux): added liquidity overview screen (#794)
Browse files Browse the repository at this point in the history
* 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
3 people authored Mar 24, 2023
1 parent 2397387 commit 4a17aea
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 4 deletions.
17 changes: 16 additions & 1 deletion apps/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import Image from "next/image";
import Link from "next/link";
import ConnectButton from "./ConnectButton";
import Banner from "./Banner";
import Navigation from "./Navigation";

export default function Header(): JSX.Element {
export default function Header({
isBridgeUp,
}: {
isBridgeUp: boolean;
}): JSX.Element {
return (
<div className="relative z-[1] flex flex-col">
<Banner />
Expand All @@ -18,10 +23,20 @@ export default function Header(): JSX.Element {
/>
</div>
</Link>
{isBridgeUp && (
<div className="hidden lg:block">
<Navigation />
</div>
)}
<div className="flex h-9 items-center md:h-10 lg:h-12">
<ConnectButton />
</div>
</div>
{isBridgeUp && (
<div className="lg:hidden px-5 md:px-10 mb-6 md:mb-12">
<Navigation />
</div>
)}
</div>
);
}
44 changes: 44 additions & 0 deletions apps/web/src/components/Link.tsx
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>
);
}
33 changes: 33 additions & 0 deletions apps/web/src/components/Navigation.tsx
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>
);
}
279 changes: 279 additions & 0 deletions apps/web/src/components/OverviewList.tsx
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>
</>
);
}
Loading

0 comments on commit 4a17aea

Please sign in to comment.