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

feat: add reinvest functionality and improve dashboard token balance … #6

Merged
merged 3 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(() => {
hugolxt marked this conversation as resolved.
Show resolved Hide resolved
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
Loading