Skip to content

Commit

Permalink
feat: add reinvest functionality and improve dashboard token balance … (
Browse files Browse the repository at this point in the history
#6)

* feat: add reinvest functionality and improve dashboard token balance handling

* create component and client side api call

* fix gap on parent was removing animation on close
  • Loading branch information
hugolxt authored Jan 16, 2025
1 parent 57ddcfa commit 18c27db
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/I18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const en = {
title: "Dashboard",
explanation:
"Pending rewards are updated approximately every 4 hours, but are claimable onchain once every week.\nIf you don’t claim your rewards from a week, you may always claim them at a later time.",
reinvest: "",
},
tokens: {
headTitle: "Merkl | Tokens",
Expand Down
2 changes: 1 addition & 1 deletion src/components/element/participate/Participate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export default function Participate({
</Group>
</Box>
)}
{loading && (
{loading && !!merklConfig.deposit && (
<Group className="w-full justify-center">
<Icon remix="RiLoader2Line" className="animate-spin" />
</Group>
Expand Down
59 changes: 59 additions & 0 deletions src/components/element/reinvest/ReinvestBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Opportunity } from "@merkl/api";
import { Collapsible, EventBlocker, Group, Icon, Space, Text, mergeClass } from "dappkit";
import { useEffect, useMemo, useState } from "react";
import { I18n } from "../../../I18n";
import OpportunityCell from "../../../components/element/opportunity/OpportunityCell";
import merklConfig from "../../../config";
import { OpportunityService } from "../../../modules/opportunity/opportunity.service";

export default function ReinvestBanner() {
const [opportunities, setOpportunities] = useState<Opportunity[]>();
const [isOpen, setIsOpen] = useState(true);

useEffect(() => {
if (!merklConfig.dashboard?.reinvestTokenAddress) return;
OpportunityService.getMany({
items: 3,
status: "LIVE",
}).then(({ opportunities }) => setOpportunities(opportunities));
}, []);

const cells = useMemo(
() =>
opportunities?.map(o => (
<EventBlocker key={`${o.chainId}_${o.type}_${o.identifier}`}>
<OpportunityCell
navigationMode={"supply"}
hideTags={["action", "chain", "status", "token", "tokenChain"]}
opportunity={o}
/>
</EventBlocker>
)),
[opportunities],
);

if (!merklConfig.dashboard?.reinvestTokenAddress) return;
return (
<Group
className="rounded-md p-md bg-main-5 flex-nowrap items-start flex-col cursor-pointer !gap-0"
onClick={() => setIsOpen(!isOpen)}>
<Group className="w-full justify-between">
<Group>
<Icon coloring={"good"} className={"text-lg text-accent-11"} remix="RiInformation2Fill" />
<Text look="bold" size="sm">
{I18n.trad.get.pages.dashboard.reinvest}
</Text>
</Group>
<Icon
data-state={!isOpen ? "closed" : "opened"}
className={"transition duration-150 ease-out data-[state=opened]:rotate-180 text-lg text-main-12"}
remix="RiArrowDownSLine"
/>
</Group>
{isOpen && <Space size="md" />}
<Collapsible state={[isOpen, setIsOpen]} className={mergeClass("w-full")}>
<Group className="grid grid-cols-1 lg:grid-cols-3 gap-lg*2 w-full">{cells}</Group>
</Collapsible>
</Group>
);
}
1 change: 1 addition & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const defaultMerklConfig: MerklConfig<Themes> = {
liquidityTab: {
enabled: false,
},
reinvestTokenAddress: "",
},
tagsDetails: {
token: {
Expand Down
4 changes: 4 additions & 0 deletions src/config/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ export type MerklConfig<T extends Themes> = {
liquidityTab: {
enabled: boolean;
};
/**
* Address of the token of user to reinvest in the dashboard
*/
reinvestTokenAddress?: string;
};
tagsDetails: {
token: {
Expand Down
4 changes: 4 additions & 0 deletions src/modules/user/routes/user.$address.header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Token from "../../../components/element/token/Token";
import merklConfig from "../../../config";
import useReward from "../../../hooks/resources/useReward";
import useRewards from "../../../hooks/resources/useRewards";
import useBalances from "../../../hooks/useBalances";
import { RewardService } from "../../../modules/reward/reward.service";
import { TokenService } from "../../../modules/token/token.service";
import { UserService } from "../user.service";
Expand Down Expand Up @@ -48,7 +49,10 @@ export default function Index() {
const { rewards: raw, address, token: rawToken } = useLoaderData<typeof loader>();
const fetcher = useFetcher<typeof loader>();

const { reload: reloadBalances } = useBalances();

const onClaimSuccess = async (_hash: string) => {
reloadBalances();
await fetcher.submit(null, { method: "post", action: `/claim/${address}?chainId=${chainId}` });
};

Expand Down
21 changes: 19 additions & 2 deletions src/modules/user/routes/user.$address.rewards.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json, useLoaderData, useOutletContext } from "@remix-run/react";
import { Container, Group, Icon, Space, Text } from "dappkit";
import { Container, Fmt, Group, Icon, Space, Text, useWalletContext } from "dappkit";
import { useMemo } from "react";
import { isAddress } from "viem";
import { I18n } from "../../../I18n";
import ReinvestBanner from "../../../components/element/reinvest/ReinvestBanner";
import ClaimRewardsLibrary from "../../../components/element/rewards/ClaimRewardsLibrary";
import merklConfig from "../../../config";
import useBalances from "../../../hooks/useBalances";
import type { OutletContextRewards } from "./user.$address.header";

export async function loader({ params: { address } }: LoaderFunctionArgs) {
if (!address || !isAddress(address)) throw "";

return json({ address });
}

export default function Index() {
const { address } = useLoaderData<typeof loader>();
const { rewards: sortedRewards, onClaimSuccess } = useOutletContext<OutletContextRewards>();

const { chainId } = useWalletContext();
const { balances, loading: balanceLoading } = useBalances(chainId);

const tokenBalance = useMemo(() => {
if (!balances || balanceLoading) return 0;
const token = balances?.find(
balance => balance?.address?.toLowerCase() === merklConfig.dashboard?.reinvestTokenAddress?.toLowerCase(),
);
if (!token) return 0;
return Fmt.toNumber(token?.balance, token?.decimals);
}, [balances, balanceLoading]);

return (
<Container>
<Space size="md" />
{!!I18n.trad.get.pages.dashboard.reinvest && tokenBalance > 0 && <ReinvestBanner />}
<Space size="md" />
{!!I18n.trad.get.pages.dashboard.explanation && (
<Group className="rounded-md p-md bg-main-5 flex-nowrap items-start">
Expand Down

0 comments on commit 18c27db

Please sign in to comment.