diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 9c02a64..587bd08 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -12,7 +12,7 @@ const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => { return ( - + {children} diff --git a/web/app/page.tsx b/web/app/page.tsx index ddfcdf8..5c6531e 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,14 +1,17 @@ "use client"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useMemo, useState } from "react"; +import Image from "next/image"; import toast from "react-hot-toast"; import { useBoolean } from "usehooks-ts"; import { parseUnits } from "viem"; -import { formatEther } from "viem"; import { useAccount, useWriteContract } from "wagmi"; -import { ArrowsUpDownIcon, LockClosedIcon } from "@heroicons/react/24/outline"; +import { ArrowsUpDownIcon, LockClosedIcon, WalletIcon } from "@heroicons/react/24/outline"; +import { Button } from "~~/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "~~/components/ui/card"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~~/components/ui/select"; import { CPAMM_ABI, CPAMM_ADDRESS } from "~~/contracts/cpamm"; -import { DAI_TOKEN, ERC20_ABI, WBTC_TOKEN } from "~~/contracts/tokens"; +import { DAI_TOKEN, ERC20_ABI, Token, WBTC_TOKEN } from "~~/contracts/tokens"; import useCpamm from "~~/hooks/use-cpamm"; import useDaiToken from "~~/hooks/use-dai-token"; import useWbtcToken from "~~/hooks/use-wbtc-token"; @@ -16,14 +19,27 @@ import { cn } from "~~/utils/cn"; import { formatTokenWithDecimals } from "~~/utils/currency"; import waitForTransactionReceipt from "~~/utils/wait-for-transaction"; +interface SwapState { + from: { + token: Token; + amount: bigint; + displayAmount: string; + }; + to: { + token: Token; + amount: bigint; + displayAmount: string; + }; +} + export default function UniswapClone() { const dai = useDaiToken(); const wbtc = useWbtcToken(); const { writeContractAsync } = useWriteContract(); - const { daiPoolLiquidity, wbtcPoolLiquidity, refetchAll } = useCpamm(); + const { daiPoolLiquidity, wbtcPoolLiquidity } = useCpamm(); const { isConnected } = useAccount(); const { value: isSwapping, setValue: setIsSwapping } = useBoolean(false); - const [swapState, setSwapState] = useState({ + const [swapState, setSwapState] = useState({ from: { token: DAI_TOKEN, amount: 0n, @@ -36,13 +52,6 @@ export default function UniswapClone() { }, }); - useEffect(() => { - const interval = setInterval(() => { - refetchAll(); - }, 5000); - return () => clearInterval(interval); - }, [refetchAll]); - const fromBalance = swapState.from.token.symbol === "DAI" ? dai.balance : wbtc.balance; const toBalance = swapState.to.token.symbol === "DAI" ? dai.balance : wbtc.balance; @@ -84,7 +93,13 @@ export default function UniswapClone() { } try { - const newAmount = parseUnits(newDisplayAmount, 18); + let newAmount: bigint; + try { + newAmount = parseUnits(newDisplayAmount, 18); + } catch { + // Invalid input, do nothing + return; + } const newAmountWithFee = (newAmount * 997n) / 1000n; // 0.3% fee const oppositeAmount = (() => { @@ -112,7 +127,7 @@ export default function UniswapClone() { [oppositeTokenType]: { ...prev[oppositeTokenType], amount: oppositeAmount, - displayAmount: formatEther(oppositeAmount), + displayAmount: formatTokenWithDecimals(oppositeAmount, 18), }, })); } catch (error) { @@ -120,8 +135,8 @@ export default function UniswapClone() { } }; - const handleTokenChange = (e: React.ChangeEvent, tokenType: "from" | "to") => { - const newToken = e.target.value === "DAI" ? DAI_TOKEN : WBTC_TOKEN; + const handleTokenChange = (value: string, tokenType: "from" | "to") => { + const newToken = value === "DAI" ? DAI_TOKEN : WBTC_TOKEN; if (swapState[tokenType].token.symbol === newToken.symbol) { return; } @@ -148,7 +163,7 @@ export default function UniswapClone() { }; const handleSwap = async () => { - if (!swapState.from.amount || !swapState.to.amount || !fromAllowance) { + if (!swapState.from.amount || !swapState.to.amount || fromAllowance === undefined) { return; } @@ -228,105 +243,59 @@ export default function UniswapClone() { return (
-
-
{ - e.preventDefault(); - handleSwap(); - }} - > -

Swap Tokens

-
- - - handleAmountChange(e, "from")} - disabled={isSwapping} - /> -
+ { + e.preventDefault(); + handleSwap(); + }} + > +

Swap

-
- -
+ handleAmountChange(e, "from")} + onTokenChange={e => handleTokenChange(e, "from")} + disabled={isSwapping} + /> -
- - - handleAmountChange(e, "to")} - disabled={isSwapping} - /> -
+
+ +
-
-

- Swap Price: 1 {swapState.from.token.symbol} = {formatTokenWithDecimals(swapPrice, 18)}{" "} - {swapState.to.token.symbol} -

-
+ handleAmountChange(e, "to")} + onTokenChange={e => handleTokenChange(e, "to")} + disabled={isSwapping} + /> -
- -
- -
+
+ Swap Price: 1 {swapState.from.token.symbol} = {formatTokenWithDecimals(swapPrice, 18)}{" "} + {swapState.to.token.symbol} +
+ + + {!isConnected && (
-
+
Connect your wallet to start
@@ -335,3 +304,67 @@ export default function UniswapClone() {
); } + +function SwapCard({ + heading, + balance, + onAmountChange, + onTokenChange, + disabled, + displayAmount, + token, + selectedToken, +}: { + heading: string; + balance: bigint; + displayAmount: string; + token: Token; + onAmountChange: (e: React.ChangeEvent) => void; + selectedToken: Token["symbol"]; + onTokenChange: (value: string) => void; + disabled: boolean; +}) { + return ( + + + {heading} + + +
+ +
+ +
+
+
+ + {formatTokenWithDecimals(balance, token.decimals)} +
+
+
+ ); +} diff --git a/web/app/pool/add/page.tsx b/web/app/pool/add/page.tsx new file mode 100644 index 0000000..8e9739d --- /dev/null +++ b/web/app/pool/add/page.tsx @@ -0,0 +1,341 @@ +"use client"; + +import React, { useState } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import toast from "react-hot-toast"; +import { useBoolean } from "usehooks-ts"; +import { parseUnits } from "viem"; +import { useAccount, useWriteContract } from "wagmi"; +import { AdjustmentsHorizontalIcon, LockClosedIcon, PlusCircleIcon, WalletIcon } from "@heroicons/react/24/outline"; +import { Button } from "~~/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "~~/components/ui/card"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~~/components/ui/select"; +import { CPAMM_ABI, CPAMM_ADDRESS } from "~~/contracts/cpamm"; +import { DAI_TOKEN, Token, WBTC_TOKEN } from "~~/contracts/tokens"; +import useCpamm from "~~/hooks/use-cpamm"; +import useDaiToken from "~~/hooks/use-dai-token"; +import useWbtcToken from "~~/hooks/use-wbtc-token"; +import { cn } from "~~/utils/cn"; +import { formatTokenWithDecimals } from "~~/utils/currency"; +import waitForTransactionReceipt from "~~/utils/wait-for-transaction"; + +interface PoolState { + tokenA: Token; + tokenB: Token; + amountA: bigint; + amountB: bigint; + displayAmountA: string; + displayAmountB: string; +} + +export default function AddLiquidity() { + const dai = useDaiToken(); + const wbtc = useWbtcToken(); + const { daiPoolLiquidity, wbtcPoolLiquidity, refetchAll: refetchCpamm } = useCpamm(); + const [poolState, setPoolState] = useState({ + tokenA: DAI_TOKEN, + tokenB: WBTC_TOKEN, + amountA: 0n, + amountB: 0n, + displayAmountA: "", + displayAmountB: "", + }); + const { isConnected } = useAccount(); + const { writeContractAsync } = useWriteContract(); + const { value: isAddingLiquidity, setValue: setIsAddingLiquidity } = useBoolean(false); + + const tokenABalance = poolState.tokenA.symbol === DAI_TOKEN.symbol ? dai.balance : wbtc.balance; + const tokenBBalance = poolState.tokenB.symbol === DAI_TOKEN.symbol ? dai.balance : wbtc.balance; + + const handleTokenSelect = (value: string, tokenType: "tokenA" | "tokenB") => { + const selected = value === DAI_TOKEN.symbol ? DAI_TOKEN : WBTC_TOKEN; + const nonSelected = selected === DAI_TOKEN ? WBTC_TOKEN : DAI_TOKEN; + const otherTokenType = tokenType === "tokenA" ? "tokenB" : "tokenA"; + + setPoolState(prev => ({ + ...prev, + [tokenType]: selected, + [otherTokenType]: nonSelected, + amountA: prev.amountB, + amountB: prev.amountA, + displayAmountA: prev.displayAmountB, + displayAmountB: prev.displayAmountA, + })); + }; + + const handleAmountChange = async (e: React.ChangeEvent, tokenType: "tokenA" | "tokenB") => { + const newDisplayAmount = e.target.value; + + // If input is empty, reset both amounts + if (!newDisplayAmount) { + setPoolState(prev => ({ + ...prev, + amountA: 0n, + amountB: 0n, + displayAmountA: "", + displayAmountB: "", + })); + return; + } + + try { + const newAmount = parseUnits(newDisplayAmount.replace(/,/g, ""), 18); + const reserve0 = daiPoolLiquidity ?? 0n; + const reserve1 = wbtcPoolLiquidity ?? 0n; + + // If pool is empty, allow any ratio + if (reserve0 === 0n || reserve1 === 0n) { + setPoolState(prev => ({ + ...prev, + [tokenType === "tokenA" ? "amountA" : "amountB"]: newAmount, + [tokenType === "tokenA" ? "displayAmountA" : "displayAmountB"]: newDisplayAmount, + })); + return; + } + + // Calculate the other amount based on the constant product formula + const isTokenADai = poolState.tokenA.symbol === "DAI"; + let calculatedAmount: bigint; + + if ((tokenType === "tokenA" && isTokenADai) || (tokenType === "tokenB" && !isTokenADai)) { + calculatedAmount = (newAmount * reserve1) / reserve0; + } else { + calculatedAmount = (newAmount * reserve0) / reserve1; + } + + setPoolState(prev => ({ + ...prev, + [tokenType === "tokenA" ? "amountA" : "amountB"]: newAmount, + [tokenType === "tokenA" ? "amountB" : "amountA"]: calculatedAmount, + [tokenType === "tokenA" ? "displayAmountA" : "displayAmountB"]: newDisplayAmount, + [tokenType === "tokenA" ? "displayAmountB" : "displayAmountA"]: formatTokenWithDecimals(calculatedAmount, 18), + })); + } catch (error) { + console.error("Error calculating amounts:", error); + } + }; + + const handleAddLiquidity = async () => { + if (poolState.amountA === 0n || poolState.amountB === 0n) { + return; + } + + setIsAddingLiquidity(true); + + const tokenA = poolState.tokenA.symbol === "DAI" ? dai : wbtc; + const tokenB = poolState.tokenB.symbol === "DAI" ? dai : wbtc; + + try { + // Check allowances for both tokens + if ((tokenA.allowance ?? 0n) < poolState.amountA) { + const approveTokenA = await toast.promise(tokenA.approve(poolState.amountA), { + loading: `Approving ${poolState.tokenA.symbol}...`, + success: `${poolState.tokenA.symbol} approved!`, + error: err => { + console.error(err); + return `Failed to approve ${poolState.tokenA.symbol}`; + }, + }); + await toast.promise(waitForTransactionReceipt({ hash: approveTokenA }), { + loading: `Waiting for ${poolState.tokenA.symbol} approval confirmation...`, + success: `${poolState.tokenA.symbol} approval confirmed!`, + error: err => { + console.error(err); + return `Failed to approve ${poolState.tokenA.symbol}`; + }, + }); + } + + if ((tokenB.allowance ?? 0n) < poolState.amountB) { + const approveTokenB = await toast.promise(tokenB.approve(poolState.amountB), { + loading: `Approving ${poolState.tokenB.symbol}...`, + success: `${poolState.tokenB.symbol} approved!`, + error: err => { + console.error(err); + return `Failed to approve ${poolState.tokenB.symbol}`; + }, + }); + await toast.promise(waitForTransactionReceipt({ hash: approveTokenB }), { + loading: `Waiting for ${poolState.tokenB.symbol} approval confirmation...`, + success: `${poolState.tokenB.symbol} approval confirmed!`, + error: err => { + console.error(err); + return `Failed to approve ${poolState.tokenB.symbol}`; + }, + }); + } + + // Add liquidity using the actual BigInt values + const addLiquidity = await toast.promise( + writeContractAsync({ + abi: CPAMM_ABI, + address: CPAMM_ADDRESS, + functionName: "addLiquidity", + args: [poolState.amountA, poolState.amountB], + }), + { + loading: "Adding liquidity...", + success: "Liquidity added successfully!", + error: err => { + console.error(err); + return "Failed to add liquidity"; + }, + }, + ); + + await toast.promise(waitForTransactionReceipt({ hash: addLiquidity }), { + loading: "Waiting for liquidity confirmation...", + success: "Liquidity confirmed!", + error: err => { + console.error(err); + return "Failed to add liquidity"; + }, + }); + + // Reset everything + setPoolState(prev => ({ + ...prev, + amountA: 0n, + amountB: 0n, + displayAmountA: "", + displayAmountB: "", + })); + } finally { + dai.refetchAll(); + wbtc.refetchAll(); + refetchCpamm(); + setIsAddingLiquidity(false); + } + }; + + return ( +
+
+

Add Liquidity

+ + + + +
+
+
{ + e.preventDefault(); + handleAddLiquidity(); + }} + > + handleAmountChange(e, "tokenA")} + onTokenChange={e => handleTokenSelect(e, "tokenA")} + disabled={isAddingLiquidity} + /> + + + + handleAmountChange(e, "tokenB")} + onTokenChange={e => handleTokenSelect(e, "tokenB")} + disabled={isAddingLiquidity} + /> + + + +
+ + {!isConnected && ( +
+
+ + Connect your wallet to add liquidity +
+
+ )} +
+
+
+ ); +} + +function PoolCard({ + heading, + balance, + onAmountChange, + onTokenChange, + disabled, + displayAmount, + token, + selectedToken, +}: { + heading: string; + balance: bigint; + displayAmount: string; + token: Token; + onAmountChange: (e: React.ChangeEvent) => void; + selectedToken: Token["symbol"]; + onTokenChange: (value: string) => void; + disabled: boolean; +}) { + return ( + + + {heading} + + +
+ +
+ +
+
+
+ + {formatTokenWithDecimals(balance, token.decimals)} +
+
+
+ ); +} diff --git a/web/app/pool/page.tsx b/web/app/pool/page.tsx index 69e4c5a..ad40365 100644 --- a/web/app/pool/page.tsx +++ b/web/app/pool/page.tsx @@ -1,337 +1,80 @@ "use client"; import React, { useState } from "react"; +import Link from "next/link"; import toast from "react-hot-toast"; -import { useBoolean } from "usehooks-ts"; -import { formatEther, parseUnits } from "viem"; -import { useAccount, useWriteContract } from "wagmi"; -import { LockClosedIcon } from "@heroicons/react/24/outline"; -import { CPAMM_ABI, CPAMM_ADDRESS } from "~~/contracts/cpamm"; -import { DAI_TOKEN, WBTC_TOKEN } from "~~/contracts/tokens"; +import { PlusIcon } from "@heroicons/react/24/solid"; +import { Button } from "~~/components/ui/button"; +import { Card } from "~~/components/ui/card"; +import { CPAMM_ADDRESS } from "~~/contracts/cpamm"; +import { CPAMM_ABI } from "~~/contracts/cpamm"; import useCpamm from "~~/hooks/use-cpamm"; -import useDaiToken from "~~/hooks/use-dai-token"; -import useWbtcToken from "~~/hooks/use-wbtc-token"; -import { cn } from "~~/utils/cn"; import { formatTokenWithDecimals } from "~~/utils/currency"; import waitForTransactionReceipt from "~~/utils/wait-for-transaction"; -interface Token { - symbol: string; - name: string; - address: string; -} - -interface PoolState { - tokenA: Token; - tokenB: Token; - amountA: bigint; - amountB: bigint; - displayAmountA: string; - displayAmountB: string; -} - export default function AddLiquidity() { - const dai = useDaiToken(); - const wbtc = useWbtcToken(); - const { daiPoolLiquidity, wbtcPoolLiquidity, refetchAll: refetchCpamm } = useCpamm(); - const [poolState, setPoolState] = useState({ - tokenA: DAI_TOKEN, - tokenB: WBTC_TOKEN, - amountA: 0n, - amountB: 0n, - displayAmountA: "", - displayAmountB: "", - }); - const { isConnected } = useAccount(); - const { writeContractAsync } = useWriteContract(); - const { value: isAddingLiquidity, setValue: setIsAddingLiquidity } = useBoolean(false); - - const tokenABalance = poolState.tokenA.symbol === DAI_TOKEN.symbol ? dai.balance : wbtc.balance; - const tokenBBalance = poolState.tokenB.symbol === DAI_TOKEN.symbol ? dai.balance : wbtc.balance; - - const handleTokenSelect = (e: React.ChangeEvent, tokenType: "tokenA" | "tokenB") => { - const selected = e.target.value === DAI_TOKEN.symbol ? DAI_TOKEN : WBTC_TOKEN; - const nonSelected = selected === DAI_TOKEN ? WBTC_TOKEN : DAI_TOKEN; - const otherTokenType = tokenType === "tokenA" ? "tokenB" : "tokenA"; - - setPoolState(prev => ({ - ...prev, - [tokenType]: selected, - [otherTokenType]: nonSelected, - amountA: prev.amountB, - amountB: prev.amountA, - displayAmountA: prev.displayAmountB, - displayAmountB: prev.displayAmountA, - })); - }; - - const handleAmountChange = async (e: React.ChangeEvent, tokenType: "tokenA" | "tokenB") => { - const newDisplayAmount = e.target.value; - - // If input is empty, reset both amounts - if (!newDisplayAmount) { - setPoolState(prev => ({ - ...prev, - amountA: 0n, - amountB: 0n, - displayAmountA: "", - displayAmountB: "", - })); - return; - } - - try { - const newAmount = parseUnits(newDisplayAmount, 18); - const reserve0 = daiPoolLiquidity ?? 0n; - const reserve1 = wbtcPoolLiquidity ?? 0n; - - // If pool is empty, allow any ratio - if (reserve0 === 0n || reserve1 === 0n) { - setPoolState(prev => ({ - ...prev, - [tokenType === "tokenA" ? "amountA" : "amountB"]: newAmount, - [tokenType === "tokenA" ? "displayAmountA" : "displayAmountB"]: newDisplayAmount, - })); - return; - } - - // Calculate the other amount based on the constant product formula - const isTokenADai = poolState.tokenA.symbol === "DAI"; - let calculatedAmount: bigint; - - if ((tokenType === "tokenA" && isTokenADai) || (tokenType === "tokenB" && !isTokenADai)) { - calculatedAmount = (newAmount * reserve1) / reserve0; - } else { - calculatedAmount = (newAmount * reserve0) / reserve1; - } - - setPoolState(prev => ({ - ...prev, - [tokenType === "tokenA" ? "amountA" : "amountB"]: newAmount, - [tokenType === "tokenA" ? "amountB" : "amountA"]: calculatedAmount, - [tokenType === "tokenA" ? "displayAmountA" : "displayAmountB"]: newDisplayAmount, - [tokenType === "tokenA" ? "displayAmountB" : "displayAmountA"]: formatEther(calculatedAmount), - })); - } catch (error) { - console.error("Error calculating amounts:", error); - } - }; - - const handleAddLiquidity = async () => { - if (poolState.amountA === 0n || poolState.amountB === 0n) { - return; - } - - setIsAddingLiquidity(true); - - const tokenA = poolState.tokenA.symbol === "DAI" ? dai : wbtc; - const tokenB = poolState.tokenB.symbol === "DAI" ? dai : wbtc; - - try { - // Check allowances for both tokens - if ((tokenA.allowance ?? 0n) < poolState.amountA) { - const approveTokenA = await toast.promise(tokenA.approve(poolState.amountA), { - loading: `Approving ${poolState.tokenA.symbol}...`, - success: `${poolState.tokenA.symbol} approved!`, - error: err => { - console.error(err); - return `Failed to approve ${poolState.tokenA.symbol}`; - }, - }); - await toast.promise(waitForTransactionReceipt({ hash: approveTokenA }), { - loading: `Waiting for ${poolState.tokenA.symbol} approval confirmation...`, - success: `${poolState.tokenA.symbol} approval confirmed!`, - error: err => { - console.error(err); - return `Failed to approve ${poolState.tokenA.symbol}`; - }, - }); - } - - if ((tokenB.allowance ?? 0n) < poolState.amountB) { - const approveTokenB = await toast.promise(tokenB.approve(poolState.amountB), { - loading: `Approving ${poolState.tokenB.symbol}...`, - success: `${poolState.tokenB.symbol} approved!`, - error: err => { - console.error(err); - return `Failed to approve ${poolState.tokenB.symbol}`; - }, - }); - await toast.promise(waitForTransactionReceipt({ hash: approveTokenB }), { - loading: `Waiting for ${poolState.tokenB.symbol} approval confirmation...`, - success: `${poolState.tokenB.symbol} approval confirmed!`, - error: err => { - console.error(err); - return `Failed to approve ${poolState.tokenB.symbol}`; - }, - }); - } - - // Add liquidity using the actual BigInt values - const addLiquidity = await toast.promise( - writeContractAsync({ - abi: CPAMM_ABI, - address: CPAMM_ADDRESS, - functionName: "addLiquidity", - args: [poolState.amountA, poolState.amountB], - }), - { - loading: "Adding liquidity...", - success: "Liquidity added successfully!", - error: err => { - console.error(err); - return "Failed to add liquidity"; - }, - }, - ); - - await toast.promise(waitForTransactionReceipt({ hash: addLiquidity }), { - loading: "Waiting for liquidity confirmation...", - success: "Liquidity confirmed!", + const { userShares, writeContractAsync } = useCpamm(); + const [removingLiquidity, setRemovingLiquidity] = useState(false); + + const formattedShares = formatTokenWithDecimals(userShares ?? 0n, 18); + + const handleRemoveLiquidity = async () => { + if (!userShares) return; + + setRemovingLiquidity(true); + const tx = await toast.promise( + writeContractAsync({ + address: CPAMM_ADDRESS, + abi: CPAMM_ABI, + functionName: "removeLiquidity", + args: [userShares ?? 0n], + }), + { + success: "Liquidity removed successfully", + loading: "Removing liquidity...", error: err => { console.error(err); - return "Failed to add liquidity"; + return "Failed to remove liquidity"; }, - }); - - // Reset everything - setPoolState(prev => ({ - ...prev, - amountA: 0n, - amountB: 0n, - displayAmountA: "", - displayAmountB: "", - })); - } finally { - dai.refetchAll(); - wbtc.refetchAll(); - refetchCpamm(); - setIsAddingLiquidity(false); - } + }, + ); + await toast.promise(waitForTransactionReceipt({ hash: tx }), { + success: "Liquidity removed successfully", + loading: "Waiting for confirmation...", + error: err => { + console.error(err); + return "Failed to remove liquidity"; + }, + }); }; return ( -
-
-
-

Add Liquidity

- -
{ - e.preventDefault(); - handleAddLiquidity(); - }} - > -
-

Current Pool Liquidity

-

{formatTokenWithDecimals(daiPoolLiquidity ?? 0n, 18)} DAI

-

{formatTokenWithDecimals(wbtcPoolLiquidity ?? 0n, 18)} WBTC

-
- -
- - -
- -
- - handleAmountChange(e, "tokenA")} - min={0} - disabled={isAddingLiquidity} - /> -
- -
AND
- -
- - -
- -
- - handleAmountChange(e, "tokenB")} - min={0} - disabled={isAddingLiquidity} - /> -
- -
- -
-
-
- - {!isConnected && ( -
-
- - Connect your wallet to add liquidity -
-
- )} +
+
+

My Positions

+ + + + + {userShares ? ( +

+ {formattedShares} share{formattedShares === "1" ? "" : "s"} +

+ ) : ( +

No positions found

+ )} +
+
); diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..b710aa9 --- /dev/null +++ b/web/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "styles/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "~~/components", + "utils": "~~/utils/cn", + "ui": "~~/components/ui", + "lib": "~~/lib", + "hooks": "~~/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/web/components/Footer.tsx b/web/components/Footer.tsx index 61a77ff..aa2f351 100644 --- a/web/components/Footer.tsx +++ b/web/components/Footer.tsx @@ -1,6 +1,6 @@ import React from "react"; import Image from "next/image"; -import { HeartIcon } from "@heroicons/react/24/outline"; +import { HeartIcon } from "@heroicons/react/24/solid"; /** * Site footer @@ -12,7 +12,7 @@ export const Footer = () => {
ยท @@ -30,7 +30,7 @@ export const Footer = () => { href="https://github.com/moonsonglabs/double-zero-dapp" target="_blank" rel="noreferrer" - className="link" + className="hover:underline" > Source diff --git a/web/components/Header.tsx b/web/components/Header.tsx index 1df5d1f..f03fdb1 100644 --- a/web/components/Header.tsx +++ b/web/components/Header.tsx @@ -1,27 +1,27 @@ "use client"; -import React, { useCallback, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Bars3Icon } from "@heroicons/react/24/outline"; -import { RainbowKitCustomConnectButton } from "~~/components/RainbowKitCustomConnectButton"; -import { useOutsideClick } from "~~/hooks/use-outside-click"; +import { RainbowKitCustomConnectButton } from "./RainbowKitCustomConnectButton"; type HeaderMenuLink = { label: string; href: string; icon?: React.ReactNode; + matches: string[]; }; const menuLinks: HeaderMenuLink[] = [ { label: "Swap", href: "/", + matches: ["/"], }, { label: "Pool", href: "/pool", + matches: ["/pool", "/pool/add"], }, ]; @@ -30,16 +30,16 @@ const HeaderMenuLinks = () => { return ( <> - {menuLinks.map(({ label, href, icon }) => { - const isActive = pathname === href; + {menuLinks.map(({ label, href, icon, matches }) => { + const isActive = matches.some(match => pathname == match); return (
  • {icon} {label} @@ -55,43 +55,14 @@ const HeaderMenuLinks = () => { * Site header */ export const Header = () => { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - const burgerMenuRef = useRef(null); - useOutsideClick( - burgerMenuRef, - useCallback(() => setIsDrawerOpen(false), []), - ); - return ( -
    +
    -
    - - {isDrawerOpen && ( -
      { - setIsDrawerOpen(false); - }} - > - -
    - )} -
    - Double Zero Swap logo + Double Zero Swap logo
    - Double Zero Swap + Double Zero Swap
      diff --git a/web/components/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx b/web/components/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx index 2c3c98a..de1b903 100644 --- a/web/components/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx +++ b/web/components/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx @@ -1,17 +1,14 @@ import { useRef, useState } from "react"; +import { Button } from "../ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu"; import CopyToClipboard from "react-copy-to-clipboard"; import { getAddress } from "viem"; import { Address } from "viem"; import { useDisconnect } from "wagmi"; -import { - ArrowLeftOnRectangleIcon, - CheckCircleIcon, - ChevronDownIcon, - DocumentDuplicateIcon, - QrCodeIcon, -} from "@heroicons/react/24/outline"; +import { ArrowLeftOnRectangleIcon, CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline"; import { BlockieAvatar } from "~~/components/BlockieAvatar"; import { useOutsideClick } from "~~/hooks/use-outside-click"; +import { cn } from "~~/utils/cn"; type AddressInfoDropdownProps = { address: Address; @@ -33,63 +30,63 @@ export const AddressInfoDropdown = ({ address, ensAvatar }: AddressInfoDropdownP useOutsideClick(dropdownRef, closeDropdown); return ( - <> -
      - + + + -
        -
      • - {addressCopied ? ( -
        - + + +
        + {addressCopied ? ( + + + ) : ( + { + setAddressCopied(true); + setTimeout(() => { + setAddressCopied(false); + }, 800); + }} + > + +
        - ) : ( - { - setAddressCopied(true); - setTimeout(() => { - setAddressCopied(false); - }, 800); - }} - > -
        -
        -
        - )} -
      • -
      • - -
      • -
      • - -
      • -
      -
      - + Copy address + + + )} +
    + disconnect()} + > + Disconnect + + + ); }; + +function DropdownButton({ + children, + className, + onClick, +}: { + children: React.ReactNode; + className?: string; + onClick?: () => void; +}) { + return ( + + ); +} diff --git a/web/components/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx b/web/components/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx index 7dcdcce..ac1fa5f 100644 --- a/web/components/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx +++ b/web/components/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx @@ -1,3 +1,5 @@ +import { Button } from "../ui/button"; +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/dropdown-menu"; import { useDisconnect } from "wagmi"; import { ArrowLeftOnRectangleIcon, ArrowTopRightOnSquareIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; import { env } from "~~/utils/env"; @@ -6,35 +8,30 @@ export const WrongNetworkDropdown = () => { const { disconnect } = useDisconnect(); return ( -
    - - -
    + + disconnect()} className="text-red-500 flex gap-3 py-3 hover:text-red-500"> + + Disconnect + + + ); }; diff --git a/web/components/RainbowKitCustomConnectButton/index.tsx b/web/components/RainbowKitCustomConnectButton/index.tsx index c396463..82b3b4f 100644 --- a/web/components/RainbowKitCustomConnectButton/index.tsx +++ b/web/components/RainbowKitCustomConnectButton/index.tsx @@ -2,8 +2,8 @@ // @refresh reset import { Balance } from "../Balance"; +import { Button } from "../ui/button"; import { AddressInfoDropdown } from "./AddressInfoDropdown"; -import { AddressQRCodeModal } from "./AddressQRCodeModal"; import { WrongNetworkDropdown } from "./WrongNetworkDropdown"; import { ConnectButton } from "@rainbow-me/rainbowkit"; import { Address } from "viem"; @@ -23,9 +23,9 @@ export const RainbowKitCustomConnectButton = () => { {(() => { if (!connected) { return ( - + ); } @@ -34,14 +34,13 @@ export const RainbowKitCustomConnectButton = () => { } return ( - <> -
    +
    +
    {chain.name}
    - - +
    ); })()} diff --git a/web/components/ui/button.tsx b/web/components/ui/button.tsx new file mode 100644 index 0000000..c115d21 --- /dev/null +++ b/web/components/ui/button.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import { cn } from "~~/utils/cn"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/web/components/ui/card.tsx b/web/components/ui/card.tsx new file mode 100644 index 0000000..99d6810 --- /dev/null +++ b/web/components/ui/card.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; +import { cn } from "~~/utils/cn"; + +const Card = React.forwardRef>(({ className, ...props }, ref) => ( +
    +)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
    + ), +); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +
    + ), +); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +
    + ), +); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) =>
    , +); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
    + ), +); +CardFooter.displayName = "CardFooter"; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/web/components/ui/dropdown-menu.tsx b/web/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..a21b891 --- /dev/null +++ b/web/components/ui/dropdown-menu.tsx @@ -0,0 +1,181 @@ +"use client"; + +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import { cn } from "~~/utils/cn"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className, + )} + {...props} + /> +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { + return ; +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/web/components/ui/select.tsx b/web/components/ui/select.tsx new file mode 100644 index 0000000..dfbcf10 --- /dev/null +++ b/web/components/ui/select.tsx @@ -0,0 +1,143 @@ +"use client"; + +import * as React from "react"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import { cn } from "~~/utils/cn"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/web/contracts/tokens.ts b/web/contracts/tokens.ts index c89b643..007e6f3 100644 --- a/web/contracts/tokens.ts +++ b/web/contracts/tokens.ts @@ -82,12 +82,16 @@ export const DAI_TOKEN = { symbol: "DAI", name: "DAI", address: env.NEXT_PUBLIC_DAI_ADDRESS, + logo: "/dai-badge.webp", + decimals: 18, }; export const WBTC_TOKEN = { symbol: "WBTC", name: "Wrapped Bitcoin", address: env.NEXT_PUBLIC_WBTC_ADDRESS, + logo: "/wbtc-badge.webp", + decimals: 18, }; export type Token = typeof DAI_TOKEN | typeof WBTC_TOKEN; diff --git a/web/hooks/use-cpamm.ts b/web/hooks/use-cpamm.ts index 0dfb5e4..ad68eb4 100644 --- a/web/hooks/use-cpamm.ts +++ b/web/hooks/use-cpamm.ts @@ -1,14 +1,28 @@ import { useCallback } from "react"; -import { useReadContract, useWriteContract } from "wagmi"; +import { useAccount, useReadContract, useWriteContract } from "wagmi"; import { CPAMM_ABI, CPAMM_ADDRESS } from "~~/contracts/cpamm"; export default function useCpamm() { + const { address } = useAccount(); const { writeContractAsync } = useWriteContract(); - const { data: liquidity, refetch: refetchLiquidity } = useReadContract({ + const { + data: liquidity, + refetch: refetchLiquidity, + error, + } = useReadContract({ address: CPAMM_ADDRESS, abi: CPAMM_ABI, functionName: "getReserves", }); + const { data: userShares } = useReadContract({ + address: CPAMM_ADDRESS, + abi: CPAMM_ABI, + functionName: "balanceOf", + args: [address ?? ""], + }); + if (error) { + console.error("error", error); + } const refetchAll = useCallback(() => { refetchLiquidity(); @@ -19,5 +33,6 @@ export default function useCpamm() { wbtcPoolLiquidity: liquidity?.[1], refetchAll, writeContractAsync, + userShares, }; } diff --git a/web/package.json b/web/package.json index c0ac0fe..102f0d4 100644 --- a/web/package.json +++ b/web/package.json @@ -15,13 +15,17 @@ }, "dependencies": { "@heroicons/react": "~2.1.5", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-slot": "^1.1.0", "@rainbow-me/rainbowkit": "2.1.6", "@t3-oss/env-nextjs": "^0.11.1", "@tanstack/react-query": "~5.59.15", "blo": "~1.2.0", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "daisyui": "4.12.10", "geist": "^1.3.1", + "lucide-react": "^0.468.0", "next": "~14.2.11", "next-nprogress-bar": "~2.3.13", "next-themes": "~0.3.0", @@ -31,6 +35,7 @@ "react-dom": "~18.3.1", "react-hot-toast": "^2.4.1", "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^3.1.0", "viem": "2.21.32", "wagmi": "2.12.23", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 56f5911..a7173de 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -11,6 +11,15 @@ importers: '@heroicons/react': specifier: ~2.1.5 version: 2.1.5(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.2 + version: 2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.1.2 + version: 2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.14)(react@18.3.1) '@rainbow-me/rainbowkit': specifier: 2.1.6 version: 2.1.6(@tanstack/react-query@5.59.20(react@18.3.1))(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.21.32(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.12.23(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/react@18.3.14)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.14)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.21.32(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8)) @@ -23,15 +32,18 @@ importers: blo: specifier: ~1.2.0 version: 1.2.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 - daisyui: - specifier: 4.12.10 - version: 4.12.10(postcss@8.4.49) geist: specifier: ^1.3.1 version: 1.3.1(next@14.2.20(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + lucide-react: + specifier: ^0.468.0 + version: 0.468.0(react@18.3.1) next: specifier: ~14.2.11 version: 14.2.20(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -59,6 +71,9 @@ importers: tailwind-merge: specifier: ^2.5.5 version: 2.5.5 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.16(ts-node@10.9.1(@types/node@18.19.67)(typescript@5.5.4))) usehooks-ts: specifier: ^3.1.0 version: 3.1.0(react@18.3.1) @@ -886,6 +901,21 @@ packages: resolution: {integrity: sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==} engines: {node: '>=14'} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/react-dom@2.1.2': + resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@heroicons/react@2.1.5': resolution: {integrity: sha512-FuzFN+BsHa+7OxbvAERtgBTNeZpUjgM/MIizfVkSCL2/edriN0Hx/DWRCR//aPYwO5QX/YlgLGXk+E3PcfZwjA==} peerDependencies: @@ -1291,6 +1321,310 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@radix-ui/number@1.1.0': + resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} + + '@radix-ui/primitive@1.1.0': + resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + + '@radix-ui/react-arrow@1.1.0': + resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.0': + resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.0': + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.0': + resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.0': + resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.1': + resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.2': + resolution: {integrity: sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.1': + resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.0': + resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.0': + resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.2': + resolution: {integrity: sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.0': + resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.2': + resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.1': + resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.0': + resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.1.2': + resolution: {integrity: sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.1.0': + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.0': + resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.1.0': + resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.0': + resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.0': + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.0': + resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.0': + resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.0': + resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.1.0': + resolution: {integrity: sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.0': + resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@rainbow-me/rainbowkit@2.1.6': resolution: {integrity: sha512-DCt6VYuPPxcPY6veuSOa784mHHHN0uSdDBTivdUBssmjTwHMmOrEs6kuKSYTPRu8EAwA1AvIc+ulSVnS022nbg==} engines: {node: '>=12.4'} @@ -1859,6 +2193,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -2115,6 +2453,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -2216,9 +2557,6 @@ packages: crossws@0.3.1: resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} - css-selector-tokenizer@0.8.0: - resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==} - css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -2231,14 +2569,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - culori@3.3.0: - resolution: {integrity: sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - daisyui@4.12.10: - resolution: {integrity: sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==} - engines: {node: '>=16.9.0'} - damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -2718,9 +3048,6 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fastparse@1.1.2: - resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} - fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -3424,6 +3751,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.468.0: + resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -4515,6 +4847,11 @@ packages: tailwind-merge@2.5.5: resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + tailwindcss@3.4.16: resolution: {integrity: sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==} engines: {node: '>=14.0.0'} @@ -6000,6 +6337,23 @@ snapshots: ethereum-cryptography: 2.2.1 micro-ftch: 0.3.1 + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.6.12 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.8': {} + '@heroicons/react@2.1.5(react@18.3.1)': dependencies: react: 18.3.1 @@ -6474,6 +6828,277 @@ snapshots: '@pkgr/core@0.1.1': {} + '@radix-ui/number@1.1.0': {} + + '@radix-ui/primitive@1.1.0': {} + + '@radix-ui/react-arrow@1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-collection@1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-context@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-context@1.1.1(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-direction@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-dismissable-layer@1.1.1(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-dropdown-menu@2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-menu': 2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-focus-scope@1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-id@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-menu@2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.1(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.14)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-popper@1.2.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-portal@1.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-presence@1.1.1(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-primitive@2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-roving-focus@1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-select@2.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.0 + '@radix-ui/primitive': 1.1.0 + '@radix-ui/react-collection': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.1(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-popper': 1.2.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.2(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.14)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.0(@types/react@18.3.14)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-slot@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.0 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-use-size@1.1.0(@types/react@18.3.14)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.14)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/react-visually-hidden@1.1.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.14 + + '@radix-ui/rect@1.1.0': {} + '@rainbow-me/rainbowkit@2.1.6(@tanstack/react-query@5.59.20(react@18.3.1))(@types/react@18.3.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(viem@2.21.32(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(wagmi@2.12.23(@tanstack/query-core@5.59.20)(@tanstack/react-query@5.59.20(react@18.3.1))(@types/react@18.3.14)(bufferutil@4.0.8)(react-dom@18.3.1(react@18.3.1))(react-native@0.76.3(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.14)(bufferutil@4.0.8)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(typescript@5.5.4)(utf-8-validate@5.0.10)(viem@2.21.32(bufferutil@4.0.8)(typescript@5.5.4)(utf-8-validate@5.0.10)(zod@3.23.8))(zod@3.23.8))': dependencies: '@tanstack/react-query': 5.59.20(react@18.3.1) @@ -7463,6 +8088,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.1: @@ -7795,6 +8424,10 @@ snapshots: dependencies: consola: 3.2.3 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + client-only@0.0.1: {} clipboardy@4.0.0: @@ -7902,28 +8535,12 @@ snapshots: dependencies: uncrypto: 0.1.3 - css-selector-tokenizer@0.8.0: - dependencies: - cssesc: 3.0.0 - fastparse: 1.1.2 - css-what@6.1.0: {} cssesc@3.0.0: {} csstype@3.1.3: {} - culori@3.3.0: {} - - daisyui@4.12.10(postcss@8.4.49): - dependencies: - css-selector-tokenizer: 0.8.0 - culori: 3.3.0 - picocolors: 1.1.1 - postcss-js: 4.0.1(postcss@8.4.49) - transitivePeerDependencies: - - postcss - damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.1: @@ -8526,8 +9143,6 @@ snapshots: fast-safe-stringify@2.1.1: {} - fastparse@1.1.2: {} - fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -9295,6 +9910,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.468.0(react@18.3.1): + dependencies: + react: 18.3.1 + make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -10530,6 +11149,10 @@ snapshots: tailwind-merge@2.5.5: {} + tailwindcss-animate@1.0.7(tailwindcss@3.4.16(ts-node@10.9.1(@types/node@18.19.67)(typescript@5.5.4))): + dependencies: + tailwindcss: 3.4.16(ts-node@10.9.1(@types/node@18.19.67)(typescript@5.5.4)) + tailwindcss@3.4.16(ts-node@10.9.1(@types/node@18.19.67)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 diff --git a/web/public/dai-badge.webp b/web/public/dai-badge.webp new file mode 100644 index 0000000..ce23b5e Binary files /dev/null and b/web/public/dai-badge.webp differ diff --git a/web/public/wbtc-badge.webp b/web/public/wbtc-badge.webp new file mode 100644 index 0000000..34dc7dd Binary files /dev/null and b/web/public/wbtc-badge.webp differ diff --git a/web/styles/globals.css b/web/styles/globals.css index a4a64ae..5b49fd5 100644 --- a/web/styles/globals.css +++ b/web/styles/globals.css @@ -3,7 +3,67 @@ @tailwind utilities; @layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.75rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } body { - @apply bg-neutral-100; + @apply bg-background text-foreground; } } diff --git a/web/tailwind.config.js b/web/tailwind.config.js index e970b11..31890ee 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -4,71 +4,68 @@ import defaultTheme from "tailwindcss/defaultTheme"; /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"], - plugins: [require("daisyui")], + plugins: [require("tailwindcss-animate")], darkTheme: "dark", - darkMode: ["selector", "[data-theme='dark']"], - daisyui: { - themes: [ - { - light: { - primary: "#1F2937", - "primary-content": colors.neutral[50], - secondary: "#DAE8FF", - "secondary-content": "#212638", - accent: "#93BBFB", - "accent-content": "#212638", - neutral: "#212638", - "neutral-content": "#ffffff", - "base-100": "#ffffff", - "base-200": "#f4f8ff", - "base-300": "#DAE8FF", - "base-content": "#212638", - info: "#93BBFB", - success: "#34EEB6", - warning: "#FFCF72", - error: "#FF8863", - ".tooltip": { "--tooltip-tail": "6px" }, - ".link": { textUnderlineOffset: "2px" }, - ".link:hover": { opacity: "80%" }, - "--rounded-box": "0.5rem", - "--rounded-btn": "0.375rem", - }, - }, - { - dark: { - primary: "#212638", - "primary-content": "#F9FBFF", - secondary: "#323f61", - "secondary-content": "#F9FBFF", - accent: "#4969A6", - "accent-content": "#F9FBFF", - neutral: "#F9FBFF", - "neutral-content": "#385183", - "base-100": "#385183", - "base-200": "#2A3655", - "base-300": "#212638", - "base-content": "#F9FBFF", - info: "#385183", - success: "#34EEB6", - warning: "#FFCF72", - error: "#FF8863", - ".tooltip": { "--tooltip-tail": "6px", "--tooltip-color": "oklch(var(--p))" }, - ".link": { textUnderlineOffset: "2px" }, - ".link:hover": { opacity: "80%" }, - "--rounded-box": "0.5rem", - "--rounded-btn": "0.375rem", - }, - }, - ], - }, + darkMode: ["selector", "[data-theme='dark']", "class"], theme: { extend: { - boxShadow: { center: "0 0 12px -2px rgb(0 0 0 / 0.05)" }, - animation: { "pulse-fast": "pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite" }, + boxShadow: { + center: "0 0 12px -2px rgb(0 0 0 / 0.05)", + }, + animation: { + "pulse-fast": "pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite", + }, fontFamily: { sans: ["var(--font-geist-sans)", ...defaultTheme.fontFamily.sans], mono: ["var(--font-geist-mono)", ...defaultTheme.fontFamily.mono], }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + colors: { + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + 1: "hsl(var(--chart-1))", + 2: "hsl(var(--chart-2))", + 3: "hsl(var(--chart-3))", + 4: "hsl(var(--chart-4))", + 5: "hsl(var(--chart-5))", + }, + }, }, }, }; diff --git a/web/utils/currency.ts b/web/utils/currency.ts index 873fadf..5ed8f49 100644 --- a/web/utils/currency.ts +++ b/web/utils/currency.ts @@ -5,5 +5,7 @@ export function formatToken(amount: bigint | number | string) { } export function formatTokenWithDecimals(amount: bigint, decimals = 18) { - return new Intl.NumberFormat("en-US", { maximumFractionDigits: 5 }).format(Number(formatUnits(amount, decimals))); + return new Intl.NumberFormat("en-US", { maximumFractionDigits: 5 }) + .format(Number(formatUnits(amount, decimals))) + .replace(/,/g, ""); }