Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CoW UX Team Minor Issues #35

Merged
merged 13 commits into from
Apr 29, 2024
10 changes: 10 additions & 0 deletions src/app/history/order/[orderHash]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Spinner } from "#/components/Spinner";
import { TokenLogo } from "#/components/TokenLogo";
import { CowOrder, useOrder } from "#/contexts/ordersContext";
import { ChainId } from "#/lib/publicClients";
import { formatTimeDelta } from "#/lib/timeDelta";
import {
buildBlockExplorerTxUrl,
buildOrderCowExplorerUrl,
Expand Down Expand Up @@ -57,6 +58,9 @@ export default function OrderPage({
const orderDateTime = formatDateTime(
epochToDate(Number(stopLossOrder?.blockTimestamp))
);
const orderWaitTime = formatTimeDelta(
stopLossOrder?.stopLossData?.validityBucketSeconds as number
);

const amountIn =
Number(stopLossOrder?.stopLossData?.tokenAmountIn) /
Expand Down Expand Up @@ -120,6 +124,12 @@ export default function OrderPage({
>
{orderDateTime}
</OrderInformation>
<OrderInformation
label="Validity Bucket Time"
tooltipText="After the oracle price achieves the defined strike price, how much time the order will wait to be filled on the orderbook."
>
{orderWaitTime}
</OrderInformation>
</div>
<Separator className="my-3" />
<div className="flex flex-col gap-y-1">
Expand Down
17 changes: 15 additions & 2 deletions src/components/RootLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { Button, Toaster } from "@bleu-fi/ui";
import { ClockIcon } from "@radix-ui/react-icons";
import { ClockIcon, PlusIcon } from "@radix-ui/react-icons";
import SafeProvider from "@safe-global/safe-apps-react-sdk";
import Link from "next/link";
import { usePathname } from "next/navigation";
Expand All @@ -26,6 +26,19 @@ function HistoryButton() {
);
}

function BuilderButton() {
return (
<Link href="/history">
<Button className="h-full" variant="default">
<span className="flex items-center gap-x-2 ">
<PlusIcon />
New Order
</span>
</Button>
</Link>
);
}

export function RootLayout({ children }: React.PropsWithChildren) {
const path = usePathname();
return (
Expand All @@ -34,7 +47,7 @@ export function RootLayout({ children }: React.PropsWithChildren) {
<ReactFlowProvider>
<div className="flex flex-col h-screen">
<Header linkUrl={"/builder"} imageSrc={"/assets/stoploss.svg"}>
{path !== "/history" && <HistoryButton />}
{path === "/history" ? <BuilderButton /> : <HistoryButton />}
</Header>
<div className="size-full bg-background">{children}</div>
<Footer
Expand Down
6 changes: 5 additions & 1 deletion src/components/TokenLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Image from "next/image";
import { useState } from "react";
import { useEffect, useState } from "react";

import { cowTokenList } from "#/lib/cowTokenList";
import { ChainId } from "#/lib/publicClients";
Expand Down Expand Up @@ -41,6 +41,10 @@ export const TokenLogo = ({
cowprotocolTokenLogoUrl(tokenAddress, chainId) || FALLBACK_SRC
);

useEffect(() => {
setImageSrc(cowprotocolTokenLogoUrl(tokenAddress, chainId) || FALLBACK_SRC);
}, [tokenAddress, chainId]);

return (
<Image
className={className}
Expand Down
37 changes: 35 additions & 2 deletions src/components/TokenSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import {
Popover,
PopoverContent,
PopoverTrigger,
toast,
} from "@bleu-fi/ui";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk";
import React, { useState } from "react";
import { Address } from "viem";
import { Address, isAddress } from "viem";

import { cowTokenList } from "#/lib/cowTokenList";
import { fetchTokenInfo } from "#/lib/fetchTokenInfo";
import { ChainId } from "#/lib/publicClients";
import { IToken } from "#/lib/types";

Expand All @@ -39,6 +41,7 @@ export function TokenSelect({
safe: { chainId },
} = useSafeAppsSDK();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");

const tokens = cowTokenList.filter(
(token) => token.chainId === chainId
Expand All @@ -51,6 +54,24 @@ export function TokenSelect({
setOpen(false);
}

async function handleImportToken() {
try {
const importedToken = await fetchTokenInfo(
search as Address,
chainId as ChainId
);
handleSelectToken(importedToken);
toast({
title: "Token imported",
});
} catch (e) {
toast({
title: "Error importing token",
variant: "destructive",
});
}
}

return (
<>
<Popover open={open} onOpenChange={setOpen}>
Expand Down Expand Up @@ -87,6 +108,10 @@ export function TokenSelect({
<PopoverContent>
<Command
filter={(value, search) => {
setSearch(search);
if (value === "import") {
return Number(isAddress(search));
}
if (!search) return 1;
const regex = new RegExp(search, "i");
return Number(regex.test(value));
Expand All @@ -95,7 +120,7 @@ export function TokenSelect({
>
<CommandInput placeholder="Search token..." className="h-9" />
<CommandList>
<CommandEmpty>No tokens found</CommandEmpty>
<CommandEmpty>No results found</CommandEmpty>
{tokens.map((token) => (
<CommandItem
key={token.address}
Expand All @@ -112,6 +137,14 @@ export function TokenSelect({
/>
</CommandItem>
))}
<CommandItem
key="import"
value="import"
onSelect={handleImportToken}
className="mx-1"
>
Import token
</CommandItem>
</CommandList>
</Command>
</PopoverContent>
Expand Down
32 changes: 16 additions & 16 deletions src/components/edges/AddHookEdge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@ export const HOOK_OPTIONS = [
label: "Mint BAL from gauges",
value: "hookMintBal",
},
{
label: "Multisend",
value: "hookMultiSend",
},
{
label: "Aave withdraw",
value: "hookAaveWithdraw",
},
{
label: "Claim vesting",
value: "hookClaimVesting",
},
{
label: "Exit pool",
value: "hookExitPool",
},
// {
// label: "Multisend",
// value: "hookMultiSend",
// },
// {
// label: "Aave withdraw",
// value: "hookAaveWithdraw",
// },
// {
// label: "Claim vesting",
// value: "hookClaimVesting",
// },
// {
// label: "Exit pool",
// value: "hookExitPool",
// },
];

export function AddHookEdge({
Expand Down
53 changes: 27 additions & 26 deletions src/components/menus/SwapMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,34 +101,35 @@ export function SwapMenu({
type="number"
step={1 / 10 ** amountDecimals}
/>
{walletAmount != "0" && (
<div className="flex gap-x-1 text-xs">

<div className="flex gap-x-1 text-xs">
<span>
<span>
<span>
Wallet Balance:{" "}
{formatNumber(
walletAmount,
4,
"decimal",
"standard",
0.0001
)}
</span>
Wallet Balance:{" "}
{walletAmount == "0"
? walletAmount
: formatNumber(
walletAmount,
4,
"decimal",
"standard",
0.0001
)}
</span>
<button
type="button"
className="text-accent outline-none hover:text-accent/70"
onClick={() => {
setValue(
"amount",
convertStringToNumberAndRoundDown(walletAmount)
);
}}
>
Max
</button>
</div>
)}
</span>
<button
type="button"
className="text-accent outline-none hover:text-accent/70"
onClick={() => {
setValue(
"amount",
convertStringToNumberAndRoundDown(walletAmount)
);
}}
>
Max
</button>
</div>
</div>
<TokenSelect
selectedToken={data.tokenSell}
Expand Down
2 changes: 2 additions & 0 deletions src/components/menus/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ function DefaultMenu() {
<div className="flex flex-col w-full">
<span className="text-lg font-bold text-highlight">Nodes menu</span>
<p>Select a node to see the menu and edit the parameters</p>
<br />
<p>If you want to delete one order node, select it and press delete</p>
</div>
);
}
15 changes: 12 additions & 3 deletions src/components/nodes/StopLossNode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { formatNumber } from "@bleu-fi/ui";

import { useBuilder } from "#/contexts/builder";
import { IStopLossConditionData } from "#/lib/types";

import { InfoTooltip } from "../Tooltip";
Expand All @@ -17,20 +18,28 @@ export function StopLossNode({
selected: boolean;
data: IStopLossConditionData;
}) {
const { getOrderDataByOrderId } = useBuilder();
const recipeData = getOrderDataByOrderId(data.orderId);

return (
<BaseNode selected={selected} isStart>
<div className="flex flex-col">
<div className="flex flex-row gap-2 items-center">
<span className="text-sm font-bold text-highlight">
Stop Loss Condition
</span>

{data.error && (
<InfoTooltip text={STOP_LOSS_ERROR_MESSSAGE[data.error]} link={"https://data.chain.link/feeds"} variant="error"/>
<InfoTooltip
text={STOP_LOSS_ERROR_MESSSAGE[data.error]}
link={"https://data.chain.link/feeds"}
variant="error"
/>
)}
</div>
<span className="text-xs">
If the sell token price falls bellow{" "}
If the {recipeData?.tokenSell.symbol}/{recipeData?.tokenBuy.symbol}{" "}
falls bellow{" "}
{formatNumber(data.strikePrice, 4, "decimal", "standard", 0.0001)}
</span>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/nodes/SubmitNode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, useToast } from "@bleu-fi/ui";
import { EnterIcon } from "@radix-ui/react-icons";
import { PaperPlaneIcon } from "@radix-ui/react-icons";
import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk";
import { useRouter } from "next/navigation";
import { useState } from "react";
Expand Down Expand Up @@ -146,7 +146,7 @@ export function SubmitButton({
onClick={onClick}
>
<div className="flex flex-row justify-center text-center items-center gap-4">
<EnterIcon />
<PaperPlaneIcon />
<p>{isSubmitting ? "Creating Tx..." : "Submit Orders"}</p>
</div>
</Button>
Expand Down
38 changes: 27 additions & 11 deletions src/components/nodes/SwapNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { formatNumber } from "@bleu-fi/ui";
import { useEffect, useState } from "react";
import { formatUnits } from "viem";

import { useBuilder } from "#/contexts/builder";
Expand All @@ -17,27 +18,42 @@ export function SwapNode({
data: ISwapData;
}) {
const { fetchBalance } = useSafeBalances();
const [sellTokenWalletAmount, setSellTokenWalletAmount] = useState<number>();
const [sellAmount, setSellAmount] = useState<number>();
const [buyAmount, setBuyAmount] = useState<number>();
const { getOrderDataByOrderId } = useBuilder();
const recipeData = getOrderDataByOrderId(data.orderId);
if (!recipeData) return null;
const sellTokenWalletAmount = Number(
formatUnits(
BigInt(fetchBalance(data.tokenSell.address)),
data.tokenSell.decimals
)
);
const [sellAmount, buyAmount] = calculateAmounts(recipeData);

useEffect(() => {
if (!recipeData) return;
const newSellTokenWalletAmount = Number(
formatUnits(
BigInt(fetchBalance(data.tokenSell.address)),
data.tokenSell.decimals
)
);
setSellTokenWalletAmount(newSellTokenWalletAmount);
const [newSellAmount, newBuyAmount] = calculateAmounts(recipeData);
setSellAmount(newSellAmount);
setBuyAmount(newBuyAmount);
}, [recipeData, data, fetchBalance]);

if (!sellAmount || !buyAmount) return null;

const sellAmountWithSymbol = `${formatNumber(sellAmount, 2, "decimal", "compact", 0.01)} ${data.tokenSell.symbol}`;
const buyAmountWithSymbol = `${formatNumber(buyAmount, 2, "decimal", "compact", 0.01)} ${data.tokenBuy.symbol}`;
return (
<BaseNode selected={selected}>
<div className="flex flex-col">
<div className="flex flex-row gap-2 items-center">
<span className="text-sm font-bold text-highlight">Swap</span>
{sellTokenWalletAmount < sellAmount && (
<InfoTooltip variant="error" text="You don't have enough amount of the selling token. The order can still be posted but it will never be filled until you don't have enough tokens in your wallet." />
{(sellTokenWalletAmount || 0) < sellAmount && (
<InfoTooltip
variant="error"
text="You don't have enough amount of the selling token. The order can still be posted but it can just be filled with the tokens that you have on your wallet."
/>
)}
</div>{" "}
</div>
<div className="text-xs">
{data.isSellOrder
? `Sell ${sellAmountWithSymbol} for at least ${buyAmountWithSymbol}`
Expand Down
Loading
Loading