diff --git a/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.module.scss b/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.module.scss index 6e3b40a0c..acce121b5 100644 --- a/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.module.scss +++ b/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.module.scss @@ -16,4 +16,5 @@ max-width: 1070px; margin: 0 auto; padding: 0 var(--space-m); -} + z-index: 2; +} \ No newline at end of file diff --git a/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.tsx b/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.tsx index aba4db739..606b9b5c6 100644 --- a/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.tsx +++ b/apps/earn-protocol-landing-page/components/layout/MasterPage/MasterPage.tsx @@ -1,4 +1,5 @@ import { type FC, type PropsWithChildren } from 'react' +import { MainBackground } from '@summerfi/app-earn-ui' import { NavigationWrapper } from '@/components/layout/Navigation/NavigationWrapper' @@ -14,6 +15,7 @@ export const MasterPage: FC> = ({ children }) {children}
Footer (:
+ ) } diff --git a/apps/earn-protocol/app/earn/[network]/[strategy_id]/page.tsx b/apps/earn-protocol/app/earn/[network]/[strategy_id]/page.tsx index 418ef18bc..0a24462c1 100644 --- a/apps/earn-protocol/app/earn/[network]/[strategy_id]/page.tsx +++ b/apps/earn-protocol/app/earn/[network]/[strategy_id]/page.tsx @@ -12,7 +12,7 @@ const EarnNetworkSelectedStrategyPage = ({ }) => { // particular strategy loaded return ( - + ) } diff --git a/apps/earn-protocol/components/layout/MasterPage/MasterPage.module.scss b/apps/earn-protocol/components/layout/MasterPage/MasterPage.module.scss index 5671efbab..ff4153bca 100644 --- a/apps/earn-protocol/components/layout/MasterPage/MasterPage.module.scss +++ b/apps/earn-protocol/components/layout/MasterPage/MasterPage.module.scss @@ -16,4 +16,5 @@ max-width: 1200px; margin: 0 auto; padding: 0 var(--space-m); + z-index: 2; } diff --git a/apps/earn-protocol/components/layout/MasterPage/MasterPage.tsx b/apps/earn-protocol/components/layout/MasterPage/MasterPage.tsx index 81119de4c..7c0618987 100644 --- a/apps/earn-protocol/components/layout/MasterPage/MasterPage.tsx +++ b/apps/earn-protocol/components/layout/MasterPage/MasterPage.tsx @@ -1,4 +1,5 @@ import { type FC, type PropsWithChildren } from 'react' +import { MainBackground } from '@summerfi/app-earn-ui' import dynamic from 'next/dynamic' import { NavigationWrapper } from '@/components/layout/Navigation/NavigationWrapper' @@ -32,6 +33,7 @@ export const MasterPage: FC> = ({ children }) > Footer + ) diff --git a/apps/earn-protocol/components/layout/StrategiesListView/StrategiesListView.tsx b/apps/earn-protocol/components/layout/StrategiesListView/StrategiesListView.tsx index dcdfa505e..cf5916969 100644 --- a/apps/earn-protocol/components/layout/StrategiesListView/StrategiesListView.tsx +++ b/apps/earn-protocol/components/layout/StrategiesListView/StrategiesListView.tsx @@ -1,6 +1,6 @@ 'use client' -import { useMemo } from 'react' +import { useMemo, useState } from 'react' import { DataBlock, SimpleGrid, @@ -9,14 +9,12 @@ import { StrategySimulationForm, } from '@summerfi/app-earn-ui' import { type DropdownOption, type IconNamesList, type NetworkNames } from '@summerfi/app-types' -import Link from 'next/link' -import { useRouter } from 'next/navigation' import { strategiesList } from '@/constants/dev-strategies-list' type StrategiesListViewProps = { selectedNetwork?: NetworkNames | 'all-networks' - selectedStrategy?: string + selectedStrategyId?: string } const allNetworksOption = { @@ -25,54 +23,87 @@ const allNetworksOption = { value: 'all-networks', } -const getStrategyLink = ( - strategy: (typeof strategiesList)[number], - selectedNetwork?: StrategiesListViewProps['selectedNetwork'], -) => { - return `/earn/${selectedNetwork ?? 'all-networks'}/${strategy.id}` +const softRouterPush = (url: string) => { + window.history.pushState(null, '', url) } export const StrategiesListView = ({ selectedNetwork, - selectedStrategy, + selectedStrategyId, }: StrategiesListViewProps) => { - const { replace } = useRouter() - const networkFilteredStrategies = useMemo(() => { - return selectedNetwork && selectedNetwork !== 'all-networks' - ? strategiesList.filter((strategy) => strategy.network === selectedNetwork) - : strategiesList - }, [selectedNetwork]) - const selectedNetworkOption = useMemo(() => { - return selectedNetwork - ? { - iconName: 'ether_circle_color' as IconNamesList, - label: selectedNetwork, - value: selectedNetwork, - } - : allNetworksOption - }, [selectedNetwork]) - const strategiesNetworksList = useMemo(() => { - return [ + const [localStrategyNetwork, setLocalStrategyNetwork] = + useState(selectedNetwork) + + const [localStrategyId, setLocalStrategyId] = useState(selectedStrategyId) + + const networkFilteredStrategies = useMemo( + () => + localStrategyNetwork && localStrategyNetwork !== 'all-networks' + ? strategiesList.filter((strategy) => strategy.network === localStrategyNetwork) + : strategiesList, + [localStrategyNetwork], + ) + + const selectedNetworkOption = useMemo( + () => + localStrategyNetwork + ? { + iconName: 'ether_circle_color' as IconNamesList, + label: localStrategyNetwork, + value: localStrategyNetwork, + } + : allNetworksOption, + [localStrategyNetwork], + ) + const strategiesNetworksList = useMemo( + () => [ ...[...new Set(strategiesList.map(({ network }) => network))].map((network) => ({ iconName: 'ether_circle_color' as IconNamesList, label: network, value: network, })), allNetworksOption, - ] - }, []) + ], + [], + ) - const selectedStrategyData = useMemo(() => { - return strategiesList.find((strategy) => strategy.id === selectedStrategy) - }, [selectedStrategy]) + const selectedStrategyData = useMemo( + () => strategiesList.find((strategy) => strategy.id === localStrategyId), + [localStrategyId], + ) const handleChangeNetwork = (selected: DropdownOption) => { - if (selected.value === 'all-networks') { - replace('/earn') + setLocalStrategyNetwork(selected.value as StrategiesListViewProps['selectedNetwork']) + switch (selected.value) { + case 'all-networks': + // if its all networks we must check if the selected strategy is from the same network + // then we can "redirect" to the selected strategy page with the network + softRouterPush( + selectedStrategyData ? `/earn/all-networks/${selectedStrategyData.id}` : '/earn', + ) + + break + + default: + // if its a specific network we must check if the selected strategy is from the same network + // then we can "redirect" to the selected strategy page with the network + // if not we just go to the network page and clear the selected strategy (if not available in the new view) + if (selectedStrategyData && selectedStrategyData.network !== selected.value) { + setLocalStrategyId(undefined) + } + softRouterPush( + selectedStrategyData && selectedStrategyData.network === selected.value + ? `/earn/${selected.value}/${selectedStrategyData.id}` + : `/earn/${selected.value}`, + ) - return + break } - replace(`/earn/${selected.value}`) + } + + const handleChangeStrategy = (strategyId: string) => { + setLocalStrategyId(strategyId) + softRouterPush(`/earn/${localStrategyNetwork ?? 'all-networks'}/${strategyId}`) } return ( @@ -103,19 +134,19 @@ export const StrategiesListView = ({ } leftContent={networkFilteredStrategies.map((strategy, strategyIndex) => ( - - - + ))} rightContent={ - + } /> ) diff --git a/apps/earn-protocol/components/organisms/Charts/MockedLineChart.tsx b/apps/earn-protocol/components/organisms/Charts/MockedLineChart.tsx index 537d39e30..dfb7891e4 100644 --- a/apps/earn-protocol/components/organisms/Charts/MockedLineChart.tsx +++ b/apps/earn-protocol/components/organisms/Charts/MockedLineChart.tsx @@ -145,7 +145,6 @@ export const MockedLineChart = () => { padding: '20px 30px', border: 'none', }} - allowEscapeViewBox={{ x: false, y: true }} /> {dataNamesParsed.map((dataName, dataIndex) => dataName === 'Summer USDS Strategy' ? ( diff --git a/apps/earn-protocol/components/organisms/RebalancingActivity/RebalancingActivity.tsx b/apps/earn-protocol/components/organisms/RebalancingActivity/RebalancingActivity.tsx index c14510eb5..642bebc2c 100644 --- a/apps/earn-protocol/components/organisms/RebalancingActivity/RebalancingActivity.tsx +++ b/apps/earn-protocol/components/organisms/RebalancingActivity/RebalancingActivity.tsx @@ -1,37 +1,10 @@ import { type FC, useMemo } from 'react' -import { Card, DataBlock, Icon, Table, TableCellText, Text, WithArrow } from '@summerfi/app-earn-ui' +import { Card, DataBlock, Table, Text, WithArrow } from '@summerfi/app-earn-ui' import { type TokenSymbolsList } from '@summerfi/app-types' -import { formatCryptoBalance, timeAgo } from '@summerfi/app-utils' -import BigNumber from 'bignumber.js' import Link from 'next/link' -const columns = [ - { - title: 'Purpose', - key: 'purpose', - sortable: false, - }, - { - title: 'Action', - key: 'action', - sortable: false, - }, - { - title: 'Amount', - key: 'amount', - sortable: false, - }, - { - title: 'Timestamp', - key: 'timestamp', - sortable: false, - }, - { - title: 'Provider', - key: 'provider', - sortable: false, - }, -] +import { rebalancingActivityColumns } from '@/components/organisms/RebalancingActivity/columns' +import { rebalancingActivityMapper } from '@/components/organisms/RebalancingActivity/mapper' export interface RebalancingActivityRawData { type: string @@ -44,60 +17,6 @@ export interface RebalancingActivityRawData { } } -const rebalancingActivityMapper = (rawData: RebalancingActivityRawData[]) => { - return rawData.map((item) => { - return { - content: { - purpose: ( -
- - {item.type === 'reduce' ? 'Reduce' : 'Increase'} Risk -
- ), - action: ( -
- {item.action.from} → - {item.action.to} -
- ), - amount: ( -
- - {formatCryptoBalance(new BigNumber(item.amount.value))} -
- ), - timestamp: ( - - {timeAgo({ from: new Date(), to: new Date(Number(item.timestamp)) })} - - ), - provider: ( - - - {item.provider.label} - - - ), - }, - } - }) -} - interface RebalancingActivityProps { rawData: RebalancingActivityRawData[] } @@ -135,7 +54,7 @@ export const RebalancingActivity: FC = ({ rawData }) = for reallocating assets from lower performing strategies to higher performing ones, within a threshold of risk. - +
View all rebalances diff --git a/apps/earn-protocol/components/organisms/RebalancingActivity/columns.ts b/apps/earn-protocol/components/organisms/RebalancingActivity/columns.ts new file mode 100644 index 000000000..54a206b7d --- /dev/null +++ b/apps/earn-protocol/components/organisms/RebalancingActivity/columns.ts @@ -0,0 +1,27 @@ +export const rebalancingActivityColumns = [ + { + title: 'Purpose', + key: 'purpose', + sortable: false, + }, + { + title: 'Action', + key: 'action', + sortable: false, + }, + { + title: 'Amount', + key: 'amount', + sortable: false, + }, + { + title: 'Timestamp', + key: 'timestamp', + sortable: false, + }, + { + title: 'Provider', + key: 'provider', + sortable: false, + }, +] diff --git a/apps/earn-protocol/components/organisms/RebalancingActivity/mapper.tsx b/apps/earn-protocol/components/organisms/RebalancingActivity/mapper.tsx new file mode 100644 index 000000000..5e0c0ad17 --- /dev/null +++ b/apps/earn-protocol/components/organisms/RebalancingActivity/mapper.tsx @@ -0,0 +1,61 @@ +import { Icon, TableCellText, Text, WithArrow } from '@summerfi/app-earn-ui' +import { formatCryptoBalance, timeAgo } from '@summerfi/app-utils' +import BigNumber from 'bignumber.js' +import Link from 'next/link' + +import { type RebalancingActivityRawData } from '@/components/organisms/RebalancingActivity/RebalancingActivity' + +export const rebalancingActivityMapper = (rawData: RebalancingActivityRawData[]) => { + return rawData.map((item) => { + return { + content: { + purpose: ( +
+ + {item.type === 'reduce' ? 'Reduce' : 'Increase'} Risk +
+ ), + action: ( +
+ {item.action.from} + + {item.action.to} +
+ ), + amount: ( +
+ + {formatCryptoBalance(new BigNumber(item.amount.value))} +
+ ), + timestamp: ( + + {timeAgo({ from: new Date(), to: new Date(Number(item.timestamp)) })} + + ), + provider: ( + + + {item.provider.label} + + + ), + }, + } + }) +} diff --git a/apps/earn-protocol/components/organisms/StrategyExposure/StrategyExposure.tsx b/apps/earn-protocol/components/organisms/StrategyExposure/StrategyExposure.tsx index 479d8c62a..e567e6203 100644 --- a/apps/earn-protocol/components/organisms/StrategyExposure/StrategyExposure.tsx +++ b/apps/earn-protocol/components/organisms/StrategyExposure/StrategyExposure.tsx @@ -1,84 +1,66 @@ -import { type FC, Fragment, useMemo } from 'react' -import { Card, Icon, Table, TableCellText, Text, TokensGroup, Tooltip } from '@summerfi/app-earn-ui' +'use client' + +import { type Dispatch, type FC, type SetStateAction, useMemo, useState } from 'react' +import { Button, Card, Table, Text } from '@summerfi/app-earn-ui' import { type TokenSymbolsList } from '@summerfi/app-types' -import { formatCryptoBalance, formatDecimalAsPercent } from '@summerfi/app-utils' -import BigNumber from 'bignumber.js' +import { capitalize } from 'lodash-es' + +import { strategyExposureColumns } from '@/components/organisms/StrategyExposure/columns' +import { strategyExposureMapper } from '@/components/organisms/StrategyExposure/mapper' -const strategyTypeTooltipContent = [ - { title: 'Isolated Lending', description: 'Text for what isolated lending is' }, - { title: 'Basic Trading', description: 'Text for what isolated lending is' }, - { title: 'Fixed Yield', description: 'Text for what isolated lending is' }, - { title: 'Lending', description: 'Text for what isolated lending is' }, -] +enum StrategyExposureFilterType { + ALL = 'ALL', + ALLOCATED = 'ALLOCATED', + UNALLOCATED = 'UNALLOCATED', +} -const columns = [ - { - title: 'Strategy', - key: 'strategy', - sortable: false, - }, - { - title: '% Allocation', - key: 'allocation', - sortable: false, - }, - { - title: 'Current APY', - key: 'currentApy', - sortable: false, - }, - { - title: 'Liquidity', - key: 'liquidity', - sortable: false, - }, - { - title: ( - - {strategyTypeTooltipContent.map((item, idx) => ( - - - {item.title} - - - {item.description} - - - ))} - - } - > -
> +} + +const StrategyExposureTypePicker: FC = ({ + currentType, + setExposureType, +}) => { + return ( +
+ {[ + StrategyExposureFilterType.ALL, + StrategyExposureFilterType.ALLOCATED, + StrategyExposureFilterType.UNALLOCATED, + ].map((itemType) => ( +
- - ), - key: 'type', - sortable: false, - }, -] + + {capitalize(itemType.toLowerCase())} + + + ))} +
+ ) +} export interface StrategyExposureRawData { strategy: { @@ -92,36 +74,6 @@ export interface StrategyExposureRawData { type: string } -const strategyExposureMapper = (rawData: StrategyExposureRawData[]) => { - return rawData.map((item) => { - return { - content: { - strategy: ( -
- - {item.strategy.label} -
- ), - allocation: ( - {formatDecimalAsPercent(new BigNumber(item.allocation))} - ), - currentApy: ( - {formatDecimalAsPercent(new BigNumber(item.currentApy))} - ), - liquidity: ( - {formatCryptoBalance(new BigNumber(item.liquidity))} - ), - type: {item.type}, - }, - } - }) -} - interface StrategyExposureProps { rawData: StrategyExposureRawData[] } @@ -129,6 +81,10 @@ interface StrategyExposureProps { export const StrategyExposure: FC = ({ rawData }) => { const rows = useMemo(() => strategyExposureMapper(rawData), [rawData]) + const [exposureType, setExposureType] = useState( + StrategyExposureFilterType.ALL, + ) + return (
@@ -144,19 +100,8 @@ export const StrategyExposure: FC = ({ rawData }) => { process. Vetted for security, performance and trustworthy teams. -
- {rows.length > 5 && ( - - View more - - )} + +
) diff --git a/apps/earn-protocol/components/organisms/StrategyExposure/columns.tsx b/apps/earn-protocol/components/organisms/StrategyExposure/columns.tsx new file mode 100644 index 000000000..700f160a9 --- /dev/null +++ b/apps/earn-protocol/components/organisms/StrategyExposure/columns.tsx @@ -0,0 +1,73 @@ +import { Fragment } from 'react' +import { TableHeadWithTooltip, Text } from '@summerfi/app-earn-ui' + +const strategyTypeTooltipContent = [ + { title: 'Isolated Lending', description: 'Text for what isolated lending is' }, + { title: 'Basic Trading', description: 'Text for what isolated lending is' }, + { title: 'Fixed Yield', description: 'Text for what isolated lending is' }, + { title: 'Lending', description: 'Text for what isolated lending is' }, +] + +export const strategyExposureColumns = [ + { + title: 'Strategy', + key: 'strategy', + sortable: false, + }, + { + title: '% Allocation', + key: 'allocation', + sortable: false, + }, + { + title: 'Current APY', + key: 'currentApy', + sortable: false, + }, + { + title: 'Liquidity', + key: 'liquidity', + sortable: false, + }, + { + title: ( + + {strategyTypeTooltipContent.map((item, idx) => ( + + + {item.title} + + + {item.description} + + + ))} + + } + /> + ), + key: 'type', + sortable: false, + }, +] diff --git a/apps/earn-protocol/components/organisms/StrategyExposure/mapper.tsx b/apps/earn-protocol/components/organisms/StrategyExposure/mapper.tsx new file mode 100644 index 000000000..215fa6be7 --- /dev/null +++ b/apps/earn-protocol/components/organisms/StrategyExposure/mapper.tsx @@ -0,0 +1,62 @@ +import { TableCellText, Text, TokensGroup, WithArrow } from '@summerfi/app-earn-ui' +import { formatCryptoBalance, formatDecimalAsPercent } from '@summerfi/app-utils' +import BigNumber from 'bignumber.js' +import Link from 'next/link' + +import { type StrategyExposureRawData } from '@/components/organisms/StrategyExposure/StrategyExposure' + +export const strategyExposureMapper = (rawData: StrategyExposureRawData[]) => { + return rawData.map((item) => { + return { + content: { + strategy: ( +
+ + {item.strategy.label} +
+ ), + allocation: ( + {formatDecimalAsPercent(new BigNumber(item.allocation))} + ), + currentApy: ( + {formatDecimalAsPercent(new BigNumber(item.currentApy))} + ), + liquidity: ( + {formatCryptoBalance(new BigNumber(item.liquidity))} + ), + type: {item.type}, + }, + details: ( +
+ + Why this strategy? + + + MetaMorpho Gauntlet MKR Blended was chosen for it’s performance track record, risk + approach and asset exposure. + + + + Learn more + + +
+ ), + } + }) +} diff --git a/apps/earn-protocol/components/organisms/UserActivity/UserActivity.tsx b/apps/earn-protocol/components/organisms/UserActivity/UserActivity.tsx index 78fec75fe..54c333e66 100644 --- a/apps/earn-protocol/components/organisms/UserActivity/UserActivity.tsx +++ b/apps/earn-protocol/components/organisms/UserActivity/UserActivity.tsx @@ -1,37 +1,10 @@ 'use client' import { type FC, useMemo, useState } from 'react' -import { Button, Card, Table, TableCellText, Text, WithArrow } from '@summerfi/app-earn-ui' -import { formatCryptoBalance, formatFiatBalance, timeAgo } from '@summerfi/app-utils' -import BigNumber from 'bignumber.js' +import { Button, Card, Table, Text, WithArrow } from '@summerfi/app-earn-ui' import Link from 'next/link' -const columns = [ - { - title: 'Position Balance', - key: 'balance', - sortable: false, - }, - { - title: 'Amout', - key: 'amount', - sortable: false, - }, - { - title: '# of Deposits', - key: 'numberOfDeposits', - sortable: false, - }, - { - title: 'Time', - key: 'time', - sortable: false, - }, - { - title: 'Earning streak', - key: 'earningStreak', - sortable: false, - }, -] +import { userActivityColumns } from '@/components/organisms/UserActivity/columns' +import { userActivityMapper } from '@/components/organisms/UserActivity/mapper' export interface UserActivityRawData { balance: string @@ -44,35 +17,6 @@ export interface UserActivityRawData { } } -const userActivityMapper = (rawData: UserActivityRawData[]) => { - return rawData.map((item) => { - return { - content: { - balance: {formatCryptoBalance(new BigNumber(item.balance))}, - amount: ${formatFiatBalance(new BigNumber(item.amount))}, - numberOfDeposits: {item.numberOfDeposits}, - time: ( - - {timeAgo({ from: new Date(), to: new Date(Number(item.time)) })} - - ), - earningStreak: ( - - - {item.earningStreak.label} - - - ), - }, - } - }) -} - interface UserActivityProps { rawData: UserActivityRawData[] } @@ -123,7 +67,7 @@ export const UserActivity: FC = ({ rawData }) => { -
+
View all depositors diff --git a/apps/earn-protocol/components/organisms/UserActivity/columns.ts b/apps/earn-protocol/components/organisms/UserActivity/columns.ts new file mode 100644 index 000000000..0962b11ed --- /dev/null +++ b/apps/earn-protocol/components/organisms/UserActivity/columns.ts @@ -0,0 +1,27 @@ +export const userActivityColumns = [ + { + title: 'Position Balance', + key: 'balance', + sortable: false, + }, + { + title: 'Amount', + key: 'amount', + sortable: false, + }, + { + title: '# of Deposits', + key: 'numberOfDeposits', + sortable: false, + }, + { + title: 'Time', + key: 'time', + sortable: false, + }, + { + title: 'Earning streak', + key: 'earningStreak', + sortable: false, + }, +] diff --git a/apps/earn-protocol/components/organisms/UserActivity/mapper.tsx b/apps/earn-protocol/components/organisms/UserActivity/mapper.tsx new file mode 100644 index 000000000..e8d4bc94f --- /dev/null +++ b/apps/earn-protocol/components/organisms/UserActivity/mapper.tsx @@ -0,0 +1,35 @@ +import { TableCellText, WithArrow } from '@summerfi/app-earn-ui' +import { formatCryptoBalance, formatFiatBalance, timeAgo } from '@summerfi/app-utils' +import BigNumber from 'bignumber.js' +import Link from 'next/link' + +import { type UserActivityRawData } from '@/components/organisms/UserActivity/UserActivity' + +export const userActivityMapper = (rawData: UserActivityRawData[]) => { + return rawData.map((item) => { + return { + content: { + balance: {formatCryptoBalance(new BigNumber(item.balance))}, + amount: ${formatFiatBalance(new BigNumber(item.amount))}, + numberOfDeposits: {item.numberOfDeposits}, + time: ( + + {timeAgo({ from: new Date(), to: new Date(Number(item.time)) })} + + ), + earningStreak: ( + + + {item.earningStreak.label} + + + ), + }, + } + }) +} diff --git a/packages/app-earn-ui/src/components/atoms/SummerBall/SummerBall.tsx b/packages/app-earn-ui/src/components/atoms/SummerBall/SummerBall.tsx new file mode 100644 index 000000000..fceeb34a8 --- /dev/null +++ b/packages/app-earn-ui/src/components/atoms/SummerBall/SummerBall.tsx @@ -0,0 +1,95 @@ +const Ellipse = ({ size, ...props }: { size: number } & React.SVGProps) => { + return +} + +export const SummerBall = ({ + size = 100, + randomSize, + style, + blurSize = 0, +}: { + size?: number + randomSize?: boolean + style?: React.CSSProperties + blurSize?: number +}) => { + // randomId is needed because svg filters/gradients need unique ids + // otherwise first one will be used for all :( + const randomId = Math.random().toString(36).substring(7) + const actualSize = size / 2 // size is the diameter, actualSize is the radius + const randomRotationValue = Math.floor(Math.random() * 360) + const randomRotation = `rotate(${randomRotationValue}, ${actualSize}, ${actualSize})` + const computedSize = randomSize ? Math.floor(Math.random() * actualSize) + actualSize : actualSize + const svgSize = Number(computedSize * 2) + const randomSpinDuration = Math.floor(Math.random() * 20) + 10 + + return ( + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss new file mode 100644 index 000000000..496856a2a --- /dev/null +++ b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss @@ -0,0 +1,11 @@ +.underware { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + > svg { + position: absolute; + } +} \ No newline at end of file diff --git a/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss.d.ts b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss.d.ts new file mode 100644 index 000000000..e5cdca24c --- /dev/null +++ b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.module.scss.d.ts @@ -0,0 +1,9 @@ +export type Styles = { + underware: string +} + +export type ClassNames = keyof Styles + +declare const styles: Styles + +export default styles diff --git a/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.tsx b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.tsx new file mode 100644 index 000000000..540dbeb4c --- /dev/null +++ b/packages/app-earn-ui/src/components/layout/BackgroundComponents/MainBackground.tsx @@ -0,0 +1,14 @@ +import { SummerBall } from '@/components/atoms/SummerBall/SummerBall' + +import mainBackgroundStyles from './MainBackground.module.scss' + +export const MainBackground = () => { + return ( +
+ + + + +
+ ) +} diff --git a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss index 9c7f5c904..aa0bd0b6f 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss +++ b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss @@ -3,7 +3,7 @@ flex-direction: column; a { margin-top: var(--general-space-12); - color: var(--earn-protocol-primary-100) + color: var(--earn-protocol-primary-100); } } @@ -12,9 +12,6 @@ grid-gap: var(--general-space-16); grid-template-columns: 1.7fr 1.2fr; margin-top: 20px; - > div { - height: min-content; - } .fullWidthBlock { grid-column: 1 / 3; } @@ -22,4 +19,12 @@ display: grid; grid-gap: var(--general-space-16); } -} \ No newline at end of file + .rightBlockWrapper { + height: 100%; + + .rightBlock { + position: sticky; + top: var(--general-space-16); + } + } +} diff --git a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss.d.ts b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss.d.ts index 76d688a7e..5083f37d3 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss.d.ts +++ b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.module.scss.d.ts @@ -1,6 +1,8 @@ export type Styles = { fullWidthBlock: string leftBlock: string + rightBlock: string + rightBlockWrapper: string strategyGridHeaderWrapper: string strategyGridPositionWrapper: string } diff --git a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.tsx b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.tsx index 44339c58f..07c89e2cd 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.tsx +++ b/packages/app-earn-ui/src/components/layout/StrategyGrid/StrategyGrid.tsx @@ -46,7 +46,9 @@ export const StrategyGrid = ({
{topContent} {leftContent} - {rightContent} +
+
{rightContent}
+
) diff --git a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss index 81bf5ddba..47611c2c6 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss +++ b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss @@ -12,13 +12,19 @@ grid-gap: var(--general-space-16); grid-template-columns: 1fr max(446px); margin-top: 20px; - > div { - height: min-content; - } .leftBlock { display: grid; grid-gap: var(--general-space-16); } + + .rightBlockWrapper { + height: 100%; + + .rightBlock { + position: sticky; + top: var(--general-space-16); + } + } } .strategyGridDetailsTopLeftWrapper { diff --git a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss.d.ts b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss.d.ts index 3850d7270..5f3f725be 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss.d.ts +++ b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.module.scss.d.ts @@ -1,5 +1,7 @@ export type Styles = { leftBlock: string + rightBlock: string + rightBlockWrapper: string strategyGridDetailsBreadcrumbsWrapper: string strategyGridDetailsPositionWrapper: string strategyGridDetailsTopLeftWrapper: string diff --git a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.tsx b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.tsx index 112ca13ad..5fd8cc9f6 100644 --- a/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.tsx +++ b/packages/app-earn-ui/src/components/layout/StrategyGridDetails/StrategyGridDetails.tsx @@ -89,7 +89,9 @@ export const StrategyGridDetails = ({ {leftContent} - {rightContent} +
+
{rightContent}
+
) diff --git a/packages/app-earn-ui/src/components/molecules/Dropdown/Dropdown.tsx b/packages/app-earn-ui/src/components/molecules/Dropdown/Dropdown.tsx index 76f021a12..b9f936429 100644 --- a/packages/app-earn-ui/src/components/molecules/Dropdown/Dropdown.tsx +++ b/packages/app-earn-ui/src/components/molecules/Dropdown/Dropdown.tsx @@ -18,12 +18,19 @@ export const Dropdown: React.FC = ({ options, dropdownValue, onCh const [isOpen, setIsOpen] = useState(false) // To manage dropdown open/close state const dropdownRef = useRef(null) // Reference for the dropdown + useEffect(() => { + if (!onChange) { + // if theres no onChange prop, set the selected option to + // the dropdownValue as controlled component behavior + setSelectedOption(dropdownValue) + } + }, [onChange, dropdownValue]) + const handleSelectOption = (option: DropdownOption) => { if (onChange) { onChange(option) - } else { - setSelectedOption(option) } + setSelectedOption(option) setIsOpen(false) // Close dropdown after selection } @@ -32,8 +39,8 @@ export const Dropdown: React.FC = ({ options, dropdownValue, onCh } // Close dropdown when clicking outside - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + const handleClickOutside = (ev: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(ev.target as Node)) { setIsOpen(false) } } diff --git a/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.module.scss b/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.module.scss index dba19587d..7dfe5903d 100644 --- a/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.module.scss +++ b/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.module.scss @@ -37,7 +37,9 @@ opacity: 0; transition: opacity 0.2s ease-out; border-radius: var(--radius-roundish); - background: linear-gradient(90deg, rgba(255, 73, 164, 0.5), rgba(176, 73, 255, 0.5)); + background: var(--color-border-hover); + box-shadow: var(--shadow-depth-3); + } &:hover::before { opacity: 1; diff --git a/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.tsx b/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.tsx index 2da5377fd..079766bf2 100644 --- a/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.tsx +++ b/packages/app-earn-ui/src/components/molecules/StrategyCard/StrategyCard.tsx @@ -9,13 +9,15 @@ import { StrategyTitleWithRisk } from '@/components/molecules/StrategyTitleWithR import strategyCardStyles from './StrategyCard.module.scss' type StrategyCardProps = EarnProtocolStrategy & { - onClick?: () => void + onClick?: (id: string) => void secondary?: boolean selected?: boolean withHover?: boolean + staggerIndex?: number } export const StrategyCard = ({ + id, symbol, risk, bestFor, @@ -25,13 +27,21 @@ export const StrategyCard = ({ withHover, secondary = false, selected = false, + onClick, }: StrategyCardProps) => { + const handleStrategyClick = () => { + if (onClick) { + onClick(id) + } + } + return (
= ({ title, tooltipOpen }) => { + return ( +
+ + {title}{' '} + + +
+ ) +} + +interface TableHeadWithTooltipProps { + title: string + tooltip: ReactNode + minWidth: string +} + +export const TableHeadWithTooltip: FC = ({ + title, + tooltip, + minWidth, +}) => { + return ( + + {(tooltipOpen) => } + + ) +} diff --git a/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.module.scss b/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.module.scss index c1dd31265..f2563257c 100644 --- a/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.module.scss +++ b/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.module.scss @@ -14,6 +14,7 @@ border-radius: var(--radius-12); max-width: 400px; opacity: 0; + z-index: 3; pointer-events: none; transition: opacity 0.3s ease, @@ -25,14 +26,14 @@ } .tooltipOpen { - z-index: 2; + z-index: 3; opacity: 1; pointer-events: auto; transform: translateY(21px); } .tooltipOpenAbove { - z-index: 2; + z-index: 3; opacity: 1; pointer-events: auto; top: 0; diff --git a/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.tsx b/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.tsx index 5a4b0a85a..ff78555e9 100644 --- a/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.tsx +++ b/packages/app-earn-ui/src/components/molecules/Tooltip/Tooltip.tsx @@ -3,6 +3,7 @@ import { type FC, type HTMLAttributes, + isValidElement, type ReactNode, useCallback, useEffect, @@ -64,13 +65,19 @@ const TooltipWrapper: FC = ({ children, isOpen, style, show ) } -interface StatefulTooltipProps extends HTMLAttributes { +type ChildrenCallback = (tooltipOpen: boolean) => ReactNode + +interface StatefulTooltipProps { tooltip: ReactNode - children: ReactNode + children: ReactNode | ChildrenCallback tooltipWrapperStyles?: HTMLAttributes['style'] + style?: HTMLAttributes['style'] showAbove?: boolean } +const childrenTypeGuard = (children: ReactNode | ChildrenCallback): children is ReactNode => + isValidElement(children) + export const Tooltip: FC = ({ tooltip, children, @@ -112,7 +119,7 @@ export const Tooltip: FC = ({ const handleClick = useCallback(() => tooltip && setTooltipOpen(true), []) if (!portalElement) { - return children + return childrenTypeGuard(children) ? children : children(tooltipOpen) } const portal = createPortal( @@ -131,7 +138,7 @@ export const Tooltip: FC = ({ className={tooltipStyles.tooltipWrapper} style={style} > - {children} + {childrenTypeGuard(children) ? children : children(tooltipOpen)} {portal}
) diff --git a/packages/app-earn-ui/src/components/organisms/StrategySimulationForm/StrategySimulationForm.tsx b/packages/app-earn-ui/src/components/organisms/StrategySimulationForm/StrategySimulationForm.tsx index 9b6005621..68948c231 100644 --- a/packages/app-earn-ui/src/components/organisms/StrategySimulationForm/StrategySimulationForm.tsx +++ b/packages/app-earn-ui/src/components/organisms/StrategySimulationForm/StrategySimulationForm.tsx @@ -20,16 +20,19 @@ const getStrategyUrl = (selectedStrategy: StrategySimulationFormProps['strategyD export const StrategySimulationForm = ({ strategyData }: StrategySimulationFormProps) => { const [inputValue, setInputValue] = useState('1000') + const handleInputChange = (ev: React.ChangeEvent) => { if (ev.target.value) { setInputValue(mapNumericInput(ev.target.value)) } } + const estimatedEarnings = useMemo(() => { if (!strategyData?.apy) return 0 return Number(inputValue.replaceAll(',', '')) * Number(strategyData.apy) }, [strategyData, inputValue]) + const dropdownLockedValue = useMemo(() => { return { tokenSymbol: strategyData?.symbol, diff --git a/packages/app-earn-ui/src/components/organisms/Table/Table.module.scss b/packages/app-earn-ui/src/components/organisms/Table/Table.module.scss index a85a0367c..ca60a1794 100644 --- a/packages/app-earn-ui/src/components/organisms/Table/Table.module.scss +++ b/packages/app-earn-ui/src/components/organisms/Table/Table.module.scss @@ -57,7 +57,7 @@ background-color: var(--earn-protocol-neutral-80); .details { - padding: 10px; + padding: 0 10px; font-size: 14px; } } diff --git a/packages/app-earn-ui/src/components/organisms/Table/Table.tsx b/packages/app-earn-ui/src/components/organisms/Table/Table.tsx index 9ec8bd5ba..570129956 100644 --- a/packages/app-earn-ui/src/components/organisms/Table/Table.tsx +++ b/packages/app-earn-ui/src/components/organisms/Table/Table.tsx @@ -1,6 +1,8 @@ 'use client' import { Fragment, type ReactNode, useState } from 'react' +import { Icon } from '@/components/atoms/Icon/Icon' + import styles from './Table.module.scss' interface Column { @@ -19,6 +21,7 @@ export function Table({ }: { rows: { content: Row + details?: ReactNode }[] columns: Column[] }) { @@ -83,21 +86,35 @@ export function Table({
setExpandedRow(expandedRow === rowIndex ? null : rowIndex)}> {Object.values(row.content).map((item, idx) => ( - + ))} - {expandedRow === rowIndex && ( + {expandedRow === rowIndex && row.details && ( )} diff --git a/packages/app-earn-ui/src/index.ts b/packages/app-earn-ui/src/index.ts index 77e5362f9..fd579485a 100644 --- a/packages/app-earn-ui/src/index.ts +++ b/packages/app-earn-ui/src/index.ts @@ -7,6 +7,7 @@ export { Text } from './components/atoms/Text/Text' export { Card } from './components/atoms/Card/Card' export { Modal } from './components/atoms/Modal/Modal' export { Icon } from './components/atoms/Icon/Icon' +export { SummerBall } from './components/atoms/SummerBall/SummerBall' export { CheckboxButton } from './components/atoms/CheckboxButton/CheckboxButton' export { Input } from './components/atoms/Input/Input' export { SkeletonLine } from './components/atoms/SkeletonLine/SkeletonLine' @@ -22,6 +23,7 @@ export { Footer } from './components/layout/Footer/Footer' export { Navigation } from './components/layout/Navigation/Navigation' export { StrategyGrid } from './components/layout/StrategyGrid/StrategyGrid' export { StrategyGridDetails } from './components/layout/StrategyGridDetails/StrategyGridDetails' +export { MainBackground } from './components/layout/BackgroundComponents/MainBackground' export { Tooltip } from './components/molecules/Tooltip/Tooltip' export { Dropdown } from './components/molecules/Dropdown/Dropdown' @@ -34,6 +36,7 @@ export { SidebarFootnote } from './components/molecules/SidebarFootnote/SidebarF export { StrategyCard } from './components/molecules/StrategyCard/StrategyCard' export { StrategyTitleWithRisk } from './components/molecules/StrategyTitleWithRisk/StrategyTitleWithRisk' export { TableCellText } from './components/molecules/TableCellText/TableCellText' +export { TableHeadWithTooltip } from './components/molecules/TableHeadWithTooltip/TableHeadWithTooltip' export { TokensGroup } from './components/molecules/TokensGroup/TokensGroup' export { TermsOfService } from './components/organisms/TermsOfService/TermsOfService' diff --git a/packages/app-earn-ui/src/styles/index.scss b/packages/app-earn-ui/src/styles/index.scss index 8e8e74380..0078b6c47 100644 --- a/packages/app-earn-ui/src/styles/index.scss +++ b/packages/app-earn-ui/src/styles/index.scss @@ -102,4 +102,6 @@ --gradient-earn-protocol-dark: linear-gradient(90deg, #ff49a4 0.78%, #b049ff 99.57%); --shadow-depth-1: 0px 0px 8px 0px #0000001A; + --shadow-depth-2: 0px 0px 8px 8px #00000026; + --shadow-depth-3: 0px 4px 8px 0px #00000033; }
{item as ReactNode} + {idx === 0 && row.details ? ( +
+ {item as ReactNode}{' '} + {/* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */} + {idx === 0 ? ( + + ) : null} +
+ ) : ( + <>{item as ReactNode} + )} +
- {/* Expandable row content here */} -
-

- Dummy Details: -

-

- dummy

-

- details

-

- bro

-
+
{row.details}