Skip to content

Commit

Permalink
update: supply modal (#5)
Browse files Browse the repository at this point in the history
* add: explicit links for external services (zyfi, zap, enso)

* add: supply modal success & warning
  • Loading branch information
clmntsnr authored Jan 15, 2025
1 parent dd3c918 commit 2a0594b
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 54 deletions.
107 changes: 64 additions & 43 deletions src/components/element/participate/Interact.client.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { Opportunity } from "@merkl/api";
import type { InteractionTarget } from "@merkl/api/dist/src/modules/v4/interaction/interaction.model";
import {
Box,
Button,
type ButtonProps,
Checkbox,
Collapsible,
Divider,
Dropdown,
Group,
Icon,
PrimitiveTag,
Expand All @@ -19,6 +18,7 @@ import { useMemo, useState } from "react";
import useBalances from "../../../hooks/useBalances";
import useInteractionTransaction from "../../../hooks/useInteractionTransaction";
import Token from "../token/Token";
import TransactionOverview from "../transaction/TransactionOverview";

export type InteractProps = {
opportunity: Opportunity;
Expand All @@ -42,7 +42,7 @@ export default function Interact({
target,
disabled,
}: InteractProps) {
const { chainId, switchChain, address: user, sponsorTransactions, setSponsorTransactions } = useWalletContext();
const { chainId, switchChain, address: user } = useWalletContext();
const {
transaction,
reload,
Expand Down Expand Up @@ -90,6 +90,15 @@ export default function Interact({
</>
),
});
else if (transaction.approved && !transaction.transaction)
createProps({
disabled: true,
children: (
<>
<Icon remix="RiProhibitedLine" /> An error occured
</>
),
});

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
if (buttonProps) return <Button {...(buttonProps as any)} />;
Expand Down Expand Up @@ -145,59 +154,71 @@ export default function Interact({
if (target.provider === "enso")
return (
<>
<Icon src="https://framerusercontent.com/images/19ye5oms8sG6XHF1K8p03vLNkg.png" /> Enso
<Dropdown
content={
<Group className="flex-col max-w-[42ch]">
<Text size="sm">
Enso provides abstract on-chain actions, shortcuts and routes that allows dApps to find the best
routes to interact with other protocols.
</Text>
<Divider look="soft" horizontal />
<Group className="flex-col">
<Button to={"https://www.enso.build/"} size="xs" look="soft">
<Icon remix="RiArrowRightLine" /> Visit Enso
</Button>
</Group>
</Group>
}>
<PrimitiveTag size="sm">
<Icon src="https://framerusercontent.com/images/19ye5oms8sG6XHF1K8p03vLNkg.png" /> Enso
</PrimitiveTag>
</Dropdown>
</>
);
if (target.provider === "zap")
return (
<>
<Icon src="https://docs.kyberswap.com/~gitbook/image?url=https%3A%2F%2F1368568567-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252Fw1XgQJc40kVeGUIxgI7c%252Ficon%252FYl1TDE5MQwDPbEsfCerK%252Fimage%2520%281%29.png%3Falt%3Dmedia%26token%3D3f984a53-8b11-4d1b-b550-193d82610e7b&width=32&dpr=1&quality=100&sign=a7af3e95&sv=2" />{" "}
Zap
<Dropdown
content={
<Group className="flex-col max-w-[42ch]">
<Text size="sm">
Zap enables users to effortlessly add liquidity into any concentrated liquidity protocol using any
tokens, thanks to the KyberSwap aggregator.
</Text>
<Divider look="soft" horizontal />
<Group className="flex-col">
<Button
to={"https://docs.kyberswap.com/kyberswap-solutions/kyberswap-zap-as-a-service"}
size="xs"
look="soft">
<Icon remix="RiArrowRightLine" /> Visit Kyberswap Zap
</Button>
</Group>
</Group>
}>
<PrimitiveTag size="sm">
<Icon src="https://docs.kyberswap.com/~gitbook/image?url=https%3A%2F%2F1368568567-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252Fw1XgQJc40kVeGUIxgI7c%252Ficon%252FYl1TDE5MQwDPbEsfCerK%252Fimage%2520%281%29.png%3Falt%3Dmedia%26token%3D3f984a53-8b11-4d1b-b550-193d82610e7b&width=32&dpr=1&quality=100&sign=a7af3e95&sv=2" />{" "}
Kyberswap Zap
</PrimitiveTag>
</Dropdown>
</>
);
}, [target]);

const canTransactionBeSponsored = opportunity.chainId === 324;
const [settingsCollapsed, setSettingsCollapsed] = useState<boolean>(false);

return (
<>
<Space size="sm" />
<Box content="sm" className="w-full !gap-0 !bg-main-2" look="base">
<Group className="w-full flex-nowrap">
<Group className="grow items-center">
{amount && inputToken && (
<Text className="flex animate-drop grow flex-nowrap items-center gap-md" size={6}>
Supply
<Token key={amount} className="animate-drop" token={inputToken} amount={amount} format="price" /> with{" "}
{providerIcon}
</Text>
)}
</Group>
<PrimitiveTag
onClick={() => setSettingsCollapsed(o => !o)}
size="sm"
look="base"
className="flex flex-nowrap gap-md">
<Icon remix="RiSettings3Line" />
<Icon
data-state={!settingsCollapsed ? "closed" : "opened"}
className={"transition duration-150 ease-out data-[state=opened]:rotate-180"}
remix="RiArrowDownSLine"
/>
</PrimitiveTag>
</Group>
<Collapsible state={[settingsCollapsed]}>
<Space size="md" />
{canTransactionBeSponsored && (
<Group className="justify-between w-full items-center">
<Text>Gasless</Text>
<Checkbox size="sm" state={[sponsorTransactions, setSponsorTransactions]} />
</Group>
)}
{settings}
</Collapsible>
</Box>
<TransactionOverview settings={settings} allowTxSponsoring={canTransactionBeSponsored}>
{amount && inputToken && (
<Text className="flex animate-drop grow flex-nowrap items-center gap-md" size={6}>
Supply
<Token key={amount} className="animate-drop" token={inputToken} amount={amount} format="price" /> with{" "}
{providerIcon}
</Text>
)}
</TransactionOverview>
<Space size="xl" />
{currentInteraction}
</>
Expand Down
38 changes: 29 additions & 9 deletions src/components/element/participate/Participate.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Opportunity } from "@merkl/api";
import { useLocation } from "@remix-run/react";
import { Button, Group, Icon, Input, PrimitiveTag, Text, Value } from "dappkit";
import { Collapsible } from "dappkit";
import { Box, Collapsible } from "dappkit";
import { useWalletContext } from "dappkit";
import { Fmt } from "dappkit";
import { Suspense, useMemo, useState } from "react";
Expand Down Expand Up @@ -43,6 +43,7 @@ export default function Participate({
const { link } = useOpportunity(opportunity);
const location = useLocation();
const isOnOpportunityPage = location.pathname.includes("/opportunities/");
const [success, setSuccess] = useState(false);

const { connected } = useWalletContext();

Expand Down Expand Up @@ -118,7 +119,10 @@ export default function Participate({
/>
<Suspense>
<Interact
onSuccess={() => setAmount(undefined)}
onSuccess={() => {
setAmount(undefined);
setSuccess(true);
}}
disabled={!loading && !targets?.length}
target={targets?.[0]}
slippage={slippage}
Expand Down Expand Up @@ -185,20 +189,36 @@ export default function Participate({
</Button>
</Group>
)}
{!!I18n.trad.get.pages.home.depositInformation && (
<Group className="rounded-md p-md bg-main-5 flex-nowrap items-start">
<Icon remix="RiInformation2Fill" className="text-lg text-accent-11 flex-shrink-0" />
<Text look="bold" size="xs">
{I18n.trad.get.pages.home.depositInformation}
</Text>
</Group>

{!loading && !!interactor && (
<Box look="soft" className="gap-xs bg-main-5">
<Group className="flex flex-nowrap">
<Icon coloring={"warn"} remix="RiErrorWarningFill" className="text-accent-11 flex-shrink-0" />
<Text size="sm">{I18n.trad.get.pages.home.depositInformation}</Text>
</Group>
</Box>
)}
{loading && (
<Group className="w-full justify-center">
<Icon remix="RiLoader2Line" className="animate-spin" />
</Group>
)}
<Collapsible state={[!!interactor]}>{interactor}</Collapsible>
<Collapsible state={[success]}>
<Box look="soft" className="gap-xs bg-main-5">
<Group>
<Icon coloring={"good"} remix="RiCheckboxCircleFill" className="text-accent-12" />
<Text look="bold" className="font-bold">
Deposit successful !
</Text>
</Group>
<Text size="sm">
Your liquidity is now earning rewards (if any are currently being distributed to this opportunity). You'll
soon be able to claim them directly from your dashboard. You can monitor your positions and withdraw your
liquidity anytime directly through the protocol app.
</Text>
</Box>
</Collapsible>
</>
);
}
3 changes: 2 additions & 1 deletion src/components/element/rewards/ClaimRewardsChainTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Fmt } from "dappkit";
import { useMemo, useState } from "react";
import merklConfig from "../../../config";
import useReward from "../../../hooks/resources/useReward";
import { UserService } from "../../../modules/user/user.service";
import Tag from "../Tag";
import { ClaimRewardsChainRow } from "./ClaimRewardsChainTable";
import { ClaimRewardsTokenTable } from "./ClaimRewardsTokenTable";
Expand All @@ -30,7 +31,7 @@ export default function ClaimRewardsChainTableRow({
const [selectedTokens, setSelectedTokens] = useState<Set<string>>(new Set<string>());

const { address: user, chainId, switchChain } = useWalletContext();
const isUserRewards = useMemo(() => user === from, [user, from]);
const isUserRewards = useMemo(() => UserService.isSame(user, from), [user, from]);
const isAbleToClaim = useMemo(
() => isUserRewards && !reward.rewards.every(({ amount, claimed }) => amount === claimed),
[isUserRewards, reward],
Expand Down
78 changes: 78 additions & 0 deletions src/components/element/transaction/TransactionOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
Box,
Button,
Checkbox,
Collapsible,
Divider,
Dropdown,
Group,
Icon,
PrimitiveTag,
Space,
Text,
useWalletContext,
} from "dappkit";
import { type ReactNode, useState } from "react";

export interface TransactionOverviewProps {
gas?: number;
allowTxSponsoring?: boolean;
settings?: ReactNode;
children?: ReactNode;
}

export default function TransactionOverview({ allowTxSponsoring, settings, children }: TransactionOverviewProps) {
const { sponsorTransactions, setSponsorTransactions } = useWalletContext();
const [settingsCollapsed, setSettingsCollapsed] = useState<boolean>(false);

return (
<Box content="sm" className="w-full !gap-0 !bg-main-2" look="base">
<Group className="w-full flex-nowrap">
<Group className="grow items-center">{children}</Group>
<PrimitiveTag
onClick={() => setSettingsCollapsed(o => !o)}
size="sm"
look="base"
className="flex flex-nowrap gap-md">
<Icon remix="RiSettings3Line" />
<Icon
data-state={!settingsCollapsed ? "closed" : "opened"}
className={"transition duration-150 ease-out data-[state=opened]:rotate-180"}
remix="RiArrowDownSLine"
/>
</PrimitiveTag>
</Group>
<Collapsible state={[settingsCollapsed, setSettingsCollapsed]}>
<Space size="md" />
{allowTxSponsoring && (
<Group className="justify-between w-full items-center">
<Text className="flex flex-nowrap gap-md items-center">
Sponsor with{" "}
<Dropdown
content={
<Group className="flex-col max-w-[42ch]">
<Text size="sm">
Zyfi leverages ZKSync's native account abstraction to allow dApps simplify gas management for
dApps users.
</Text>
<Divider look="soft" horizontal />
<Group className="flex-col">
<Button to={"https://www.zyfi.org/"} size="xs" look="soft">
<Icon remix="RiArrowRightLine" /> Visit Zyfi
</Button>
</Group>
</Group>
}>
<PrimitiveTag size="sm">
<Icon src="https://www.zyfi.org/favicon.ico" /> Zyfi
</PrimitiveTag>
</Dropdown>
</Text>
<Checkbox size="sm" state={[sponsorTransactions, setSponsorTransactions]} />
</Group>
)}
{settings}
</Collapsible>
</Box>
);
}
3 changes: 2 additions & 1 deletion src/modules/user/routes/user.$address.header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import useReward from "../../../hooks/resources/useReward";
import useRewards from "../../../hooks/resources/useRewards";
import { RewardService } from "../../../modules/reward/reward.service";
import { TokenService } from "../../../modules/token/token.service";
import { UserService } from "../user.service";

export async function loader({ params: { address }, request }: LoaderFunctionArgs) {
if (!address || !isAddress(address)) throw "";
Expand Down Expand Up @@ -63,7 +64,7 @@ export default function Index() {
const reward = useMemo(() => rawRewards.find(({ chain: { id } }) => id === chainId), [chainId, rawRewards]);
const { claimTransaction } = useReward(reward, user);

const isUserRewards = useMemo(() => user === address, [user, address]);
const isUserRewards = useMemo(() => UserService.isSame(user, address), [user, address]);
const isAbleToClaim = useMemo(
() => isUserRewards && reward && !reward.rewards.every(({ amount, claimed }) => amount === claimed),
[isUserRewards, reward],
Expand Down
14 changes: 14 additions & 0 deletions src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isAddressEqual } from "viem";

export abstract class UserService {
/**
* Compares addresses to check if they are equal
* @notice needed because addresse can be checksum or not
* @param a address
* @param b address
*/
static isSame(a?: string, b?: string): boolean {
if (a?.startsWith("0x") && b.startsWith("0x")) return isAddressEqual(a as `0x${string}`, b as `0x${string}`);
return false;
}
}

0 comments on commit 2a0594b

Please sign in to comment.