Skip to content

Commit

Permalink
Improve Transfer UI (#656)
Browse files Browse the repository at this point in the history
* feat: new component TransferSectionTitle

* feat: new component TransferTokenSelect

* feat: new component TransferSection

* feat: new component TransferTokenSection

* feat: new component TransferAmountInput

* feat: new component TransferAmountSection

* feat: new component TransferInformation

* feat: new component TransferInformationSection

* feat: new component TransferChainSelect

* feat: support offsetSize prop of Select

* refactor: update padding-x of TransferSection

* refactor: update padding-x of TransferSectionTitle

* refactor: improve ui of TransferChainSelect

* feat: new component TransferSwitch

* style: format switch svg

* feat: component TransferChainSection

* refactor: token category

* feat: transfer utils

* refactor: change image to logo of TokenOption interface

* fix: correct filter of getTargetChainOptions

* refactor: update value type of transfer token select

* refactor: adapt padding-x of transfer section

* feat: component TransferV2

* feat: transfer switch

* refactor: change opacity when hover of token for select

* feat: transfer button

* feat: fetch balance

* refactor: update gap of sections

* feat: fetch sorted relay data

* feat: transfer information display

* feat: insufficient alert

* refactor: hide transfer information when zero amount

* fix: reset amount after change token

* refactor: show transfer information if has amount input

* refactor: update use allowance hook

* feat: transfer

* feat: change url

* feat: init state from url params

* style: improve code block position

* feat: minimum amount verify

* feat: min transfer

* fix: position center

* refactor: use transfer v2

* refactor: hover shadow switch icon

* refactor: disable transfer select from balances

* refactor: lg:gap-5 between sections

* refactor: improve max font size of transfer amount input

* fix: target token of url param

* refactor: transfer amount input auto focus

* refactor: lg:gap-large between sections

* refactor: only token category url param

* fix: max height of chain drowdown

* refactor: improve chain select ui

* feat: footer xtoken link

* fix: correct xtoken link

* refactor: insufficient alert

* refactor: change max height of transfer chain select

* refactor: improve global scrollbar style

* refactor: increase toke icon size

* refactor: optimize transfer v2 ui

* refactor: update modal button styles

* refactor: improve transfer modal v2 styles

* fix: correct source token options

* feat: tooltip status

* refactor: transfer information warning tooltips

* refactor: optimize chain select hover styles

* refactor: change gap berween chain options

* feat: support search in chain select

* refactor: optimize chain options component style

* refactor: remove title from transfer information

* refactor: update padding and font style of transfer information

* refactor: improve transfer switch style

* refactor: update max height of chain options

* feat: create useSupportChains

* refactor: no cache to fetch support chains

* refactor: truncate chain name in chain select

* refactor: update props of ComponentLoading

* feat: chain section loading

* feat: verify support chains

* refactor: improve loading style of chain section

* fix: correct available chain options

* fix: switch verification

* style: change code block position

* refactor: remove usdc
  • Loading branch information
JayJay1024 authored Mar 13, 2024
1 parent 6e4b748 commit 3f8ab44
Show file tree
Hide file tree
Showing 65 changed files with 2,026 additions and 38 deletions.
6 changes: 4 additions & 2 deletions public/images/switch.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ body {
rgb(var(--background-start-rgb));
} */

@layer base {
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
box-shadow: inset 0 0 5x rgba(0, 0, 0, 0.2);
background: hsla(0, 0%, 100%, 0.4);
}
}

@layer components {
/* Layout */
.app-header {
Expand All @@ -41,7 +52,7 @@ body {

/* Page */
.page-container {
@apply max-w-8xl w-full mx-auto px-medium py-5;
@apply mx-auto w-full max-w-8xl px-medium py-5;
}

/* User */
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ export const metadata: Metadata = {
description: "Perform cross-chain transfers through Helix Bridge",
};

const Transfer = dynamic(() => import("@/components/transfer"), { ssr: false });
const TransferV2 = dynamic(() => import("@/components/transfer-v2"), { ssr: false });

export default function HomePage() {
return (
<PageWrap>
<Transfer />
<TransferV2 />
</PageWrap>
);
}
2 changes: 1 addition & 1 deletion src/bridges/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export abstract class BaseBridge {
}

formatEstimateTime() {
return `${this.estimateTime.min}~${this.estimateTime.max} minutes`;
return `${this.estimateTime.min}~${this.estimateTime.max} Minutes`;
}

getTxGasLimit() {
Expand Down
8 changes: 8 additions & 0 deletions src/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ export default function Footer() {
>
Explorer
</Link>
<a
className="text-sm font-medium text-white/50 transition hover:text-white active:scale-95"
href="https://xtoken.helixbridge.app/"
rel="noopener noreferrer"
target="_blank"
>
xToken
</a>
<a
className="text-sm font-medium text-white/50 transition hover:text-white active:scale-95"
href="https://docs.helixbridge.app/"
Expand Down
213 changes: 213 additions & 0 deletions src/components/modals/transfer-modal-v2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { BaseBridge } from "@/bridges";
import { GQL_HISTORY_RECORD_BY_TX_HASH } from "@/config";
import { useApp } from "@/hooks";
import {
ChainConfig,
HistoryRecordByTxHashReqParams,
HistoryRecordByTxHashResData,
RecordResult,
Token,
} from "@/types";
import ProgressIcon from "@/ui/progress-icon";
import { formatBalance, getChainLogoSrc, toShortAdrress } from "@/utils";
import { useQuery } from "@apollo/client";
import dynamic from "next/dynamic";
import Image from "next/image";
import Link from "next/link";
import { Address, Hex, isHex } from "viem";

const Modal = dynamic(() => import("@/ui/modal"), { ssr: false });

interface Props {
sender?: `0x${string}` | null;
recipient?: `0x${string}` | null;
sourceChain: ChainConfig;
sourceToken: Token;
targetChain: ChainConfig;
targetToken: Token;
txHash: Hex | null | undefined;
fee: { token: Token; value: bigint } | null | undefined;
bridge: BaseBridge | undefined;
amount: bigint;
busy: boolean;
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}

export default function TransferModalV2({
sender,
recipient,
busy,
fee,
bridge,
sourceChain,
sourceToken,
targetChain,
targetToken,
txHash,
amount,
isOpen,
onClose,
onConfirm,
}: Props) {
const { data: txProgressData } = useQuery<HistoryRecordByTxHashResData, HistoryRecordByTxHashReqParams>(
GQL_HISTORY_RECORD_BY_TX_HASH,
{
variables: { txHash: txHash ?? "0x" },
pollInterval: txHash ? 300 : 0,
skip: !txHash,
},
);
const { updateBalances } = useApp();

return (
<Modal
title="Transfer Summary"
isOpen={isOpen}
className="w-full lg:w-[34rem]"
okText="Confirm"
disabledCancel={busy}
busy={busy}
forceFooterHidden={!!txHash}
onClose={onClose}
onCancel={onClose}
onOk={onConfirm}
>
{/* From-To */}
<div className="flex flex-col gap-small">
<SourceTarget type="source" address={sender} chain={sourceChain} token={sourceToken} amount={amount} />
<div className="relative">
<div className="absolute bottom-0 left-0 right-0 top-0 flex items-center justify-center">
<Image width={36} height={36} alt="Transfer to" src="images/transfer-to.svg" className="shrink-0" />
</div>
</div>
<SourceTarget type="target" address={recipient} chain={targetChain} token={targetToken} amount={amount} />
</div>

{/* information */}
<div className="flex flex-col gap-medium">
<span className="text-sm font-extrabold text-white/50">Information</span>
<Information fee={fee} bridge={bridge} />
</div>

{txHash ? (
<div className="flex h-12 items-center rounded-large bg-inner px-5">
<Progress
confirmedBlocks={txProgressData?.historyRecordByTxHash?.confirmedBlocks}
result={txProgressData?.historyRecordByTxHash?.result}
id={txProgressData?.historyRecordByTxHash?.id}
onFinished={updateBalances}
/>
</div>
) : null}
</Modal>
);
}

function SourceTarget({
type,
address,
chain,
token,
amount,
}: {
type: "source" | "target";
amount: bigint;
chain?: ChainConfig;
token?: Token;
address?: Address | null;
}) {
return chain && token ? (
<div className="flex items-center justify-between rounded-3xl bg-inner p-5">
{/* Left */}
<div className="flex items-center gap-medium">
<Image width={36} height={36} alt="Chain" src={getChainLogoSrc(chain.logo)} className="shrink-0 rounded-full" />
<div className="flex flex-col items-start">
<span className="text-base font-extrabold text-white">{chain.name}</span>
<span className="hidden text-sm font-medium text-white/50 lg:inline">{address}</span>
{address ? (
<span className="text-sm font-medium text-white/50 lg:hidden">{toShortAdrress(address, 8, 6)}</span>
) : null}
</div>
</div>

{/* Right */}
<div className="flex flex-col items-end">
<span className={`text-base font-medium ${type === "source" ? "text-app-red" : "text-app-green"}`}>
{type === "source" ? "-" : "+"}
{formatBalance(amount, token.decimals)}
</span>
<span className="text-sm font-extrabold text-white">{token.symbol}</span>
</div>
</div>
) : null;
}

function Information({ fee, bridge }: { fee?: { value: bigint; token: Token } | null; bridge?: BaseBridge | null }) {
return (
<div className="flex flex-col gap-small rounded-3xl bg-inner p-5">
<Item
label="Transaction Fee"
value={fee ? `${formatBalance(fee.value, fee.token.decimals, { precision: 6 })} ${fee.token.symbol}` : null}
/>
<Item label="Estimated Arrival Time" value={bridge?.formatEstimateTime()} />
</div>
);
}

function Item({ label, value }: { label: string; value?: string | null }) {
return (
<div className="flex items-center justify-between gap-medium text-sm font-extrabold text-white">
<span>{label}</span>
<span className="truncate">{value}</span>
</div>
);
}

function Progress({
confirmedBlocks,
result,
id,
onFinished = () => undefined,
}: {
confirmedBlocks: string | null | undefined;
result: RecordResult | null | undefined;
id: string | null | undefined;
onFinished?: () => void;
}) {
const splited = isHex(confirmedBlocks) ? [1, 1] : confirmedBlocks?.split("/");
if (splited?.length === 2) {
const finished = Number(splited[0]);
const total = Number(splited[1]);

if (finished === total || result === RecordResult.SUCCESS) {
onFinished();
return (
<div className="flex w-full items-center justify-between">
<div className="inline-flex">
<span className="text-sm font-extrabold">LnProvider relay finished. Go to&nbsp;</span>
<Link href={`/records/${id}`} className="text-sm font-extrabold text-primary hover:underline">
Detail
</Link>
</div>
<Image width={20} height={20} alt="Finished" src="/images/finished.svg" />
</div>
);
} else {
return (
<div className="flex w-full items-center justify-between">
<span className="text-sm font-extrabold">{`Waiting for LnProvider relay message(${confirmedBlocks})`}</span>
<ProgressIcon percent={(finished * 100) / total} />
</div>
);
}
} else {
return (
<div className="flex w-full items-center justify-between">
<span className="text-sm font-extrabold">Waiting for indexing...</span>
<ProgressIcon percent={10} />
</div>
);
}
}
2 changes: 1 addition & 1 deletion src/components/record-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function RecordDetail(props: Props) {
<div className="mt-5 overflow-x-auto">
<div className="relative flex min-w-max flex-col gap-medium rounded-large bg-component px-7 py-medium">
{/* loading */}
<ComponentLoading loading={loading} className="rounded-large" />
<ComponentLoading loading={loading} className="rounded-large bg-black/30" />

<Item label="Transfer Route">
<TransferRoute record={record?.historyRecordById} />
Expand Down
Loading

0 comments on commit 3f8ab44

Please sign in to comment.