mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 20:27:58 -05:00
fix: bridge UI minor fixes (#104)
* fix: duplicate metamask wallet issue and dollars prices not updated issue * fix: refactor submit button state management + update Transactions page * fix: add multi platform docker build for bridge ui * fix: update bridge ui dockerfile + change github action runner os * fix: remove multi platform docker build * fix: update docs link + fix tooltip arrow issue * fix: chrome autofill background issue with input
This commit is contained in:
8
.github/workflows/bridge-ui-publish.yml
vendored
8
.github/workflows/bridge-ui-publish.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: besu-arm64
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -35,10 +35,8 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- # Setting up Docker Buildx with docker-container driver is required
|
||||
# at the moment to be able to use a subdirectory with Git context
|
||||
name: Set up Docker Buildx
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker Image Build and Publish
|
||||
|
||||
@@ -22,7 +22,7 @@ COPY ./bridge-ui ./bridge-ui
|
||||
COPY $ENV_FILE ./bridge-ui/.env.production
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store apk add --virtual build-dependencies --no-cache python3 make g++ \
|
||||
&& pnpm install --frozen-lockfile \
|
||||
&& pnpm install --frozen-lockfile --prefer-offline \
|
||||
&& pnpm run -F bridge-ui build \
|
||||
&& apk del build-dependencies
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ body {
|
||||
}
|
||||
|
||||
@layer components {
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active{
|
||||
-webkit-box-shadow: 0 0 0 30px #2D2D2D inset !important;
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply mx-auto px-4;
|
||||
max-width: 1280px;
|
||||
@@ -34,9 +41,21 @@ body {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tooltip:after {
|
||||
.tooltip-top::after {
|
||||
border-color: #505050 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip-right::after {
|
||||
border-color: transparent #505050 transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip-bottom::after {
|
||||
border-color: transparent transparent #505050 transparent;
|
||||
}
|
||||
|
||||
.tooltip-left::after {
|
||||
border-color: transparent transparent transparent #505050;
|
||||
}
|
||||
|
||||
.tooltip:before {
|
||||
border: 1px solid #505050;
|
||||
|
||||
@@ -196,7 +196,7 @@ const Bridge = () => {
|
||||
{!isConnected && <ConnectButton fullWidth />}
|
||||
</div>
|
||||
</form>
|
||||
{token && networkLayer !== NetworkLayer.UNKNOWN && token[networkLayer] && (
|
||||
{isConnected && token && networkLayer !== NetworkLayer.UNKNOWN && token[networkLayer] && (
|
||||
<div className="mt-4 px-2">
|
||||
<ERC20Stepper />
|
||||
</div>
|
||||
|
||||
@@ -26,18 +26,25 @@ export function ReceivedAmount({ receivedAmount }: ReceivedAmountProps) {
|
||||
{isConnected && (
|
||||
<>
|
||||
<span className="text-2xl font-semibold text-white">
|
||||
{formatBalance(receivedAmount) || 0} {token?.symbol}
|
||||
{(parseFloat(receivedAmount || "0") > 0 && formatBalance(receivedAmount)) || 0} {token?.symbol}
|
||||
</span>
|
||||
{networkType === NetworkType.MAINNET && (
|
||||
<span className="label-text flex items-center">
|
||||
<PiApproximateEqualsBold />
|
||||
{tokenPrices?.[tokenAddress]?.usd
|
||||
? (Number(receivedAmount) * tokenPrices?.[tokenAddress]?.usd).toLocaleString("en-US", {
|
||||
{receivedAmount &&
|
||||
parseFloat(receivedAmount) > 0 &&
|
||||
tokenPrices?.[tokenAddress.toLowerCase()]?.usd &&
|
||||
tokenPrices?.[tokenAddress.toLowerCase()]?.usd > 0 ? (
|
||||
<>
|
||||
<PiApproximateEqualsBold />
|
||||
{(Number(receivedAmount) * tokenPrices?.[tokenAddress.toLowerCase()]?.usd).toLocaleString("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 4,
|
||||
})
|
||||
: "$0.00"}
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -122,14 +122,20 @@ export function Amount() {
|
||||
/>
|
||||
{networkType === NetworkType.MAINNET && (
|
||||
<span className="label-text flex items-center justify-end">
|
||||
<PiApproximateEqualsBold />
|
||||
{amount && tokenPrices?.[tokenAddress]?.usd
|
||||
? `${(Number(amount) * tokenPrices?.[tokenAddress]?.usd).toLocaleString("en-US", {
|
||||
{amount &&
|
||||
tokenPrices?.[tokenAddress.toLowerCase()]?.usd &&
|
||||
tokenPrices?.[tokenAddress.toLowerCase()]?.usd > 0 ? (
|
||||
<>
|
||||
<PiApproximateEqualsBold />
|
||||
{(Number(amount) * tokenPrices?.[tokenAddress.toLowerCase()]?.usd).toLocaleString("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 4,
|
||||
})}`
|
||||
: "$0.00"}
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useSwitchNetwork, useAllowance, useApprove } from "@/hooks";
|
||||
import { Transaction } from "@/models";
|
||||
import { useChainStore } from "@/stores/chainStore";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { useTokenBalance } from "@/hooks/useTokenBalance";
|
||||
|
||||
export type BridgeForm = {
|
||||
amount: string;
|
||||
@@ -22,20 +21,15 @@ export default function Approve() {
|
||||
|
||||
// Form
|
||||
const { getValues, setValue, watch } = useFormContext();
|
||||
const watchAmount = watch("amount", false);
|
||||
const watchBalance = watch("balance", false);
|
||||
const [watchAmount, watchBalance] = watch(["amount", "balance"]);
|
||||
|
||||
// Context
|
||||
const { token, fromChain, tokenBridgeAddress, tokenAddress } = useChainStore((state) => ({
|
||||
tokenAddress: state.token?.[state.networkLayer],
|
||||
const { token, fromChain, tokenBridgeAddress } = useChainStore((state) => ({
|
||||
token: state.token,
|
||||
fromChain: state.fromChain,
|
||||
tokenBridgeAddress: state.tokenBridgeAddress,
|
||||
}));
|
||||
|
||||
// Hooks
|
||||
const { balance } = useTokenBalance(tokenAddress, token?.decimals);
|
||||
|
||||
const { switchChain } = useSwitchNetwork(fromChain?.id);
|
||||
const { allowance, refetchAllowance } = useAllowance();
|
||||
const { hash: newTxHash, setHash, writeApprove, isLoading: isApprovalLoading } = useApprove();
|
||||
@@ -43,7 +37,8 @@ export default function Approve() {
|
||||
// Wagmi
|
||||
const { address } = useAccount();
|
||||
|
||||
const hasInsufficientBalance = watchAmount && balance < watchAmount;
|
||||
const hasInsufficientBalance =
|
||||
watchAmount && token && parseUnits(watchAmount, token.decimals) > parseUnits(watchBalance, token.decimals);
|
||||
|
||||
const {
|
||||
isLoading: isWaitingLoading,
|
||||
@@ -101,12 +96,22 @@ export default function Approve() {
|
||||
}
|
||||
}, [watchAmount, allowance, token, setValue]);
|
||||
|
||||
// Click on approve
|
||||
const approveHandler = async () => {
|
||||
await switchChain();
|
||||
|
||||
if (token) {
|
||||
const amount = getValues("amount");
|
||||
const amountToApprove = parseUnits(amount, token.decimals);
|
||||
const amountBigInt = parseUnits(amount, token.decimals);
|
||||
let amountToApprove = amountBigInt;
|
||||
|
||||
if (allowance && allowance > 0n) {
|
||||
if (allowance >= amountBigInt) {
|
||||
amountToApprove = allowance - amountBigInt;
|
||||
} else {
|
||||
amountToApprove = amountBigInt - allowance;
|
||||
}
|
||||
}
|
||||
|
||||
writeApprove(amountToApprove, tokenBridgeAddress);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { parseUnits } from "viem";
|
||||
import { useAccount, useBalance } from "wagmi";
|
||||
import { MdInfo } from "react-icons/md";
|
||||
import { NetworkLayer } from "@/config";
|
||||
import { useBridge } from "@/hooks";
|
||||
import { useChainStore } from "@/stores/chainStore";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import ApproveERC20 from "./ApproveERC20";
|
||||
import { Button } from "../../ui";
|
||||
import { useAccount, useBalance } from "wagmi";
|
||||
import { Button, Tooltip } from "../../ui";
|
||||
import { cn } from "@/utils/cn";
|
||||
import { parseUnits } from "viem";
|
||||
|
||||
type SubmitProps = {
|
||||
isLoading: boolean;
|
||||
@@ -18,7 +19,7 @@ export function Submit({ isLoading = false, isWaitingLoading = false }: SubmitPr
|
||||
const { watch, formState } = useFormContext();
|
||||
const { errors } = formState;
|
||||
|
||||
const [watchAmount, watchAllowance, watchClaim] = watch(["amount", "allowance", "claim"]);
|
||||
const [watchAmount, watchAllowance, watchClaim, watchBalance] = watch(["amount", "allowance", "claim", "balance"]);
|
||||
|
||||
// Context
|
||||
const { token, networkLayer, toChainId } = useChainStore((state) => ({
|
||||
@@ -38,18 +39,22 @@ export function Submit({ isLoading = false, isWaitingLoading = false }: SubmitPr
|
||||
},
|
||||
});
|
||||
|
||||
const destinationBalanceTooLow =
|
||||
watchClaim === "manual" && destinationChainBalance && destinationChainBalance.value === 0n;
|
||||
|
||||
const originChainBalanceTooLow =
|
||||
token !== null &&
|
||||
(errors?.amount?.message !== undefined ||
|
||||
parseUnits(watchBalance, token.decimals) < parseUnits(watchAmount, token.decimals));
|
||||
|
||||
const isERC20Token = token && networkLayer !== NetworkLayer.UNKNOWN && token[networkLayer];
|
||||
const isButtonDisabled = !bridgeEnabled(watchAmount, watchAllowance || BigInt(0), errors);
|
||||
const isButtonDisabled = !bridgeEnabled(watchAmount, watchAllowance || BigInt(0), errors) || originChainBalanceTooLow;
|
||||
const isETHTransfer = token && token.symbol === "ETH";
|
||||
const showApproveERC20 =
|
||||
!isETHTransfer &&
|
||||
(!watchAllowance || (token?.decimals && watchAllowance < parseUnits(watchAmount, token.decimals)));
|
||||
|
||||
// TODO: refactor this
|
||||
const destinationBalanceTooLow =
|
||||
watchClaim === "manual" && destinationChainBalance && destinationChainBalance.value === 0n;
|
||||
|
||||
const buttonText = errors?.amount?.message
|
||||
const buttonText = originChainBalanceTooLow
|
||||
? "Insufficient balance"
|
||||
: destinationBalanceTooLow
|
||||
? "Bridge anyway"
|
||||
@@ -65,18 +70,38 @@ export function Submit({ isLoading = false, isWaitingLoading = false }: SubmitPr
|
||||
loading={isLoading || isWaitingLoading}
|
||||
>
|
||||
{buttonText}
|
||||
{destinationBalanceTooLow && (
|
||||
<Tooltip
|
||||
text="You have selected Manual Claim and do not have ETH on the recipient chain to pay for gas. Click this to Bridge Anyway"
|
||||
className="z-[99] normal-case"
|
||||
position="bottom"
|
||||
>
|
||||
<MdInfo />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
) : showApproveERC20 && isERC20Token ? (
|
||||
<ApproveERC20 />
|
||||
) : (
|
||||
<Button
|
||||
id="submit-erc-btn"
|
||||
className="w-full text-lg font-normal"
|
||||
className={cn("w-full text-lg font-normal", {
|
||||
"btn-secondary": destinationBalanceTooLow,
|
||||
})}
|
||||
disabled={isButtonDisabled}
|
||||
loading={isLoading || isWaitingLoading}
|
||||
type="submit"
|
||||
>
|
||||
{buttonText}
|
||||
{destinationBalanceTooLow && (
|
||||
<Tooltip
|
||||
text="You have selected Manual Claim and do not have ETH on the recipient chain to pay for gas. Click this to Bridge Anyway"
|
||||
className="z-[100] normal-case"
|
||||
position="top"
|
||||
>
|
||||
<MdInfo />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import Link from "next/link";
|
||||
import ReloadHistoryButton from "./ReloadHistoryButton";
|
||||
import { useFetchHistory } from "@/hooks";
|
||||
|
||||
export function NoTransactions() {
|
||||
const { clearHistory } = useFetchHistory();
|
||||
return (
|
||||
<div className="flex min-h-80 flex-col items-center justify-center gap-8 rounded-lg border-2 border-card bg-cardBg p-4">
|
||||
<span className="text-[#C0C0C0]">No bridge transactions found</span>
|
||||
<Link href="/" className="btn btn-primary max-w-xs rounded-full uppercase">
|
||||
Bridge assets
|
||||
</Link>
|
||||
<div className="rounded-lg border-2 border-card bg-cardBg p-4">
|
||||
<ReloadHistoryButton clearHistory={clearHistory} />
|
||||
<div className="flex min-h-80 flex-col items-center justify-center gap-8 ">
|
||||
<span className="text-[#C0C0C0]">No bridge transactions found</span>
|
||||
<Link href="/" className="btn btn-primary max-w-xs rounded-full uppercase">
|
||||
Bridge assets
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Button } from "../ui";
|
||||
|
||||
export default function ReloadHistoryButton({ clearHistory }: { clearHistory: () => void }) {
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
id="reload-history-btn"
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="font-light normal-case text-gray-200 no-underline opacity-60 hover:text-primary hover:opacity-100"
|
||||
onClick={clearHistory}
|
||||
>
|
||||
Reload history
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { TransactionHistory } from "@/models/history";
|
||||
import { formatDate, fromUnixTime } from "date-fns";
|
||||
import { NoTransactions } from "./NoTransaction";
|
||||
import { useFetchHistory } from "@/hooks";
|
||||
import { Button } from "../ui";
|
||||
import ReloadHistoryButton from "./ReloadHistoryButton";
|
||||
|
||||
const groupByDay = (transactions: TransactionHistory[]): Record<string, TransactionHistory[]> => {
|
||||
return transactions.reduce(
|
||||
@@ -56,24 +56,6 @@ function SkeletonLoader() {
|
||||
);
|
||||
}
|
||||
|
||||
function ReloadHistoryButton({ clearHistory }: { clearHistory: () => void }) {
|
||||
return (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
id="reload-history-btn"
|
||||
variant="link"
|
||||
size="sm"
|
||||
className="font-light normal-case text-gray-200 no-underline opacity-60 hover:text-primary hover:opacity-100"
|
||||
onClick={() => {
|
||||
clearHistory();
|
||||
}}
|
||||
>
|
||||
Reload history
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TransactionGroup({ date, transactions }: { date: string; transactions: TransactionHistory[] }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -20,13 +20,15 @@ export default function Stepper({ steps, activeStep }: StepperProps) {
|
||||
className={cn(
|
||||
"-mx-px flex size-14 shrink-0 items-center justify-center rounded-full border-2 border-card bg-cardBg p-1.5",
|
||||
{
|
||||
"bg-primary": index < activeStep,
|
||||
"border-primary": index <= activeStep,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn("text-base font-bold text-[#E5E5E5]", {
|
||||
"text-primary": index <= activeStep,
|
||||
"text-cardBg": index < activeStep,
|
||||
"text-primary": index === activeStep,
|
||||
})}
|
||||
>
|
||||
{index + 1}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const wagmiConfig = defaultWagmiConfig({
|
||||
projectId: config.walletConnectId,
|
||||
showQrModal: false,
|
||||
}),
|
||||
injected({ shimDisconnect: true, target: "metaMask" }),
|
||||
injected({ shimDisconnect: true }),
|
||||
coinbaseWallet({
|
||||
appName: "Linea Bridge",
|
||||
}),
|
||||
|
||||
@@ -170,6 +170,11 @@ const useBridge = (): UseBridge => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const amountInteger = parseFloat(amount);
|
||||
if (isNaN(amountInteger) || amountInteger === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check form errors
|
||||
if (!isEmptyObject(errors)) {
|
||||
return false;
|
||||
|
||||
@@ -24,7 +24,7 @@ export const MENU_ITEMS = [
|
||||
},
|
||||
{
|
||||
title: "Docs",
|
||||
href: "https://docs.linea.build/",
|
||||
href: "https://docs.linea.build/developers/guides/bridge/how-to-bridge-eth",
|
||||
external: true,
|
||||
Icon: DocsIcon,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user