diff --git a/src/I18n/en.ts b/src/I18n/en.ts index 1158f4f..2d9e22a 100644 --- a/src/I18n/en.ts +++ b/src/I18n/en.ts @@ -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", diff --git a/src/components/element/participate/Participate.tsx b/src/components/element/participate/Participate.tsx index da44003..79c4cb3 100644 --- a/src/components/element/participate/Participate.tsx +++ b/src/components/element/participate/Participate.tsx @@ -198,7 +198,7 @@ export default function Participate({ )} - {loading && ( + {loading && !!merklConfig.deposit && ( diff --git a/src/components/element/reinvest/ReinvestBanner.tsx b/src/components/element/reinvest/ReinvestBanner.tsx new file mode 100644 index 0000000..70e8ccc --- /dev/null +++ b/src/components/element/reinvest/ReinvestBanner.tsx @@ -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(); + 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 => ( + + + + )), + [opportunities], + ); + + if (!merklConfig.dashboard?.reinvestTokenAddress) return; + return ( + setIsOpen(!isOpen)}> + + + + + {I18n.trad.get.pages.dashboard.reinvest} + + + + + {isOpen && } + + {cells} + + + ); +} diff --git a/src/config/index.ts b/src/config/index.ts index d630f53..baf6ab5 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -101,6 +101,7 @@ const defaultMerklConfig: MerklConfig = { liquidityTab: { enabled: false, }, + reinvestTokenAddress: "", }, tagsDetails: { token: { diff --git a/src/config/type.ts b/src/config/type.ts index 2c1558e..23be957 100644 --- a/src/config/type.ts +++ b/src/config/type.ts @@ -154,6 +154,10 @@ export type MerklConfig = { liquidityTab: { enabled: boolean; }; + /** + * Address of the token of user to reinvest in the dashboard + */ + reinvestTokenAddress?: string; }; tagsDetails: { token: { diff --git a/src/modules/user/routes/user.$address.header.tsx b/src/modules/user/routes/user.$address.header.tsx index 7e89c65..1de2120 100644 --- a/src/modules/user/routes/user.$address.header.tsx +++ b/src/modules/user/routes/user.$address.header.tsx @@ -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"; @@ -48,7 +49,10 @@ export default function Index() { const { rewards: raw, address, token: rawToken } = useLoaderData(); const fetcher = useFetcher(); + const { reload: reloadBalances } = useBalances(); + const onClaimSuccess = async (_hash: string) => { + reloadBalances(); await fetcher.submit(null, { method: "post", action: `/claim/${address}?chainId=${chainId}` }); }; diff --git a/src/modules/user/routes/user.$address.rewards.tsx b/src/modules/user/routes/user.$address.rewards.tsx index 8272099..573f305 100644 --- a/src/modules/user/routes/user.$address.rewards.tsx +++ b/src/modules/user/routes/user.$address.rewards.tsx @@ -1,14 +1,17 @@ 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 }); } @@ -16,8 +19,22 @@ export default function Index() { const { address } = useLoaderData(); const { rewards: sortedRewards, onClaimSuccess } = useOutletContext(); + 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 ( + + {!!I18n.trad.get.pages.dashboard.reinvest && tokenBalance > 0 && } {!!I18n.trad.get.pages.dashboard.explanation && (