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

Personalized strategy ordering enhancement #1636

Merged
merged 20 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 2 additions & 3 deletions src/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import ConnectButton from "components/_buttons/ConnectButton"
import { Link } from "components/Link"
import { useRouter } from "next/router"
import { NAV_LINKS } from "utils/navLinks"
import { useIsMounted } from "hooks/utils/useIsMounted"
import { LogoTextIcon } from "./_icons"
import useBetterMediaQuery from "hooks/utils/useBetterMediaQuery"
import { useScrollDirection } from "hooks/utils/useScrollDirection"
import { HamburgerIcon } from "./_icons/HamburgerIcon"

export const Nav: VFC<FlexProps> = (props) => {
const isMounted = useIsMounted()
const [scrolled, setScrolled] = useState<boolean>(false)
const scrollDirection = useScrollDirection()
const { isOpen, onOpen, onClose } = useDisclosure()
Expand All @@ -43,7 +41,8 @@ export const Nav: VFC<FlexProps> = (props) => {
}
})
return () => {
window.removeEventListener("scroll", () => {})
window.removeEventListener("scroll", () => {});
setScrolled(false);
Comment on lines +47 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect removal of event listener in useEffect cleanup function.

- window.removeEventListener("scroll", () => {});
+ const handleScroll = () => {
+   if (window.scrollY >= 80) {
+     setScrolled(true);
+   } else {
+     setScrolled(false);
+   }
+ };
+ window.addEventListener("scroll", handleScroll);
+ return () => {
+   window.removeEventListener("scroll", handleScroll);
+ };

This change ensures that the same function instance is used for both adding and removing the event listener, preventing potential memory leaks and ensuring proper cleanup.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
window.removeEventListener("scroll", () => {});
setScrolled(false);
const handleScroll = () => {
if (window.scrollY >= 80) {
setScrolled(true);
} else {
setScrolled(false);
}
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};

}
}, [])

Expand Down
6 changes: 3 additions & 3 deletions src/components/_buttons/WithdrawButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const WithdrawButton: VFC<
ButtonProps & {
isDeprecated?: boolean
}
> = (props) => {
> = ({ isDeprecated, ...buttonProps }) => {
const { isOpen, onOpen, onClose } = useDisclosure()

function closeModal() {
Expand Down Expand Up @@ -59,9 +59,9 @@ export const WithdrawButton: VFC<

onOpen()
}}
{...props}
{...buttonProps}
>
{props.isDeprecated
{isDeprecated
? "Withdraw Only"
: "Withdraw"}
</SecondaryButton>
Expand Down
2 changes: 0 additions & 2 deletions src/components/_cards/ApyPerfomanceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ export const ApyPerfomanceCard: VFC<BoxProps> = (props) => {
<HStack spacing={8}>
<VStack spacing={0} align="flex-start">
<CardHeading>
<Text>
{timeline} {apyChartLabel(cellarConfig)}
</Text>
</CardHeading>
<HStack>
<Text fontSize="2.5rem" fontWeight="bold">
Expand Down
9 changes: 7 additions & 2 deletions src/components/_cards/PortfolioCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,13 @@ import { Rewards } from "./Rewards"
import { useNetwork } from "wagmi"
import WithdrawQueueCard from "../WithdrawQueueCard"
import withdrawQueueV0821 from "src/abi/withdraw-queue-v0.8.21.json"
import { add } from "lodash"
import { CellarNameKey } from "data/types"
import { PointsDisplay } from "./PointsDisplay"

export const PortfolioCard: VFC<BoxProps> = (props) => {
const theme = useTheme()
const isMounted = useIsMounted()
const { address, isConnected } = useAccount()
const { address, isConnected: connected } = useAccount()
const id = useRouter().query.id as string
const cellarConfig = cellarDataMap[id].config
const slug = cellarDataMap[id].slug
Expand All @@ -73,6 +72,12 @@ export const PortfolioCard: VFC<BoxProps> = (props) => {
cellarConfig.chain.id
) as Token[]

// using local state to avoid Next.js errors
const [isConnected, setConnected] = useState(false);
useEffect(() => {
setConnected(connected)
}, [connected])

const { lpToken } = useUserBalances(cellarConfig)
let { data: lpTokenData } = lpToken
const lpTokenDisabled =
Expand Down
14 changes: 9 additions & 5 deletions src/components/_layout/LayoutWithSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import { Box, Container, Flex } from "@chakra-ui/react"
import Footer from "components/Footer"
import { Nav } from "components/Nav"
import { Sidebar } from "components/_sidebar"
import { useAllStrategiesData } from "data/hooks/useAllStrategiesData"
import { FC, useRef } from "react"
import { FC, useEffect, useRef, useState } from "react"
import { useAccount } from "wagmi"
import { useInView } from "react-intersection-observer"

export const LayoutWithSidebar: FC = ({ children }) => {
const { isConnected } = useAccount()

const { isLoading } = useAllStrategiesData()
const { isConnected: connected } = useAccount()

const containerRef = useRef<HTMLDivElement>(null)
const { ref, inView } = useInView({
threshold: 0,
})

// using local state to avoid Next.js errors
const [isConnected, setConnected] = useState(false);
useEffect(() => {
setConnected(connected)
}, [connected])

return (
<Box display="block">
<Flex bg="#1A1A23" flexDir="column" position="relative">
Expand Down
134 changes: 89 additions & 45 deletions src/components/_pages/PageHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import {
useDepositModalStore,
} from "data/hooks/useDepositModalStore"
import useBetterMediaQuery from "hooks/utils/useBetterMediaQuery"
import { useMemo, useState, useEffect } from "react"
import { InfoBanner } from "components/_banners/InfoBanner"
import { useMemo, useState } from "react"
import { ChainFilter } from "components/_filters/ChainFilter"
import { chainConfig } from "src/data/chainConfig"
import {
Expand All @@ -39,6 +38,10 @@ import {
} from "components/_filters/MiscFilter"
import { isEqual } from "lodash"
import { DeleteCircleIcon } from "components/_icons"
import { add, isBefore } from "date-fns"
import { useUserDataAllStrategies } from "data/hooks/useUserDataAllStrategies"
import { useAccount } from "wagmi"
import { StrategyData } from "data/actions/types"

export const PageHome = () => {
const {
Expand All @@ -62,6 +65,9 @@ export const PageHome = () => {
} = useDepositModalStore()

const { timeline } = useHome()
let { data: userData } = useUserDataAllStrategies();
const { isConnected } = useAccount();

const columns = isDesktop
? StrategyDesktopColumn({
timeline,
Expand Down Expand Up @@ -237,57 +243,95 @@ export const PageHome = () => {
}

const strategyData = useMemo(() => {
return (
data?.filter((item) => {
// Chain filter
const isChainSelected = selectedChainIds.includes(
item?.config.chain.id!
)

// Deposit asset filter
const hasSelectedDepositAsset = cellarDataMap[
item!.slug
const filteredData = data?.filter((item) => {
// Chain filter
const isChainSelected = selectedChainIds.includes(
item?.config.chain.id!
)

// Deposit asset filter
const hasSelectedDepositAsset = cellarDataMap[
item!.slug
].depositTokens.list.some((tokenSymbol) =>
selectedDepositAssets.hasOwnProperty(tokenSymbol)
)

// Deprecated filter
const isDeprecated = cellarDataMap[item!.slug].deprecated
const deprecatedCondition = showDeprecated
? isDeprecated
: !isDeprecated

// Incentivised filter
// Badge check for custom rewards
const hasGreenBadge = cellarDataMap[
item!.slug
selectedDepositAssets.hasOwnProperty(tokenSymbol)
)

// Deprecated filter
const isDeprecated = cellarDataMap[item!.slug].deprecated
const deprecatedCondition = showDeprecated
? isDeprecated
: !isDeprecated

// Incentivised filter
// Badge check for custom rewards
const hasGreenBadge = cellarDataMap[
item!.slug
].config.badges?.some(
(badge) => badge.customStrategyHighlightColor === "#00C04B"
)

// Staking period check for somm/vesting rewards
const hasLiveStakingPeriod =
item?.rewardsApy?.value !== undefined &&
item?.rewardsApy?.value > 0

const incentivisedCondition = showIncentivised
? hasGreenBadge || hasLiveStakingPeriod
: true

return (
isChainSelected &&
hasSelectedDepositAsset &&
deprecatedCondition &&
incentivisedCondition
)
}) || []
)
(badge) => badge.customStrategyHighlightColor === "#00C04B"
)

// Staking period check for somm/vesting rewards
const hasLiveStakingPeriod =
item?.rewardsApy?.value !== undefined &&
item?.rewardsApy?.value > 0

const incentivisedCondition = showIncentivised
? hasGreenBadge || hasLiveStakingPeriod
: true

return (
isChainSelected &&
hasSelectedDepositAsset &&
deprecatedCondition &&
incentivisedCondition
)
}) || []

return filteredData.sort((a, b) => {

// 1. Priority - does user own same assets as in strategy
if (isConnected && userData?.depositAssetBalances) {
for (const balance of userData?.depositAssetBalances) {
const doesStrategyHaveAsset = (strategy: StrategyData) => strategy?.tradedAssets?.some(
asset => asset.symbol === balance.symbol
)
const strategyAHasAsset = doesStrategyHaveAsset(a);
const strategyBHasAsset = doesStrategyHaveAsset(b);

if (strategyAHasAsset && strategyBHasAsset) {
break;
}
if ((strategyAHasAsset || strategyBHasAsset) && !(strategyAHasAsset && strategyBHasAsset)) {
return strategyAHasAsset ? -1 : 1;
}
}
}
// 2. Priority - new strategies
const isNewStrategy = (strategy: StrategyData) => isBefore(new Date(), add(new Date(strategy?.launchDate ?? ''), { weeks: 4 }));
const isANew = isNewStrategy(a);
const isBNew = isNewStrategy(b);
if (isANew && isBNew) {
return new Date(b?.launchDate ?? '').getTime() - new Date(a?.launchDate ?? '').getTime();
} else if (isANew || isBNew) {
return isANew ? -1 : 1;
}

// 3. Priority - Somm rewards
if ((a?.rewardsApy || b?.rewardsApy) && !(a?.rewardsApy && b?.rewardsApy)) {
return a?.rewardsApy ? -1 : 1;
}

// 4. Priority - TVL
return parseFloat(b?.tvm?.value ?? '') - parseFloat(a?.tvm?.value ?? '');
});
}, [
data,
selectedChainIds,
selectedDepositAssets,
showDeprecated,
showIncentivised,
userData,
isConnected
])

const loading = isFetching || isRefetching || isLoading
Expand Down
30 changes: 30 additions & 0 deletions src/data/actions/common/getUserDataAllStrategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import { getUserData } from "./getUserData"
import { fetchCoingeckoPrice } from "queries/get-coingecko-price"
import { cellarDataMap } from "data/cellarDataMap"
import { ConfigProps } from "data/types"
import { fetchBalance } from "@wagmi/core"
import { getAddress } from "ethers/lib/utils"
import { getAcceptedDepositAssetsByChain } from "data/tokenConfig"
import { ResolvedConfig } from "abitype"
import BigNumber from "bignumber.js"

export const getUserDataAllStrategies = async ({
allContracts,
Expand Down Expand Up @@ -74,6 +79,30 @@ export const getUserDataAllStrategies = async ({
)
)

const tokenList = getAcceptedDepositAssetsByChain(chain);
const depositAssetBalances : {
decimals: ResolvedConfig['IntType'];
formatted: string;
symbol: string;
value: ResolvedConfig['BigIntType'];
}[] = [];

for (const token of tokenList) {
await fetchBalance({
token: getAddress(token!.address),
address: getAddress(userAddress)
}).then((balance) => {
if (!balance.value.isZero()) {
depositAssetBalances.push(balance);
}
})
.catch((error) => console.log("error", error))
}

depositAssetBalances.sort(
(x, y) => new BigNumber(y.value._hex).minus(new BigNumber(x.value._hex)).toNumber()
)

const userData = userDataRes.filter((item) => !!item)

const totalNetValue = (() => {
Expand Down Expand Up @@ -116,6 +145,7 @@ export const getUserDataAllStrategies = async ({
value: totalNetValue,
formatted: formatUSD(String(totalNetValue)),
},
depositAssetBalances,
totalSommRewards: {
value: totalSommRewards,
formatted: Number(
Expand Down
4 changes: 4 additions & 0 deletions src/data/hooks/useAllStrategiesData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export const useAllStrategiesData = () => {
}
})
.catch((error) => setError(error))
return () => {
setError(null);
setcellarData(undefined);
};
}, [])

const query = useQuery(
Expand Down
4 changes: 1 addition & 3 deletions src/data/hooks/useUserDataAllStrategies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useAccount, useSigner } from "wagmi"
import { useAllContracts } from "./useAllContracts"
import { useAllStrategiesData } from "./useAllStrategiesData"
import { useCoinGeckoPrice } from "./useCoinGeckoPrice"
import { useState, useEffect } from "react"
import { useNetwork } from "wagmi"
import { chainConfig } from "data/chainConfig"
import { tokenConfig } from "data/tokenConfig"
Expand All @@ -14,7 +13,6 @@ export const useUserDataAllStrategies = () => {
const { address } = useAccount()
const { data: allContracts } = useAllContracts()
const strategies = useAllStrategiesData()
const [error, setError] = useState(null)

const { chain } = useNetwork()

Expand Down Expand Up @@ -62,6 +60,6 @@ export const useUserDataAllStrategies = () => {

return {
...query,
isError: Boolean(error) || query.isError,
isError: query.isError,
}
}
Loading