diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/amount-input.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/amount-input.tsx new file mode 100644 index 0000000000..2ad421adb2 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/amount-input.tsx @@ -0,0 +1,31 @@ +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { Input } from "../../ui/input"; + +export const AmountInput = ({ + form, + walletConnected, +}: { + form: any; + walletConnected: boolean; +}) => { + return ( + ( + + + + + + + )} + /> + ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/chain-select.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/chain-select.tsx new file mode 100644 index 0000000000..aee2be1637 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/chain-select.tsx @@ -0,0 +1,45 @@ +import { Chain } from "@/src/types"; +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "../../ui/select"; + +export const ChainSelect = ({ + form, + chains, + name, +}: { + form: any; + chains: Chain[]; + name: string; +}) => { + return ( + ( + + + + + )} + /> + ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/index.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/index.tsx index 3ecb6caf54..eca0a6bf42 100644 --- a/contracts/src/bridge/frontend/src/components/modules/bridge/index.tsx +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/index.tsx @@ -6,37 +6,20 @@ import { Card, CardDescription, } from "@/src/components/ui/card"; -import { Button } from "@/src/components/ui/button"; -import { Skeleton } from "@/src/components/ui/skeleton"; -import { ArrowDownUpIcon, Loader, PlusIcon } from "lucide-react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../../ui/select"; import { Separator } from "../../ui/separator"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/src/components/ui/form"; -import { Input } from "@/src/components/ui/input"; +import { Form } from "@/src/components/ui/form"; import { toast } from "@/src/components/ui/use-toast"; import { DrawerDialog } from "../common/drawer-dialog"; -import { L1TOKENS, L2TOKENS, PERCENTAGES } from "@/src/lib/constants"; +import { L1TOKENS, L2TOKENS } from "@/src/lib/constants"; import { z } from "zod"; import { useFormHook } from "@/src/hooks/useForm"; import { useWalletStore } from "../../providers/wallet-provider"; -import { ToastType, Token, WalletNetwork } from "@/src/types"; -import ConnectWalletButton from "../common/connect-wallet"; -import TruncatedAddress from "../common/truncated-address"; +import { ToastType, Token } from "@/src/types"; import { useContract } from "@/src/hooks/useContract"; +import { TransferFromSection } from "./transfer-from-section"; +import { SubmitButton } from "./submit-button"; +import { SwitchNetworkButton } from "./switch-network-button"; +import { TransferToSection } from "./transfer-to-section"; export default function Dashboard() { const { @@ -57,17 +40,11 @@ export default function Dashboard() { const tokens = isL1ToL2 ? L1TOKENS : L2TOKENS; const receiver = form.watch("receiver"); - const swapTokens = () => { - switchNetwork( - isL1ToL2 ? WalletNetwork.L2_TEN_TESTNET : WalletNetwork.L1_SEPOLIA - ); - }; - const [open, setOpen] = React.useState(false); const watchTokenChange = form.watch("token"); React.useEffect(() => { - const tokenBalance = async (value: string, token: Token) => { + const tokenBalance = async (token: Token) => { setLoading(true); try { const balance = token.isNative @@ -84,64 +61,91 @@ export default function Dashboard() { if (watchTokenChange) { const token = tokens.find((t) => t.value === watchTokenChange); if (token) { - tokenBalance(watchTokenChange, token); + tokenBalance(token); } } - }, [watchTokenChange, address, provider]); + }, [watchTokenChange, address, provider, tokens]); - async function onSubmit(data: z.infer) { - try { - setLoading(true); - const transactionData = { ...data, receiver: receiver || address }; - toast({ - title: "Bridge Transaction", - description: "Bridge transaction initiated", - variant: ToastType.INFO, - }); - const token = tokens.find((t) => t.value === transactionData.token); - if (!token) throw new Error("Invalid token"); + const onSubmit = React.useCallback( + async (data: z.infer) => { + try { + setLoading(true); + const transactionData = { ...data, receiver: receiver || address }; + toast({ + title: "Bridge Transaction", + description: "Bridge transaction initiated", + variant: ToastType.INFO, + }); + const token = tokens.find((t) => t.value === transactionData.token); + if (!token) throw new Error("Invalid token"); - const sendTransaction = token.isNative ? sendNative : sendERC20; - const res = await sendTransaction( - transactionData.receiver, - transactionData.amount, - token.address - ); + const sendTransaction = token.isNative ? sendNative : sendERC20; + const res = await sendTransaction( + transactionData.receiver, + transactionData.amount, + token.address + ); - toast({ - title: "Bridge Transaction", - description: `Bridge transaction completed: ${res.transactionHash}`, - variant: ToastType.SUCCESS, - }); - form.reset(); - } catch (error) { - setLoading(false); - console.error(error); - toast({ - title: "Bridge Transaction", - description: `Error: ${ - error instanceof Error - ? error.message - : "initiating bridge transaction" - }`, - variant: ToastType.DESTRUCTIVE, - }); - } finally { - setLoading(false); - } - } + toast({ + title: "Bridge Transaction", + description: `Bridge transaction completed: ${res.transactionHash}`, + variant: ToastType.SUCCESS, + }); + form.reset(); + } catch (error) { + console.error(error); + toast({ + title: "Bridge Transaction", + description: `Error: ${ + error instanceof Error + ? error.message + : "initiating bridge transaction" + }`, + variant: ToastType.DESTRUCTIVE, + }); + } finally { + setLoading(false); + } + }, + [address, form, receiver, sendERC20, sendNative, tokens] + ); - const setAmount = (value: number) => { - if (!form.getValues("token")) { - form.setError("token", { - type: "manual", - message: "Please select a token to get the balance", - }); - return; - } - const amount = Math.floor(((fromTokenBalance * value) / 100) * 100) / 100; - form.setValue("amount", amount.toString()); - }; + const setAmount = React.useCallback( + (value: number) => { + if (!form.getValues("token")) { + form.setError("token", { + type: "manual", + message: "Please select a token to get the balance", + }); + return; + } + const amount = Math.floor(((fromTokenBalance * value) / 100) * 100) / 100; + form.setValue("amount", amount.toString()); + }, + [form, fromTokenBalance] + ); + + const handleSwitchNetwork = React.useCallback( + async (event: any) => { + event.preventDefault(); + try { + setLoading(true); + await switchNetwork(); + } catch (error) { + console.error("Network switch failed", error); + toast({ + title: "Network Switch", + description: `Error: ${ + error instanceof Error ? error.message : "switching network" + }`, + variant: ToastType.DESTRUCTIVE, + }); + } finally { + setLoading(false); + } + }, + [switchNetwork] + ); return (
@@ -157,225 +161,32 @@ export default function Dashboard() {
-
-
- Transfer from - {/* From Chain Select */} - ( - - - - - )} - /> -
- -
-
- {/* Token Select */} - ( - - - - - )} - /> - {/* Balance */} -
-

Balance:

- - {loading ? : fromTokenBalance || 0} - -
-
- -
- {/* Amount Input */} - ( - - - - - - - )} - /> -
- {/* Percentage Buttons */} -
- {PERCENTAGES.map((percentage) => ( - - ))} -
-
-
-
-
- - {/* Swap Bridge Tokens */} -
- -
- - {/* Transfer to */} -
-
- Transfer to - {/* To Chain Select */} - ( - - - - - )} - /> -
-
- {/* Destination Address Input */} - -
-
-
- - {form.getValues().token} - -
-

- You will receive: -

- - {loading ? : form.watch("amount") || 0} - -
-
-
-
- Receiver Address -
- {receiver || address ? ( - - ) : null} -
-
-
- -
- {walletConnected ? ( - - ) : ( - - )} -
+ + + + diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/percentage-buttons.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/percentage-buttons.tsx new file mode 100644 index 0000000000..c340a17fbb --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/percentage-buttons.tsx @@ -0,0 +1,23 @@ +import { PERCENTAGES } from "@/src/lib/constants"; +import { Button } from "../../ui/button"; + +export const PercentageButtons = ({ setAmount }: any) => { + return ( +
+
+ {PERCENTAGES.map((percentage) => ( + + ))} +
+
+ ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/submit-button.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/submit-button.tsx new file mode 100644 index 0000000000..ea20bce387 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/submit-button.tsx @@ -0,0 +1,29 @@ +import { Loader } from "lucide-react"; +import { Button } from "../../ui/button"; +import ConnectWalletButton from "../common/connect-wallet"; + +export const SubmitButton = ({ + walletConnected, + loading, + fromTokenBalance, +}: any) => { + return ( +
+ {walletConnected ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/switch-network-button.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/switch-network-button.tsx new file mode 100644 index 0000000000..1fb3b90096 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/switch-network-button.tsx @@ -0,0 +1,19 @@ +import { ArrowDownUpIcon, Loader } from "lucide-react"; +import { Button } from "../../ui/button"; + +export const SwitchNetworkButton = ({ handleSwitchNetwork, loading }: any) => { + return ( +
+ +
+ ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/token-select.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/token-select.tsx new file mode 100644 index 0000000000..3afed14c60 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/token-select.tsx @@ -0,0 +1,40 @@ +import { FormField, FormItem, FormControl, FormMessage } from "../../ui/form"; +import { + Select, + SelectTrigger, + SelectContent, + SelectItem, + SelectValue, +} from "../../ui/select"; + +export const TokenSelect = ({ form, tokens }: any) => { + return ( + ( + + + + + )} + /> + ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-from-section.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-from-section.tsx new file mode 100644 index 0000000000..715f4e32c6 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-from-section.tsx @@ -0,0 +1,50 @@ +import { Chain, Token } from "@/src/types"; +import { Separator } from "../../ui/separator"; +import { Skeleton } from "../../ui/skeleton"; +import { AmountInput } from "./amount-input"; +import { ChainSelect } from "./chain-select"; +import { PercentageButtons } from "./percentage-buttons"; +import { TokenSelect } from "./token-select"; + +export const TransferFromSection = ({ + form, + fromChains, + tokens, + fromTokenBalance, + loading, + setAmount, + walletConnected, +}: { + form: any; + fromChains: Chain[]; + tokens: Token[]; + fromTokenBalance: string; + loading: boolean; + setAmount: (value: number) => void; + walletConnected: boolean; +}) => { + return ( +
+
+ Transfer from + +
+
+
+ +
+

Balance:

+ + {loading ? : fromTokenBalance || 0} + +
+
+ +
+ + +
+
+
+ ); +}; diff --git a/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-to-section.tsx b/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-to-section.tsx new file mode 100644 index 0000000000..0ae89d3cd5 --- /dev/null +++ b/contracts/src/bridge/frontend/src/components/modules/bridge/transfer-to-section.tsx @@ -0,0 +1,60 @@ +import { PlusIcon } from "lucide-react"; +import { Button } from "../../ui/button"; +import { Skeleton } from "../../ui/skeleton"; +import TruncatedAddress from "../common/truncated-address"; +import { ChainSelect } from "./chain-select"; +import { Chain } from "@/src/types"; + +export const TransferToSection = ({ + form, + toChains, + loading, + receiver, + address, + setOpen, +}: { + form: any; + toChains: Chain[]; + loading: boolean; + receiver?: string; + address: string; + setOpen: (open: boolean) => void; +}) => { + return ( +
+
+ Transfer to + +
+
+ +
+
+
+ {form.getValues().token} +
+

You will receive:

+ + {loading ? : form.watch("amount") || 0} + +
+
+
+
+ To: + {receiver ? ( + + ) : address ? ( + + ) : null} +
+
+ ); +};