diff --git a/.changeset/eager-ants-laugh.md b/.changeset/eager-ants-laugh.md new file mode 100644 index 0000000000..4e38a2c797 --- /dev/null +++ b/.changeset/eager-ants-laugh.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +add Markets tab to Dashboard page diff --git a/apps/evm/src/components/AdCarousel/Banner/index.tsx b/apps/evm/src/components/AdCarousel/Banner/index.tsx new file mode 100644 index 0000000000..58d21b8e7c --- /dev/null +++ b/apps/evm/src/components/AdCarousel/Banner/index.tsx @@ -0,0 +1,46 @@ +import { cn } from '@venusprotocol/ui'; +import type { To } from 'react-router'; + +import { ButtonWrapper } from 'components'; +import { Link } from 'containers/Link'; + +export interface BannerProps { + title: string; + description: string; + buttonLabel: string; + children?: React.ReactNode; + to?: To; + href?: string; + className?: string; +} + +export const Banner: React.FC = ({ + className, + title, + description, + buttonLabel, + to, + href, + children, +}) => ( +
+
{children}
+ +
+

{title}

+ +

{description}

+ + + + {buttonLabel} + + +
+
+); diff --git a/apps/evm/src/components/AdCarousel/BoostBanner/index.tsx b/apps/evm/src/components/AdCarousel/BoostBanner/index.tsx new file mode 100644 index 0000000000..712155797a --- /dev/null +++ b/apps/evm/src/components/AdCarousel/BoostBanner/index.tsx @@ -0,0 +1,26 @@ +import { VENUS_DOC_URL } from 'constants/production'; +import { useTranslation } from 'libs/translations'; +import { Banner } from '../Banner'; +import rocketIllustrationSrc from './rocket.png'; + +const LEARN_MORE_URL = `${VENUS_DOC_URL}/guides/leveraged-positions`; + +export const BoostBanner: React.FC = () => { + const { t } = useTranslation(); + + return ( + + {t('adCarousel.boostBanner.rocketIllustration.altText')} + + ); +}; diff --git a/apps/evm/src/components/AdCarousel/BoostBanner/rocket.png b/apps/evm/src/components/AdCarousel/BoostBanner/rocket.png new file mode 100644 index 0000000000..64b1b652fc Binary files /dev/null and b/apps/evm/src/components/AdCarousel/BoostBanner/rocket.png differ diff --git a/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/background.jpg b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/background.jpg new file mode 100644 index 0000000000..1c491fdf34 Binary files /dev/null and b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/background.jpg differ diff --git a/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/coins.png b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/coins.png new file mode 100644 index 0000000000..6155d8c5b9 Binary files /dev/null and b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/coins.png differ diff --git a/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/index.tsx b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/index.tsx new file mode 100644 index 0000000000..2050603294 --- /dev/null +++ b/apps/evm/src/components/AdCarousel/IsolatedPoolsSunsetBanner/index.tsx @@ -0,0 +1,32 @@ +import { VENUS_COMMUNITY_URL } from 'constants/production'; +import { useTranslation } from 'libs/translations'; +import { Banner } from '../Banner'; +import backgroundIllustrationJpg from './background.jpg'; +import coinsIllustrationSrc from './coins.png'; + +const LEARN_MORE_URL = `${VENUS_COMMUNITY_URL}/t/isolated-pools-sunset/5603`; + +export const IsolatedPoolsSunsetBanner: React.FC = () => { + const { t } = useTranslation(); + + return ( + + {t('adCarousel.isolatedPoolsSunsetBanner.backgroundIllustration.altText')} + + {t('adCarousel.isolatedPoolsSunsetBanner.coinsIllustration.altText')} + + ); +}; diff --git a/apps/evm/src/components/AdCarousel/ProbableBanner/background.jpg b/apps/evm/src/components/AdCarousel/ProbableBanner/background.jpg new file mode 100644 index 0000000000..40f5a82da8 Binary files /dev/null and b/apps/evm/src/components/AdCarousel/ProbableBanner/background.jpg differ diff --git a/apps/evm/src/components/AdCarousel/ProbableBanner/coin.png b/apps/evm/src/components/AdCarousel/ProbableBanner/coin.png new file mode 100644 index 0000000000..bfa6d81a8a Binary files /dev/null and b/apps/evm/src/components/AdCarousel/ProbableBanner/coin.png differ diff --git a/apps/evm/src/components/AdCarousel/ProbableBanner/index.tsx b/apps/evm/src/components/AdCarousel/ProbableBanner/index.tsx new file mode 100644 index 0000000000..1ac6ce81d9 --- /dev/null +++ b/apps/evm/src/components/AdCarousel/ProbableBanner/index.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from 'libs/translations'; +import { Banner } from '../Banner'; +import backgroundIllustrationJpg from './background.jpg'; +import coinIllustrationSrc from './coin.png'; + +const LEARN_MORE_URL = 'https://probable.markets'; + +export const ProbableBanner: React.FC = () => { + const { t } = useTranslation(); + + return ( + + {t('adCarousel.probableBanner.backgroundIllustration.altText')} + + {t('adCarousel.probableBanner.coinIllustration.altText')} + + ); +}; diff --git a/apps/evm/src/components/AdCarousel/VenusFluxBanner/background.jpg b/apps/evm/src/components/AdCarousel/VenusFluxBanner/background.jpg new file mode 100644 index 0000000000..e72f66a3e6 Binary files /dev/null and b/apps/evm/src/components/AdCarousel/VenusFluxBanner/background.jpg differ diff --git a/apps/evm/src/components/AdCarousel/VenusFluxBanner/index.tsx b/apps/evm/src/components/AdCarousel/VenusFluxBanner/index.tsx new file mode 100644 index 0000000000..31daf61656 --- /dev/null +++ b/apps/evm/src/components/AdCarousel/VenusFluxBanner/index.tsx @@ -0,0 +1,31 @@ +import { VENUS_FLUX_URL } from 'constants/production'; +import { useTranslation } from 'libs/translations'; +import { Banner } from '../Banner'; +import backgroundIllustrationJpg from './background.jpg'; +import venusFluxIconSrc from './venusFluxIcon.png'; + +export const VenusFluxBanner: React.FC = () => { + const { t } = useTranslation(); + + return ( + + {t('adCarousel.venusFluxBanner.backgroundIllustration.altText')} + + {t('adCarousel.venusFluxBanner.venusFluxIcon.altText')} + + ); +}; diff --git a/apps/evm/src/components/AdCarousel/VenusFluxBanner/venusFluxIcon.png b/apps/evm/src/components/AdCarousel/VenusFluxBanner/venusFluxIcon.png new file mode 100644 index 0000000000..3ea86a3c69 Binary files /dev/null and b/apps/evm/src/components/AdCarousel/VenusFluxBanner/venusFluxIcon.png differ diff --git a/apps/evm/src/components/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/components/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 0000000000..c0a9e3aa21 --- /dev/null +++ b/apps/evm/src/components/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`AdCarousel > displays correctly 1`] = `"Boost is available now!One-click leverage to boost your yield up to 5xLearn moreShare $1M in Venus Flux IncentivesLet your liquidity Flux, watch your yield grow.Start nowBelieve in something?Give your conviction a boost on probably.marketsStart nowIsolated pools are being sunsetClaim your rewards and close your positionsLearn more"`; diff --git a/apps/evm/src/components/AdCarousel/__tests__/index.spec.tsx b/apps/evm/src/components/AdCarousel/__tests__/index.spec.tsx new file mode 100644 index 0000000000..2b0dacdfde --- /dev/null +++ b/apps/evm/src/components/AdCarousel/__tests__/index.spec.tsx @@ -0,0 +1,17 @@ +import type { Mock } from 'vitest'; + +import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; +import { renderComponent } from 'testUtils/render'; +import { AdCarousel } from '..'; + +describe('AdCarousel', () => { + beforeEach(() => { + (useIsFeatureEnabled as Mock).mockImplementation(() => true); + }); + + it('displays correctly', () => { + const { container } = renderComponent(); + + expect(container.textContent).toMatchSnapshot(); + }); +}); diff --git a/apps/evm/src/components/AdCarousel/index.tsx b/apps/evm/src/components/AdCarousel/index.tsx new file mode 100644 index 0000000000..981ad7d8a5 --- /dev/null +++ b/apps/evm/src/components/AdCarousel/index.tsx @@ -0,0 +1,35 @@ +import { Carousel, CarouselItem } from 'components'; +import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; +import { BoostBanner } from './BoostBanner'; +import { IsolatedPoolsSunsetBanner } from './IsolatedPoolsSunsetBanner'; +import { ProbableBanner } from './ProbableBanner'; +import { VenusFluxBanner } from './VenusFluxBanner'; + +export const AdCarousel: React.FC = () => { + const isPrimeCalculatorFeatureEnabled = useIsFeatureEnabled({ name: 'primeCalculator' }); + + const slides: React.ReactNode[] = []; + + if (isPrimeCalculatorFeatureEnabled) { + slides.push( + , + , + , + , + ); + } + + return ( +
+ + {slides.map((slide, i) => ( + {slide} + ))} + +
+ ); +}; diff --git a/apps/evm/src/components/Carousel/index.tsx b/apps/evm/src/components/Carousel/index.tsx index e2b146d4f6..d8f052aaff 100644 --- a/apps/evm/src/components/Carousel/index.tsx +++ b/apps/evm/src/components/Carousel/index.tsx @@ -79,7 +79,7 @@ export const Carousel = forwardRef< key={i} className={cn( 'size-1.5 rounded-full cursor-pointer', - i === activeSlideIndex ? 'bg-white' : 'bg-white/30', + i === activeSlideIndex ? 'bg-blue' : 'bg-white', )} /> ))} diff --git a/apps/evm/src/components/Icon/icons/crown.tsx b/apps/evm/src/components/Icon/icons/crown.tsx new file mode 100644 index 0000000000..b0b9d4ccfe --- /dev/null +++ b/apps/evm/src/components/Icon/icons/crown.tsx @@ -0,0 +1,19 @@ +import type { SVGProps } from 'react'; + +const SvgCrown = (props: SVGProps) => ( + + + +); + +export default SvgCrown; diff --git a/apps/evm/src/components/Icon/icons/graphOutline.tsx b/apps/evm/src/components/Icon/icons/graphOutline.tsx new file mode 100644 index 0000000000..148c287e9c --- /dev/null +++ b/apps/evm/src/components/Icon/icons/graphOutline.tsx @@ -0,0 +1,37 @@ +import type { SVGProps } from 'react'; + +const SvgGraphOutline = (props: SVGProps) => ( + + + + + + + + +); + +export default SvgGraphOutline; diff --git a/apps/evm/src/components/Icon/icons/index.ts b/apps/evm/src/components/Icon/icons/index.ts index d86e6758b0..5085c5b27d 100644 --- a/apps/evm/src/components/Icon/icons/index.ts +++ b/apps/evm/src/components/Icon/icons/index.ts @@ -63,6 +63,7 @@ export { default as infinity } from './infinity'; export { default as shield2 } from './shield2'; export { default as lightning2 } from './lightning2'; export { default as graph } from './graph'; +export { default as graphOutline } from './graphOutline'; export { default as star } from './star'; export { default as download } from './download'; export { default as arrowUpFull2 } from './arrowUpFull2'; @@ -85,3 +86,4 @@ export { default as closedEye } from './closedEye'; export { default as pending } from './pending'; export { default as innerArrows } from './innerArrows'; export { default as outerArrows } from './outerArrows'; +export { default as crown } from './crown'; diff --git a/apps/evm/src/components/Table/Head.tsx b/apps/evm/src/components/Table/Head.tsx index 04f80c024b..8d7a9b8922 100644 --- a/apps/evm/src/components/Table/Head.tsx +++ b/apps/evm/src/components/Table/Head.tsx @@ -28,11 +28,13 @@ function Head({ controls, }: HeadProps) { const styles = useStyles(); + return ( {columns.map(column => { const active = orderBy?.key === column.key; + const sortable = controls && !!column.sortRows; return ( ({ align={column.align} > onRequestOrder(column) : undefined} + onClick={sortable ? () => onRequestOrder(column) : undefined} hideSortIcon={false} // @ts-expect-error Override IconComponent with null so it doesn't render IconComponent={null} > {column.label} - {controls && !!column.sortRows && ( + {sortable && (
({ {isFetching && } -
+
{data.map((row, rowIndex) => { const rowKey = rowKeyExtractor(row); const content = ( -
+
{titleColumn.renderCell(row, rowIndex)}
- {otherColumns.map(column => ( - -
{column.renderCell(row, rowIndex)}
-
- ))} +
+ {otherColumns.map(column => ( + +
{column.renderCell(row, rowIndex)}
+
+ ))} +
); @@ -102,7 +104,8 @@ export function TableCards({ ) => rowOnClick(e, row))} diff --git a/apps/evm/src/components/Table/index.stories.tsx b/apps/evm/src/components/Table/index.stories.tsx index 464e2922fc..023a1242f4 100644 --- a/apps/evm/src/components/Table/index.stories.tsx +++ b/apps/evm/src/components/Table/index.stories.tsx @@ -2,7 +2,7 @@ import type { Meta } from '@storybook/react'; import { Table } from '.'; -import { columns, data, orderableColumns, useTableStyles } from './storiesUtils'; +import { columns, data, sortableColumns, useTableStyles } from './storiesUtils'; export default { title: 'Components/Table', @@ -36,7 +36,7 @@ export const WithOrderableColumns = () => { return ( row.token.address} diff --git a/apps/evm/src/components/Table/index.tsx b/apps/evm/src/components/Table/index.tsx index 791bf6df43..b8165f5eb8 100644 --- a/apps/evm/src/components/Table/index.tsx +++ b/apps/evm/src/components/Table/index.tsx @@ -22,6 +22,7 @@ export * from './types'; export function Table({ controls = true, columns, + className, cardClassName, cardColumns, data, @@ -33,6 +34,7 @@ export function Table({ rowKeyExtractor, breakpoint, isFetching, + variant = 'primary', header, placeholder, selectVariant, @@ -73,7 +75,11 @@ export function Table({ }, [data, order]); return ( - + {title && (
{title} @@ -94,6 +100,7 @@ export function Table({ ({ key={rowKey} css={[ styles.link, - styles.getTableRow({ clickable: !!getRowHref || !!rowOnClick }), + styles.getTableRow({ + clickable: !!getRowHref || !!rowOnClick, + rounded: variant === 'secondary', + }), ]} onClick={ rowOnClick diff --git a/apps/evm/src/components/Table/storiesUtils.tsx b/apps/evm/src/components/Table/storiesUtils.tsx index 44c1f48ca8..047a777da3 100644 --- a/apps/evm/src/components/Table/storiesUtils.tsx +++ b/apps/evm/src/components/Table/storiesUtils.tsx @@ -88,7 +88,7 @@ export const columns: TableColumn[] = [ }, ]; -export const orderableColumns: TableColumn[] = columns.map(column => ({ +export const sortableColumns: TableColumn[] = columns.map(column => ({ ...column, sortRows: column.key === 'collateral' diff --git a/apps/evm/src/components/Table/styles.ts b/apps/evm/src/components/Table/styles.ts index 9a8aab8a84..037d64697c 100644 --- a/apps/evm/src/components/Table/styles.ts +++ b/apps/evm/src/components/Table/styles.ts @@ -33,7 +33,7 @@ export const useStyles = (props?: StylesProps) => { } `, getHeader: ({ breakpoint }: { breakpoint?: keyof (typeof BREAKPOINTS)['values'] }) => css` - padding: ${theme.spacing(0, 6)}; + padding: ${theme.spacing(0, 4)}; ${breakpoint && theme.breakpoints.down(breakpoint)} { padding: 0; @@ -88,7 +88,7 @@ export const useStyles = (props?: StylesProps) => { ` } `, - getTableRow: ({ clickable }: { clickable: boolean }) => css` + getTableRow: ({ clickable, rounded }: { clickable: boolean; rounded: boolean }) => css` height: ${theme.spacing(18)}; :hover { @@ -96,21 +96,26 @@ export const useStyles = (props?: StylesProps) => { overflow: hidden; } - > td:first-child { - border-top-left-radius: ${theme.spacing(2)}; - border-bottom-left-radius: ${theme.spacing(2)}; - } - - > td:last-child { - border-bottom-right-radius: ${theme.spacing(2)}; - border-top-right-radius: ${theme.spacing(2)}; - } - ${ - clickable && + rounded && css` - cursor: pointer; - ` + > td:first-child { + border-top-left-radius: ${theme.spacing(2)}; + border-bottom-left-radius: ${theme.spacing(2)}; + } + + > td:last-child { + border-bottom-right-radius: ${theme.spacing(2)}; + border-top-right-radius: ${theme.spacing(2)}; + } + + ${ + clickable && + css` + cursor: pointer; + ` + } + ` } `, cellTitleMobile: css` @@ -138,15 +143,15 @@ export const useStyles = (props?: StylesProps) => { } .MuiTableCell-root:first-of-type { - padding-left: ${theme.spacing(6)}; + padding-left: ${theme.spacing(4)}; } .MuiTableCell-root:last-child { - padding-right: ${theme.spacing(6)}; + padding-right: ${theme.spacing(4)}; } `, - tableSortLabel: ({ orderable }: { orderable: boolean }) => css` - cursor: ${orderable ? 'pointer' : 'auto'}; + tableSortLabel: ({ sortable }: { sortable: boolean }) => css` + cursor: ${sortable ? 'pointer' : 'auto'}; &.MuiTableSortLabel-root { span { diff --git a/apps/evm/src/components/Table/types.ts b/apps/evm/src/components/Table/types.ts index 42d40e05a7..e0ba0c855a 100644 --- a/apps/evm/src/components/Table/types.ts +++ b/apps/evm/src/components/Table/types.ts @@ -1,7 +1,9 @@ +import type { CSSProperties } from 'react'; +import type { To } from 'react-router'; + import type { BREAKPOINTS } from 'App/MuiThemeProvider/muiTheme'; import type { CardProps } from 'components/Card'; import type { SelectProps } from 'components/Select'; -import type { CSSProperties } from 'react'; export interface TableColumn { key: string; @@ -28,12 +30,13 @@ export interface TableProps extends Omit { className?: string; isFetching?: boolean; rowOnClick?: (e: React.MouseEvent, row: R) => void; - getRowHref?: (row: R) => string; + getRowHref?: (row: R) => To; title?: React.ReactNode | string; header?: React.ReactNode; placeholder?: React.ReactNode; selectVariant?: SelectProps['variant']; cellHeight?: CSSProperties['height']; + variant?: 'primary' | 'secondary'; controls?: boolean; } diff --git a/apps/evm/src/components/TokenGroup/index.stories.tsx b/apps/evm/src/components/TokenGroup/index.stories.tsx deleted file mode 100644 index 251a70ecca..0000000000 --- a/apps/evm/src/components/TokenGroup/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { Meta } from '@storybook/react'; - -import { bnb, eth, lisUsd, usdc, usdt, xvs } from '__mocks__/models/tokens'; - -import type { Token } from 'types'; - -import { TokenGroup } from '.'; - -export default { - title: 'Components/TokenGroup', - component: TokenGroup, -} as Meta; - -const tokens: Token[] = [usdt, eth, usdc, xvs, bnb, lisUsd]; - -export const Default = () => ; - -export const WithLimit = () => ; diff --git a/apps/evm/src/components/TokenGroup/index.tsx b/apps/evm/src/components/TokenGroup/index.tsx deleted file mode 100644 index e86248274c..0000000000 --- a/apps/evm/src/components/TokenGroup/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import Typography from '@mui/material/Typography'; - -import type { Token } from 'types'; - -import { TokenIcon } from '../TokenIcon'; -import { useStyles } from './styles'; - -export interface TokenGroupProps { - className?: string; - tokens: Token[]; - limit?: number; -} - -export const TokenGroup: React.FC = ({ className, tokens, limit = 0 }) => { - const styles = useStyles(); - - const filteredTokens = limit > 0 ? tokens.slice(0, limit) : tokens; - - return ( -
- {filteredTokens.map(token => ( - - ))} - - {limit > 0 && tokens.length > limit && ( - - +{tokens.length - limit} - - )} -
- ); -}; diff --git a/apps/evm/src/components/TokenGroup/styles.ts b/apps/evm/src/components/TokenGroup/styles.ts deleted file mode 100644 index f04a4e567d..0000000000 --- a/apps/evm/src/components/TokenGroup/styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { css } from '@emotion/react'; -import { useTheme } from '@mui/material'; - -export const useStyles = () => { - const theme = useTheme(); - - return { - container: css` - display: flex; - align-items: center; - `, - token: css` - margin-right: ${theme.spacing(1)}; - `, - leftoverCount: css` - color: ${theme.palette.text.primary}; - `, - }; -}; diff --git a/apps/evm/src/components/TokenIcon/index.tsx b/apps/evm/src/components/TokenIcon/index.tsx index 67245cc507..3a31dc2ed8 100644 --- a/apps/evm/src/components/TokenIcon/index.tsx +++ b/apps/evm/src/components/TokenIcon/index.tsx @@ -6,29 +6,14 @@ import type { Token } from 'types'; export interface TokenIconProps { token: Token; displayChain?: boolean; - size?: 'md' | 'lg'; className?: string; } -export const TokenIcon: React.FC = ({ - className, - token, - displayChain = false, - size = 'md', -}) => { +export const TokenIcon: React.FC = ({ className, token, displayChain = false }) => { const chain = chains[token.chainId]; return ( -
+
{token.symbol} {displayChain && ( diff --git a/apps/evm/src/components/TokenIconWithSymbol/index.tsx b/apps/evm/src/components/TokenIconWithSymbol/index.tsx index 9474ebcdcd..b38c3daca9 100644 --- a/apps/evm/src/components/TokenIconWithSymbol/index.tsx +++ b/apps/evm/src/components/TokenIconWithSymbol/index.tsx @@ -5,6 +5,7 @@ import { TokenIcon, type TokenIconProps } from '../TokenIcon'; export interface TokenIconWithSymbolProps extends TokenIconProps { tokenIconClassName?: string; + size?: 'sm' | 'md' | 'lg'; } export const TokenIconWithSymbol: React.FC = ({ @@ -17,8 +18,18 @@ export const TokenIconWithSymbol: React.FC = ({ const chain = chains[token.chainId]; return ( -
- +
+

= ({ > @@ -142,7 +142,7 @@ export const TokenListWrapper: React.FC = ({ > diff --git a/apps/evm/src/components/index.ts b/apps/evm/src/components/index.ts index 9cdb773db9..a01b28659b 100644 --- a/apps/evm/src/components/index.ts +++ b/apps/evm/src/components/index.ts @@ -39,7 +39,6 @@ export * from './SelectTokenTextField'; export * from './InfoIcon'; export * from './CellGroup'; export * from './Cell'; -export * from './TokenGroup'; export * from './TagGroup'; export * from './SpendingLimit'; export * from './MarkdownEditor'; @@ -64,4 +63,4 @@ export * from './TokenIcon'; export * from './BalanceUpdates'; export * from './TokenListWrapper'; export * from './Wrapper'; -export * from './AssetCard'; +export * from './AdCarousel'; diff --git a/apps/evm/src/components/AssetCard/index.stories.tsx b/apps/evm/src/containers/AssetCard/index.stories.tsx similarity index 100% rename from apps/evm/src/components/AssetCard/index.stories.tsx rename to apps/evm/src/containers/AssetCard/index.stories.tsx diff --git a/apps/evm/src/components/AssetCard/index.tsx b/apps/evm/src/containers/AssetCard/index.tsx similarity index 79% rename from apps/evm/src/components/AssetCard/index.tsx rename to apps/evm/src/containers/AssetCard/index.tsx index 9e1113e6a6..ecda169493 100644 --- a/apps/evm/src/components/AssetCard/index.tsx +++ b/apps/evm/src/containers/AssetCard/index.tsx @@ -2,8 +2,8 @@ import { cn } from '@venusprotocol/ui'; import type { Address } from 'viem'; import { Apy, type ApyProps, Card, TokenIconWithSymbol } from 'components'; -import { routes } from 'constants/routing'; import { Link } from 'containers/Link'; +import { useMarketPageTo } from 'hooks/useMarketPageTo'; import type { Asset } from 'types'; export interface AssetCardProps { @@ -19,9 +19,12 @@ export const AssetCard: React.FC = ({ type, className, }) => { - const to = routes.market.path - .replace(':poolComptrollerAddress', poolComptrollerContractAddress) - .replace(':vTokenAddress', asset.vToken.address); + const { formatMarketPageTo } = useMarketPageTo(); + const to = formatMarketPageTo({ + poolComptrollerContractAddress, + vTokenAddress: asset.vToken.address, + tabId: type, + }); return ( , 'className'> { analyticVariant?: string; + buttonClassName?: ButtonProps['className']; buttonVariant?: ButtonProps['variant']; - small?: boolean; + buttonSize?: ButtonProps['size']; message?: string; className?: string; children?: React.ReactNode; @@ -16,8 +19,9 @@ export const ConnectWallet: React.FC = ({ children, message, analyticVariant, + buttonClassName, buttonVariant, - small = false, + buttonSize, ...otherProps }) => { const { accountAddress } = useAccountAddress(); @@ -41,10 +45,10 @@ export const ConnectWallet: React.FC = ({ {!!message && } diff --git a/apps/evm/src/containers/Layout/Header/MarketInfo/index.tsx b/apps/evm/src/containers/Layout/Header/MarketInfo/index.tsx index 5e2a5f8c09..62e10a1459 100644 --- a/apps/evm/src/containers/Layout/Header/MarketInfo/index.tsx +++ b/apps/evm/src/containers/Layout/Header/MarketInfo/index.tsx @@ -4,7 +4,7 @@ import { CellGroup, type CellProps, Icon, Spinner, TokenIcon, Wrapper } from 'co import { NULL_ADDRESS } from 'constants/address'; import { PLACEHOLDER_KEY } from 'constants/placeholders'; import { Link } from 'containers/Link'; -import { useGetMarketsPagePath } from 'hooks/useGetMarketsPagePath'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import { useMemo } from 'react'; @@ -23,7 +23,7 @@ export const MarketInfo = () => { const { t } = useTranslation(); - const { marketsPagePath } = useGetMarketsPagePath(); + const { marketsPagePath } = useMarketsPagePath(); const { data: getAssetData } = useGetAsset({ vTokenAddress, diff --git a/apps/evm/src/containers/Layout/NavBar/useMenuItems/index.tsx b/apps/evm/src/containers/Layout/NavBar/useMenuItems/index.tsx index e9bd056113..66f3e5098b 100644 --- a/apps/evm/src/containers/Layout/NavBar/useMenuItems/index.tsx +++ b/apps/evm/src/containers/Layout/NavBar/useMenuItems/index.tsx @@ -4,7 +4,7 @@ import { useAccountAddress } from 'libs/wallet'; import { useGetPools } from 'clients/api'; // import { VENUS_FLUX_URL } from 'constants/production'; -import { useGetMarketsPagePath } from 'hooks/useGetMarketsPagePath'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; import { useTranslation } from 'libs/translations'; import type { MenuItem, SubMenu } from '../types'; // import venusCoreIconSrc from './venusCoreIcon.png'; @@ -16,7 +16,7 @@ export const useMenuItems = () => { const swapRouteEnabled = useIsFeatureEnabled({ name: 'swapRoute' }); const vaiRouteEnabled = useIsFeatureEnabled({ name: 'vaiRoute' }); const bridgeRouteEnabled = useIsFeatureEnabled({ name: 'bridgeRoute' }); - const { marketsPagePath } = useGetMarketsPagePath(); + const { marketsPagePath } = useMarketsPagePath(); const { data: getPoolsData } = useGetPools(); const pools = getPoolsData?.pools || []; diff --git a/apps/evm/src/containers/Link/index.tsx b/apps/evm/src/containers/Link/index.tsx index d817ba6fe3..43e07ef1e4 100644 --- a/apps/evm/src/containers/Link/index.tsx +++ b/apps/evm/src/containers/Link/index.tsx @@ -14,7 +14,8 @@ export type LinkProps = (RRLinkProps | React.AnchorHTMLAttributes = forwardRef( ({ className, children, noStyle, chainId, ...otherProps }, ref) => { const { formatTo } = useFormatTo(); - const formattedTo = 'to' in otherProps ? formatTo({ to: otherProps.to, chainId }) : undefined; + const formattedTo = + 'to' in otherProps && otherProps.to ? formatTo({ to: otherProps.to, chainId }) : undefined; const classes = cn( !noStyle && diff --git a/apps/evm/src/containers/MarketLoader/index.tsx b/apps/evm/src/containers/MarketLoader/index.tsx index d4bd4f7904..312e235b40 100644 --- a/apps/evm/src/containers/MarketLoader/index.tsx +++ b/apps/evm/src/containers/MarketLoader/index.tsx @@ -2,7 +2,7 @@ import { useGetAsset, useGetPool } from 'clients/api'; import { Spinner } from 'components'; import { NULL_ADDRESS } from 'constants/address'; import { Redirect } from 'containers/Redirect'; -import { useGetMarketsPagePath } from 'hooks/useGetMarketsPagePath'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; import { useAccountAddress } from 'libs/wallet'; import type { Asset, Pool } from 'types'; import type { Address } from 'viem'; @@ -22,7 +22,7 @@ export const MarketLoader: React.FC = ({ children, }) => { const { accountAddress } = useAccountAddress(); - const { marketsPagePath } = useGetMarketsPagePath(); + const { marketsPagePath } = useMarketsPagePath(); const { data: getAssetData, isLoading: isGetAssetLoading } = useGetAsset({ vTokenAddress, diff --git a/apps/evm/src/containers/MarketTable/index.tsx b/apps/evm/src/containers/MarketTable/index.tsx index 3128c3325a..c39b184bc2 100644 --- a/apps/evm/src/containers/MarketTable/index.tsx +++ b/apps/evm/src/containers/MarketTable/index.tsx @@ -4,12 +4,13 @@ import { type InputHTMLAttributes, useMemo } from 'react'; import type { Address } from 'viem'; import { Card, Delimiter, Table, type TableProps, TextField, Toggle } from 'components'; -import { routes } from 'constants/routing'; import { SwitchChainNotice } from 'containers/SwitchChainNotice'; import { useBreakpointUp } from 'hooks/responsive'; import { useCollateral } from 'hooks/useCollateral'; +import { useMarketPageTo } from 'hooks/useMarketPageTo'; import { handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; +import { useAccountChainId, useChainId } from 'libs/wallet'; import type { Asset, EModeGroup } from 'types'; import pauseIconSrc from './pause.svg'; import { useStyles } from './styles'; @@ -17,6 +18,8 @@ import type { ColumnKey } from './types'; import { useColumns } from './useColumns'; import { useControls } from './useControls'; +export * from './types'; + export interface MarketTableProps extends Partial< Omit, 'columns' | 'rowKeyIndex' | 'initialOrder' | 'getRowHref'> @@ -48,12 +51,14 @@ export const MarketTable: React.FC = ({ controls = true, isFetching, header, + className, ...otherTableProps }) => { const styles = useStyles(); const { t } = useTranslation(); const { toggleCollateral } = useCollateral(); + const { formatMarketPageTo } = useMarketPageTo(); // The fallback breakpoint is just to satisfy TS here, it is not actually used const _isBreakpointUp = useBreakpointUp(breakpoint || '2xl'); @@ -76,6 +81,10 @@ export const MarketTable: React.FC = ({ userEModeGroup, }); + const { chainId: currentChainId } = useChainId(); + const { chainId: accountChainId } = useAccountChainId(); + const isOnWrongChain = accountChainId !== currentChainId; + const handleSearchInputChange: InputHTMLAttributes['onChange'] = changeEvent => onSearchValueChange(changeEvent.currentTarget.value); @@ -113,9 +122,11 @@ export const MarketTable: React.FC = ({ }, [columns, initialOrder]); const getRowHref = (row: Asset) => - routes.market.path - .replace(':poolComptrollerAddress', poolComptrollerContractAddress) - .replace(':vTokenAddress', row.vToken.address); + formatMarketPageTo({ + poolComptrollerContractAddress: poolComptrollerContractAddress, + vTokenAddress: row.vToken.address, + tabId: marketType, + }); return (

= ({ columns={columns} data={filteredAssets} css={styles.cardContentGrid} - className={cn(isBreakpointUp && !title && 'pt-0 sm:pt-0')} + className={cn( + isBreakpointUp && !title && 'pt-0 sm:pt-0', + !isBreakpointUp && 'border-0', + className, + )} title={title} rowKeyExtractor={row => `market-table-row-${marketType}-${row.vToken.address}`} initialOrder={formattedInitialOrder} header={ - (header || controls || columnKeys.includes('collateral')) && ( -
+ (header || controls || (columnKeys.includes('collateral') && isOnWrongChain)) && ( +
{(controls || header) && (
{header} @@ -140,7 +155,7 @@ export const MarketTable: React.FC = ({
diff --git a/apps/evm/src/containers/Vault/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/containers/Vault/__tests__/__snapshots__/index.spec.tsx.snap index da892a8ac6..d5a9db59dd 100644 --- a/apps/evm/src/containers/Vault/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/containers/Vault/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,3 +1,3 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`pages/Vault/Vault > renders vault correctly 1`] = `"VAIYou are staking200VAI Stake APR> 10,000%Daily Emission144Total Staked415VAI Stake APR> 10,000%Daily Emission144Total Staked415StakeWithdraw"`; +exports[`pages/Vault/Vault > renders vault correctly 1`] = `"VAIYou are staking200VAI Stake APR> 10,000%Daily emission144Total staked415VAI Stake APR> 10,000%Daily emission144Total staked415StakeWithdraw"`; diff --git a/apps/evm/src/containers/Vault/index.tsx b/apps/evm/src/containers/Vault/index.tsx index 5a55c2d233..014577ac09 100644 --- a/apps/evm/src/containers/Vault/index.tsx +++ b/apps/evm/src/containers/Vault/index.tsx @@ -163,7 +163,7 @@ export const Vault: React.FC = ({ vault, variant = 'primary', classN isPrimeEnabled && primePoolIndex !== undefined && vault.poolIndex === primePoolIndex && ( - + )} {/* Mobile */} diff --git a/apps/evm/src/hooks/useMarketPageTo/__tests__/index.spec.tsx b/apps/evm/src/hooks/useMarketPageTo/__tests__/index.spec.tsx new file mode 100644 index 0000000000..880c906378 --- /dev/null +++ b/apps/evm/src/hooks/useMarketPageTo/__tests__/index.spec.tsx @@ -0,0 +1,63 @@ +import { renderHook } from '@testing-library/react'; +import type { Mock } from 'vitest'; + +import { useFormatTo } from 'hooks/useFormatTo'; +import { TAB_PARAM_KEY } from 'hooks/useTabs'; + +import { useMarketPageTo } from '..'; + +vi.mock('hooks/useFormatTo'); + +describe('useMarketPageTo', () => { + const poolComptrollerContractAddress = '0x1111111111111111111111111111111111111111'; + const vTokenAddress = '0x2222222222222222222222222222222222222222'; + + it('returns the formatted market page route with the default tab', () => { + const formattedTo = { pathname: '/formatted', search: '?tab=supply' }; + const mockFormatTo = vi.fn(() => formattedTo); + + (useFormatTo as Mock).mockImplementation(() => ({ + formatTo: mockFormatTo, + })); + + const { result } = renderHook(() => useMarketPageTo()); + + const marketPageTo = result.current.formatMarketPageTo({ + poolComptrollerContractAddress, + vTokenAddress, + }); + + expect(mockFormatTo).toHaveBeenCalledWith({ + to: { + pathname: `/markets/${poolComptrollerContractAddress}/${vTokenAddress}`, + search: `${TAB_PARAM_KEY}=supply`, + }, + }); + expect(marketPageTo).toBe(formattedTo); + }); + + it('returns the formatted market page route with the provided tab', () => { + const formattedTo = { pathname: '/formatted', search: '?tab=borrow' }; + const mockFormatTo = vi.fn(() => formattedTo); + + (useFormatTo as Mock).mockImplementation(() => ({ + formatTo: mockFormatTo, + })); + + const { result } = renderHook(() => useMarketPageTo()); + + const marketPageTo = result.current.formatMarketPageTo({ + poolComptrollerContractAddress, + vTokenAddress, + tabId: 'borrow', + }); + + expect(mockFormatTo).toHaveBeenCalledWith({ + to: { + pathname: `/markets/${poolComptrollerContractAddress}/${vTokenAddress}`, + search: `${TAB_PARAM_KEY}=borrow`, + }, + }); + expect(marketPageTo).toBe(formattedTo); + }); +}); diff --git a/apps/evm/src/hooks/useMarketPageTo/index.ts b/apps/evm/src/hooks/useMarketPageTo/index.ts new file mode 100644 index 0000000000..d1c3631717 --- /dev/null +++ b/apps/evm/src/hooks/useMarketPageTo/index.ts @@ -0,0 +1,28 @@ +import type { Address } from 'viem'; + +import { routes } from 'constants/routing'; +import { useFormatTo } from 'hooks/useFormatTo'; +import { TAB_PARAM_KEY } from 'hooks/useTabs'; + +export const useMarketPageTo = () => { + const { formatTo } = useFormatTo(); + + const formatMarketPageTo = ({ + poolComptrollerContractAddress, + vTokenAddress, + tabId = 'supply', + }: { poolComptrollerContractAddress: Address; vTokenAddress: Address; tabId?: string }) => { + const pathname = routes.market.path + .replace(':poolComptrollerAddress', poolComptrollerContractAddress) + .replace(':vTokenAddress', vTokenAddress); + + return formatTo({ + to: { + pathname, + search: `${TAB_PARAM_KEY}=${tabId}`, + }, + }); + }; + + return { formatMarketPageTo }; +}; diff --git a/apps/evm/src/hooks/useGetMarketsPagePath/__tests__/index.spec.ts b/apps/evm/src/hooks/useMarketsPagePath/__tests__/index.spec.ts similarity index 62% rename from apps/evm/src/hooks/useGetMarketsPagePath/__tests__/index.spec.ts rename to apps/evm/src/hooks/useMarketsPagePath/__tests__/index.spec.ts index 76cef02538..82e76b2180 100644 --- a/apps/evm/src/hooks/useGetMarketsPagePath/__tests__/index.spec.ts +++ b/apps/evm/src/hooks/useMarketsPagePath/__tests__/index.spec.ts @@ -1,8 +1,8 @@ -import { useGetMarketsPagePath } from '..'; +import { useMarketsPagePath } from '..'; -describe('useGetMarketsPagePath', () => { +describe('useMarketsPagePath', () => { it('returns the correct Markets page path', async () => { - const result = useGetMarketsPagePath(); + const result = useMarketsPagePath(); expect(result).toMatchInlineSnapshot(` { diff --git a/apps/evm/src/hooks/useGetMarketsPagePath/index.tsx b/apps/evm/src/hooks/useMarketsPagePath/index.tsx similarity index 87% rename from apps/evm/src/hooks/useGetMarketsPagePath/index.tsx rename to apps/evm/src/hooks/useMarketsPagePath/index.tsx index 1e6d0b0936..7782b0cdeb 100644 --- a/apps/evm/src/hooks/useGetMarketsPagePath/index.tsx +++ b/apps/evm/src/hooks/useMarketsPagePath/index.tsx @@ -1,7 +1,7 @@ import { routes } from 'constants/routing'; import { useChain } from 'hooks/useChain'; -export const useGetMarketsPagePath = () => { +export const useMarketsPagePath = () => { const { corePoolComptrollerContractAddress } = useChain(); const marketsPagePath = routes.markets.path.replace( diff --git a/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap index effd4f1b02..3a4ba7900c 100644 --- a/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap @@ -2,6 +2,7 @@ exports[`useUserChainSettings > returns correct settings from the store 1`] = ` { + "doNotExpandGuide": false, "doNotShowImportPositionsModal": false, "doNotShowUserBalances": false, "gaslessTransactions": true, diff --git a/apps/evm/src/hooks/useUserChainSettings/index.tsx b/apps/evm/src/hooks/useUserChainSettings/index.tsx index 96f27d49a6..ab2c282b77 100644 --- a/apps/evm/src/hooks/useUserChainSettings/index.tsx +++ b/apps/evm/src/hooks/useUserChainSettings/index.tsx @@ -9,6 +9,7 @@ export const defaultUserChainSettings: UserChainSettings = { showUserEModeAssetsOnly: false, doNotShowImportPositionsModal: false, doNotShowUserBalances: false, + doNotExpandGuide: false, slippageTolerancePercentage: String(DEFAULT_SLIPPAGE_TOLERANCE_PERCENTAGE), }; diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index cfffbad9d6..7eff1258db 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -22,6 +22,49 @@ "learnMore": "Learn more", "startNow": "Start now" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "Learn more", + "description": "One-click leverage to boost your yield up to 5x", + "rocketIllustration": { + "altText": "Rocket illustration" + }, + "title": "Boost is available now!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "Blue desert background" + }, + "buttonLabel": "Learn more", + "coinsIllustration": { + "altText": "Coins illustration" + }, + "description": "Claim your rewards and close your positions", + "title": "Isolated pools are being sunset" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "Horizon background" + }, + "buttonLabel": "Start now", + "coinIllustration": { + "altText": "Coin illustration" + }, + "description": "Give your conviction a boost on probably.markets", + "title": "Believe in something?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "Galaxy background" + }, + "buttonLabel": "Start now", + "description": "Let your liquidity Flux, watch your yield grow.", + "title": "Share $1M in Venus Flux Incentives", + "venusFluxIcon": { + "altText": "Venus Flux icon" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance paused BETH withdrawals on 2023-04-26. We recommend users reduce their exposure to BETH on Venus. Borrowing has been paused. Convert BETH to WBETH on-chain at a 1:1 ratio here." @@ -268,36 +311,40 @@ "connectButton": "Connect wallet" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "Calculate your Prime rewards" - }, - "description": "Venus Prime rewards dedicated users for their lending and borrowing activities.", - "illustration": { - "altText": "Prime illustration" - }, - "title": "Achieve greater heights with Venus Prime" - } - }, "guide": { "completed": "Completed", "step1": { - "description": "Earn yield by supplying assets or migrating your existing positions to the Venus protocol.", + "description": "Earn yield by supplying any asset or moving your existing positions from another protocol to Venus.", "title": "Supply / Migrate" }, "step2": { - "description": "Borrow assets to power up your DeFi strategies, or leverage liquidities in one click.", + "description": "Borrow any asset to power your DeFi strategies, and boost it in one simple click.", "title": "Borrow / Leverage" }, "step3": { - "description": "Deposit XVS into the vault and earn yield powered by the protocol's revenues.", + "description": "Deposit XVS to our vault and earn yield powered by Venus' revenue.", "title": "Stake XVS" }, "uncompleted": "Uncompleted" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "E-mode illustration", + "title": "E-mode: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "Borrowed", + "suppliedTabTitle": "Supplied", + "title": "Assets" + }, "overview": { "absolutePerformance": "Absolute performance", + "healthFactor": { + "tooltip": "Health factor (liquidation at < 1.0)" + }, "netWorth": { "label": "Net worth", "tooltip": "Net available assets after accounting for vault stake and borrowings", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "Browse" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "No borrow positions yet" + } + }, + "placeholder": { + "title": "No supply or borrow positions yet" + }, + "supplyTab": { + "placeholder": { + "title": "No supply positions yet" + } + } + }, "staking": { "placeholder": { "title": "No staking positions yet" @@ -353,15 +415,15 @@ "borrowButton": { "label": "Borrow" }, - "interactiveTitle": "Top Supply / Borrow on Venus", + "interactiveTitle": "Spotlight (supply/borrow)", "supplyButton": { "label": "Supply" }, - "textTitle": "Top markets on Venus" + "textTitle": "Spotlight" }, "transactions": { "placeholder": { - "title": "No transactions yet" + "title": "No transactions" }, "selects": { "source": { @@ -791,6 +853,14 @@ "content": "Your borrowing power is increased by supplying to assets from the {{ eModeGroupName }} group exclusively", "label": "Isolated" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "Borrow APY", + "supplyApyLabel": "Supply APY", + "supplyBalanceLabel": "Supply balance", + "title": "Holdings-based recommendation" + } + }, "tabs": { "assets": { "title": "Assets" @@ -1672,4 +1742,4 @@ }, "withdrawTabTitle": "Withdraw" } -} +} \ No newline at end of file diff --git a/apps/evm/src/libs/translations/translations/ja.json b/apps/evm/src/libs/translations/translations/ja.json index 53f5b9a26f..0898fd6c8f 100644 --- a/apps/evm/src/libs/translations/translations/ja.json +++ b/apps/evm/src/libs/translations/translations/ja.json @@ -22,6 +22,49 @@ "learnMore": "詳細を見る", "startNow": "今すぐ始める" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "詳細を見る", + "description": "ワンクリックのレバレッジで利回りを最大5倍までブースト。", + "rocketIllustration": { + "altText": "ロケットのイラスト" + }, + "title": "Boostが利用可能になりました!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "青い砂漠の背景" + }, + "buttonLabel": "詳細を見る", + "coinsIllustration": { + "altText": "コインのイラスト" + }, + "description": "報酬を請求してポジションを閉じてください", + "title": "隔離プールは終了予定です" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "地平線の背景" + }, + "buttonLabel": "今すぐ始める", + "coinIllustration": { + "altText": "コインのイラスト" + }, + "description": "probable.marketsで信念をブーストしよう", + "title": "何かを信じますか?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "銀河の背景" + }, + "buttonLabel": "今すぐ始める", + "description": "流動性をFluxさせ、利回りの伸びを見届けよう。", + "title": "Venus Fluxインセンティブ $1M を山分け", + "venusFluxIcon": { + "altText": "Venus Flux アイコン" + } + } + }, "announcements": { "bethUpdate": { "description": "Binanceは2023-04-26にBETHの引き出しを停止しました。VenusでのBETHへのエクスポージャーを減らすことを推奨します。借入は停止されています。オンチェーンでBETHをWBETHに1:1の比率で変換できます。こちらをご覧ください。" @@ -268,18 +311,6 @@ "connectButton": "ウォレットを接続" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "Prime報酬を計算" - }, - "description": "Venus Primeはレンディングと借入の活動に対して熱心なユーザーに報酬を付与します。", - "illustration": { - "altText": "Primeのイラスト" - }, - "title": "Venus Primeでさらなる高みへ" - } - }, "guide": { "completed": "完了", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "未完了" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "Eモードのイラスト", + "title": "Eモード:{{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "借入済み", + "suppliedTabTitle": "供給済み", + "title": "資産" + }, "overview": { "absolutePerformance": "絶対パフォーマンス", + "healthFactor": { + "tooltip": "健全性ファクター(清算ライン: < 1.0)" + }, "netWorth": { "label": "純資産", "tooltip": "ボールトステークと借入を考慮した利用可能な純資産", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "閲覧" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "借入ポジションはまだありません" + } + }, + "placeholder": { + "title": "供給/借入ポジションはまだありません" + }, + "supplyTab": { + "placeholder": { + "title": "供給ポジションはまだありません" + } + } + }, "staking": { "placeholder": { "title": "まだステーキングのポジションはありません" @@ -353,11 +415,11 @@ "borrowButton": { "label": "借入" }, - "interactiveTitle": "Venusのトップ供給 / 借入", + "interactiveTitle": "スポットライト(供給/借入)", "supplyButton": { "label": "供給" }, - "textTitle": "Venusのトップマーケット" + "textTitle": "スポットライト" }, "transactions": { "placeholder": { @@ -580,6 +642,7 @@ "protectionTitle": "保護を最優先" }, "safety": { + "audit_one": "監査", "audit_other": "監査", "otherAuditors": "Fairyproof、Hacken、HashExによる
14件以上の監査", "scoreAlt": "スコアのイラスト", @@ -790,6 +853,14 @@ "content": "{{ eModeGroupName }}グループの資産のみに供給することで借入力が高まります", "label": "隔離" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "借入APY", + "supplyApyLabel": "供給APY", + "supplyBalanceLabel": "供給残高", + "title": "推奨" + } + }, "tabs": { "assets": { "title": "資産" diff --git a/apps/evm/src/libs/translations/translations/th.json b/apps/evm/src/libs/translations/translations/th.json index 51f7850fe2..bcc543b6d8 100644 --- a/apps/evm/src/libs/translations/translations/th.json +++ b/apps/evm/src/libs/translations/translations/th.json @@ -22,6 +22,49 @@ "learnMore": "เรียนรู้เพิ่มเติม", "startNow": "เริ่มตอนนี้" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "เรียนรู้เพิ่มเติม", + "description": "เลเวอเรจคลิกเดียวเพื่อบูสต์ผลตอบแทนของคุณ สูงสุด 5 เท่า.", + "rocketIllustration": { + "altText": "ภาพประกอบจรวด" + }, + "title": "Boost ใช้งานได้แล้ว!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "พื้นหลังทะเลทรายสีน้ำเงิน" + }, + "buttonLabel": "เรียนรู้เพิ่มเติม", + "coinsIllustration": { + "altText": "ภาพประกอบเหรียญ" + }, + "description": "รับรางวัลของคุณและปิดตำแหน่งของคุณ", + "title": "พูลแบบแยกกำลังจะยุติการให้บริการ" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "พื้นหลังเส้นขอบฟ้า" + }, + "buttonLabel": "เริ่มตอนนี้", + "coinIllustration": { + "altText": "ภาพประกอบเหรียญ" + }, + "description": "เพิ่มความเชื่อมั่นของคุณบน probable.markets", + "title": "คุณเชื่อในสิ่งใดไหม?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "พื้นหลังกาแล็กซี" + }, + "buttonLabel": "เริ่มตอนนี้", + "description": "ปล่อยให้สภาพคล่องของคุณ Flux แล้วดูผลตอบแทนเติบโต", + "title": "ร่วมแบ่งปันรางวัล Venus Flux มูลค่า $1M", + "venusFluxIcon": { + "altText": "ไอคอน Venus Flux" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance ได้หยุดการถอน BETH เมื่อ 2023-04-26 เราแนะนำให้ผู้ใช้ลดการถือครอง BETH บน Venus การยืมถูกระงับแล้ว สามารถแปลง BETH เป็น WBETH แบบออนเชนที่อัตรา 1:1 ที่นี่." @@ -268,18 +311,6 @@ "connectButton": "เชื่อมต่อกระเป๋า" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "คำนวณรางวัล Prime ของคุณ" - }, - "description": "Venus Prime มอบรางวัลให้กับผู้ใช้ที่ทุ่มเทสำหรับกิจกรรมการฝากและการยืมของพวกเขา", - "illustration": { - "altText": "ภาพประกอบ Prime" - }, - "title": "ก้าวสู่ระดับที่สูงขึ้นด้วย Venus Prime" - } - }, "guide": { "completed": "เสร็จสิ้น", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "ยังไม่เสร็จ" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "ภาพประกอบ E-mode", + "title": "E-mode: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "ยืมแล้ว", + "suppliedTabTitle": "ฝากแล้ว", + "title": "สินทรัพย์" + }, "overview": { "absolutePerformance": "ผลการดำเนินงานแบบสัมบูรณ์", + "healthFactor": { + "tooltip": "ตัวคูณความปลอดภัย (ถูกชำระบัญชีเมื่อ < 1.0)" + }, "netWorth": { "label": "มูลค่าสุทธิ", "tooltip": "สินทรัพย์สุทธิที่พร้อมใช้งานหลังจากคำนึงถึงการสเตคในวอลต์และการยืม", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "เรียกดู" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "ยังไม่มีสถานะการยืม" + } + }, + "placeholder": { + "title": "ยังไม่มีสถานะการฝากหรือยืม" + }, + "supplyTab": { + "placeholder": { + "title": "ยังไม่มีสถานะการฝาก" + } + } + }, "staking": { "placeholder": { "title": "ยังไม่มีสถานะการสเตค" @@ -353,11 +415,11 @@ "borrowButton": { "label": "ยืม" }, - "interactiveTitle": "อันดับสูงสุดของ ฝาก / ยืม บน Venus", + "interactiveTitle": "ไฮไลต์ (ฝาก/ยืม)", "supplyButton": { "label": "ฝาก" }, - "textTitle": "ตลาดยอดนิยมบน Venus" + "textTitle": "ไฮไลต์" }, "transactions": { "placeholder": { @@ -580,6 +642,7 @@ "protectionTitle": "ให้ความสำคัญกับการปกป้อง" }, "safety": { + "audit_one": "การตรวจสอบ", "audit_other": "การตรวจสอบ", "otherAuditors": "+14 การตรวจสอบโดย
Fairyproof, Hacken และ HashEx", "scoreAlt": "ภาพประกอบคะแนน", @@ -790,6 +853,14 @@ "content": "พลังการยืมของคุณจะเพิ่มขึ้นเมื่อฝากเฉพาะสินทรัพย์จากกลุ่ม {{ eModeGroupName }}", "label": "แยก" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "APY การยืม", + "supplyApyLabel": "APY การฝาก", + "supplyBalanceLabel": "ยอดฝากคงเหลือ", + "title": "คำแนะนำ" + } + }, "tabs": { "assets": { "title": "สินทรัพย์" diff --git a/apps/evm/src/libs/translations/translations/tr.json b/apps/evm/src/libs/translations/translations/tr.json index fd81b38a72..822df022c2 100644 --- a/apps/evm/src/libs/translations/translations/tr.json +++ b/apps/evm/src/libs/translations/translations/tr.json @@ -22,6 +22,49 @@ "learnMore": "Daha fazla bilgi", "startNow": "Şimdi başla" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "Daha fazla bilgi", + "description": "Tek tık kaldıraçla getirinizi 5 kata kadar artırın.", + "rocketIllustration": { + "altText": "Roket illüstrasyonu" + }, + "title": "Boost artık kullanılabilir!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "Mavi çöl arka planı" + }, + "buttonLabel": "Daha fazla bilgi", + "coinsIllustration": { + "altText": "Madeni para illüstrasyonu" + }, + "description": "Ödüllerinizi talep edin ve pozisyonlarınızı kapatın", + "title": "İzole havuzlar aşamalı olarak kapatılıyor" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "Ufuk arka planı" + }, + "buttonLabel": "Şimdi başla", + "coinIllustration": { + "altText": "Madeni para illüstrasyonu" + }, + "description": "probable.markets'te inancınızı artırın", + "title": "Bir şeye inanıyor musunuz?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "Galaksi arka planı" + }, + "buttonLabel": "Şimdi başla", + "description": "Likiditenizi Flux'a bırakın, getirinizi büyürken izleyin.", + "title": "Venus Flux teşviklerinden $1M paylaşın", + "venusFluxIcon": { + "altText": "Venus Flux simgesi" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance, 2023-04-26 tarihinde BETH çekimlerini durdurdu. Kullanıcıların Venus üzerindeki BETH maruziyetini azaltmasını öneriyoruz. Borç alma duraklatıldı. BETH'yi zincir üzerinde 1:1 oranında WBETH'ye dönüştürün buradan." @@ -268,18 +311,6 @@ "connectButton": "Cüzdan bağla" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "Prime ödüllerinizi hesaplayın" - }, - "description": "Venus Prime, ödünç verme ve borç alma faaliyetlerinde aktif olan kullanıcılara ödül verir.", - "illustration": { - "altText": "Prime illüstrasyonu" - }, - "title": "Venus Prime ile daha yüksek hedeflere ulaşın" - } - }, "guide": { "completed": "Tamamlandı", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "Tamamlanmadı" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "E-modu illüstrasyonu", + "title": "E-modu: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "Borç alınan", + "suppliedTabTitle": "Sağlanan", + "title": "Varlıklar" + }, "overview": { "absolutePerformance": "Mutlak performans", + "healthFactor": { + "tooltip": "Sağlık faktörü (likidasyon < 1.0)" + }, "netWorth": { "label": "Net değer", "tooltip": "Kasa stake’i ve borçlanmalar dikkate alındıktan sonra kullanılabilir net varlıklar", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "Göz at" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "Henüz borç pozisyonu yok" + } + }, + "placeholder": { + "title": "Henüz sağlama veya borç pozisyonu yok" + }, + "supplyTab": { + "placeholder": { + "title": "Henüz sağlama pozisyonu yok" + } + } + }, "staking": { "placeholder": { "title": "Henüz staking pozisyonu yok" @@ -353,11 +415,11 @@ "borrowButton": { "label": "Borç al" }, - "interactiveTitle": "Venus’ta en iyi Arz / Borç", + "interactiveTitle": "Öne çıkanlar (arz/borç)", "supplyButton": { "label": "Arz" }, - "textTitle": "Venus’taki en iyi piyasalar" + "textTitle": "Öne çıkanlar" }, "transactions": { "placeholder": { @@ -791,6 +853,14 @@ "content": "{{ eModeGroupName }} grubundaki varlıklara yalnızca tedarik yaparak borçlanma gücünüz artar", "label": "İzole" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "Borç APY", + "supplyApyLabel": "Sağlama APY", + "supplyBalanceLabel": "Sağlama bakiyesi", + "title": "Öneri" + } + }, "tabs": { "assets": { "title": "Varlıklar" diff --git a/apps/evm/src/libs/translations/translations/vi.json b/apps/evm/src/libs/translations/translations/vi.json index aef7498803..3959adf5f5 100644 --- a/apps/evm/src/libs/translations/translations/vi.json +++ b/apps/evm/src/libs/translations/translations/vi.json @@ -22,6 +22,49 @@ "learnMore": "Tìm hiểu thêm", "startNow": "Bắt đầu ngay" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "Tìm hiểu thêm", + "description": "Đòn bẩy một lần nhấp để tăng lợi suất tối đa 5x.", + "rocketIllustration": { + "altText": "Hình minh họa tên lửa" + }, + "title": "Tăng cường đã khả dụng!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "Nền sa mạc xanh" + }, + "buttonLabel": "Tìm hiểu thêm", + "coinsIllustration": { + "altText": "Hình minh họa đồng xu" + }, + "description": "Nhận phần thưởng của bạn và đóng vị thế", + "title": "Các pool cô lập đang được dừng" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "Nền đường chân trời" + }, + "buttonLabel": "Bắt đầu ngay", + "coinIllustration": { + "altText": "Hình minh họa đồng xu" + }, + "description": "Tăng niềm tin của bạn trên probable.markets", + "title": "Bạn tin vào điều gì?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "Nền thiên hà" + }, + "buttonLabel": "Bắt đầu ngay", + "description": "Hãy để thanh khoản của bạn Flux, theo dõi lợi suất tăng trưởng.", + "title": "Chia sẻ $1M phần thưởng Venus Flux", + "venusFluxIcon": { + "altText": "Biểu tượng Venus Flux" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance đã tạm dừng rút BETH vào 2023-04-26. Chúng tôi khuyến nghị người dùng giảm mức tiếp xúc với BETH trên Venus. Việc vay đã bị tạm dừng. Chuyển BETH sang WBETH on-chain theo tỷ lệ 1:1 tại đây." @@ -268,18 +311,6 @@ "connectButton": "Kết nối ví" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "Tính phần thưởng Prime của bạn" - }, - "description": "Venus Prime thưởng cho người dùng tận tâm vì hoạt động cho vay và vay của họ.", - "illustration": { - "altText": "Minh họa Prime" - }, - "title": "Đạt tầm cao mới cùng Venus Prime" - } - }, "guide": { "completed": "Hoàn thành", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "Chưa hoàn thành" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "Hình minh họa E-mode", + "title": "E-mode: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "Đã vay", + "suppliedTabTitle": "Đã cung cấp", + "title": "Tài sản" + }, "overview": { "absolutePerformance": "Hiệu suất tuyệt đối", + "healthFactor": { + "tooltip": "Hệ số an toàn (thanh lý tại < 1.0)" + }, "netWorth": { "label": "Giá trị ròng", "tooltip": "Tài sản ròng khả dụng sau khi tính stake trong vault và khoản vay", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "Duyệt" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "Chưa có vị thế vay" + } + }, + "placeholder": { + "title": "Chưa có vị thế cung cấp hoặc vay" + }, + "supplyTab": { + "placeholder": { + "title": "Chưa có vị thế cung cấp" + } + } + }, "staking": { "placeholder": { "title": "Chưa có vị thế staking" @@ -353,11 +415,11 @@ "borrowButton": { "label": "Vay" }, - "interactiveTitle": "Top Cung / Vay trên Venus", + "interactiveTitle": "Điểm nổi bật (cung cấp/vay)", "supplyButton": { "label": "Cung" }, - "textTitle": "Top thị trường trên Venus" + "textTitle": "Điểm nổi bật" }, "transactions": { "placeholder": { @@ -580,6 +642,7 @@ "protectionTitle": "Ưu tiên bảo vệ" }, "safety": { + "audit_one": "Kiểm toán", "audit_other": "Các cuộc kiểm toán", "otherAuditors": "+14 cuộc kiểm toán với
Fairyproof, Hacken và HashEx", "scoreAlt": "Minh họa điểm số", @@ -790,6 +853,14 @@ "content": "Sức mạnh vay của bạn tăng lên khi chỉ cung cấp tài sản trong nhóm {{ eModeGroupName }}", "label": "Cô lập" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "APY vay", + "supplyApyLabel": "APY cung cấp", + "supplyBalanceLabel": "Số dư cung cấp", + "title": "Khuyến nghị" + } + }, "tabs": { "assets": { "title": "Tài sản" diff --git a/apps/evm/src/libs/translations/translations/zh-Hans.json b/apps/evm/src/libs/translations/translations/zh-Hans.json index e3a52a2382..45f3f19fa6 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hans.json +++ b/apps/evm/src/libs/translations/translations/zh-Hans.json @@ -22,6 +22,49 @@ "learnMore": "了解更多", "startNow": "立即开始" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "了解更多", + "description": "一键杠杆提升收益,最高 5 倍", + "rocketIllustration": { + "altText": "火箭插图" + }, + "title": "Boost 已上线!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "蓝色沙漠背景" + }, + "buttonLabel": "了解更多", + "coinsIllustration": { + "altText": "硬币插图" + }, + "description": "领取奖励并关闭你的仓位", + "title": "隔离池即将关闭" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "地平线背景" + }, + "buttonLabel": "立即开始", + "coinIllustration": { + "altText": "硬币插图" + }, + "description": "在 probable.markets 上增强你的信念", + "title": "预测点什么?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "银河背景" + }, + "buttonLabel": "立即开始", + "description": "让你的流动性 Flux,见证收益增长。", + "title": "分享 $1M Venus Flux 激励", + "venusFluxIcon": { + "altText": "Venus Flux 图标" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance 于 2023-04-26 暂停了 BETH 提现。我们建议用户减少在 Venus 上的 BETH 曝险。借款已暂停。可在链上按 1:1 比例将 BETH 转换为 WBETH,点击此处。" @@ -268,18 +311,6 @@ "connectButton": "连接钱包" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "计算您的 Prime 奖励" - }, - "description": "Venus Prime 会奖励在借贷活动中积极参与的用户。", - "illustration": { - "altText": "Prime 插图" - }, - "title": "与 Venus Prime 一起登上更高境界" - } - }, "guide": { "completed": "已完成", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "未完成" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "E-mode插图", + "title": "E-mode: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "已借入", + "suppliedTabTitle": "已存款", + "title": "资产" + }, "overview": { "absolutePerformance": "绝对表现", + "healthFactor": { + "tooltip": "健康系数(清算阈值 < 1.0)" + }, "netWorth": { "label": "净值", "tooltip": "在计入金库质押和借款后可用的净资产", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "浏览" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "暂无借入仓位" + } + }, + "placeholder": { + "title": "暂无存款或借入仓位" + }, + "supplyTab": { + "placeholder": { + "title": "暂无存款仓位" + } + } + }, "staking": { "placeholder": { "title": "暂无质押头寸" @@ -353,11 +415,11 @@ "borrowButton": { "label": "借款" }, - "interactiveTitle": "Venus 顶级 供应 / 借款", + "interactiveTitle": "焦点(存款/借款)", "supplyButton": { - "label": "供应" + "label": "存款" }, - "textTitle": "Venus 顶级市场" + "textTitle": "焦点" }, "transactions": { "placeholder": { @@ -580,6 +642,7 @@ "protectionTitle": "安全保障优先" }, "safety": { + "audit_one": "审计", "audit_other": "审计", "otherAuditors": "+14 次审计,来自
Fairyproof、Hacken 和 HashEx", "scoreAlt": "评分插图", @@ -790,6 +853,14 @@ "content": "仅向 {{ eModeGroupName }} 组内资产存款可提升你的借款能力", "label": "隔离" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "借款 APY", + "supplyApyLabel": "存款 APY", + "supplyBalanceLabel": "存款余额", + "title": "推荐" + } + }, "tabs": { "assets": { "title": "资产" diff --git a/apps/evm/src/libs/translations/translations/zh-Hant.json b/apps/evm/src/libs/translations/translations/zh-Hant.json index e7052996f7..7716c45422 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hant.json +++ b/apps/evm/src/libs/translations/translations/zh-Hant.json @@ -22,6 +22,49 @@ "learnMore": "瞭解更多", "startNow": "立即開始" }, + "adCarousel": { + "boostBanner": { + "buttonLabel": "瞭解更多", + "description": "一鍵槓桿提升收益,最高 5 倍", + "rocketIllustration": { + "altText": "火箭插圖" + }, + "title": "Boost 已上線!" + }, + "isolatedPoolsSunsetBanner": { + "backgroundIllustration": { + "altText": "藍色沙漠背景" + }, + "buttonLabel": "瞭解更多", + "coinsIllustration": { + "altText": "硬幣插圖" + }, + "description": "領取獎勵並關閉你的倉位", + "title": "隔離池正在關閉" + }, + "probableBanner": { + "backgroundIllustration": { + "altText": "地平線背景" + }, + "buttonLabel": "立即開始", + "coinIllustration": { + "altText": "硬幣插圖" + }, + "description": "在 probable.markets 上增強你的信念", + "title": "相信點什麼?" + }, + "venusFluxBanner": { + "backgroundIllustration": { + "altText": "銀河背景" + }, + "buttonLabel": "立即開始", + "description": "讓你的流動性 Flux,見證收益成長。", + "title": "分享 $1M Venus Flux 激勵", + "venusFluxIcon": { + "altText": "Venus Flux 圖標" + } + } + }, "announcements": { "bethUpdate": { "description": "Binance 於 2023-04-26 暫停了 BETH 提現。我們建議用戶減少在 Venus 上的 BETH 曝險。借款已暫停。可在鏈上按 1:1 比例將 BETH 轉換為 WBETH,點擊此處。" @@ -268,18 +311,6 @@ "connectButton": "連接錢包" }, "dashboard": { - "adCarousel": { - "primeBanner": { - "button": { - "label": "計算您的 Prime 獎勵" - }, - "description": "Venus Prime 會獎勵在借貸活動中積極參與的用戶。", - "illustration": { - "altText": "Prime 插圖" - }, - "title": "與 Venus Prime 一起登上更高境界" - } - }, "guide": { "completed": "已完成", "step1": { @@ -296,8 +327,24 @@ }, "uncompleted": "未完成" }, + "marketBreakdown": { + "tables": { + "eModeButton": { + "illustrationAltText": "E-mode插圖", + "title": "E-mode: {{ eModeGroupName }}" + } + } + }, + "markets": { + "borrowTabTitle": "已借入", + "suppliedTabTitle": "已供應", + "title": "資產" + }, "overview": { "absolutePerformance": "絕對表現", + "healthFactor": { + "tooltip": "健康係數(清算閾值 < 1.0)" + }, "netWorth": { "label": "淨值", "tooltip": "計入金庫質押與借款後的可用淨資產", @@ -339,6 +386,21 @@ "placeholder": { "buttonLabel": "瀏覽" }, + "pools": { + "borrowTab": { + "placeholder": { + "title": "尚無借入部位" + } + }, + "placeholder": { + "title": "尚無供應或借入部位" + }, + "supplyTab": { + "placeholder": { + "title": "尚無供應部位" + } + } + }, "staking": { "placeholder": { "title": "尚無質押部位" @@ -353,11 +415,11 @@ "borrowButton": { "label": "借款" }, - "interactiveTitle": "Venus 頂級 供應 / 借款", + "interactiveTitle": "焦點(供應/借款)", "supplyButton": { "label": "供應" }, - "textTitle": "Venus 頂級市場" + "textTitle": "焦點" }, "transactions": { "placeholder": { @@ -580,6 +642,7 @@ "protectionTitle": "安全保障優先" }, "safety": { + "audit_one": "審計", "audit_other": "審計", "otherAuditors": "+14 次審計,來自
Fairyproof、Hacken 與 HashEx", "scoreAlt": "評分插圖", @@ -790,6 +853,14 @@ "content": "僅向 {{ eModeGroupName }} 組內資產供應可提升你的借款能力", "label": "隔離" }, + "recommendations": { + "recommendation": { + "borrowApyLabel": "借款 APY", + "supplyApyLabel": "供應 APY", + "supplyBalanceLabel": "供應餘額", + "title": "推薦" + } + }, "tabs": { "assets": { "title": "資產" diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/desktopIllustration.png b/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/desktopIllustration.png deleted file mode 100644 index 0ff5690caa..0000000000 Binary files a/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/desktopIllustration.png and /dev/null differ diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/index.tsx b/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/index.tsx deleted file mode 100644 index f327e1005d..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/PrimeBanner/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ButtonWrapper } from 'components'; -import { routes } from 'constants/routing'; -import { Link } from 'containers/Link'; -import { useTranslation } from 'libs/translations'; -import desktopIllustrationSrc from './desktopIllustration.png'; - -export const PrimeBanner: React.FC = () => { - const { t, Trans } = useTranslation(); - - return ( -
- {t('dashboard.adCarousel.primeBanner.illustration.altText')} - -
-

- - ), - }} - /> -

- -

- {t('dashboard.adCarousel.primeBanner.description')} -

- - - - {t('dashboard.adCarousel.primeBanner.button.label')} - - -
-
- ); -}; diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap deleted file mode 100644 index f4db5ddffe..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/__snapshots__/index.spec.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`AdCarousel > displays correctly 1`] = `"Achieve greater heights with Venus PrimeVenus Prime rewards dedicated users for their lending and borrowing activities.Calculate your Prime rewards"`; - -exports[`AdCarousel > displays nothing if user previously hid the carousel 1`] = `""`; - -exports[`AdCarousel > hide banners if user hides it 1`] = `""`; diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/index.spec.tsx b/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/index.spec.tsx deleted file mode 100644 index 3576a63289..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/__tests__/index.spec.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { fireEvent, screen } from '@testing-library/react'; - -import { renderComponent } from 'testUtils/render'; -import { AdCarousel } from '..'; -import { store } from '../store'; - -describe('AdCarousel', () => { - it('displays correctly', () => { - const { container } = renderComponent(); - - expect(container.textContent).toMatchSnapshot(); - }); - - it('displays nothing if user previously hid the carousel', () => { - store.getState().hideAdCarousel(); - - const { container } = renderComponent(); - - expect(container.textContent).toMatchSnapshot(); - }); - - it('hide banners if user hides it', () => { - const { container } = renderComponent(); - - fireEvent.click(screen.getByRole('button')); - - expect(store.getState().doNotShowAdCarousel).toBe(true); - expect(container.textContent).toMatchSnapshot(); - }); -}); diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/index.tsx b/apps/evm/src/pages/Dashboard/AdCarousel/index.tsx deleted file mode 100644 index e6c92fc647..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Carousel, CarouselItem, Icon } from 'components'; -import { PrimeBanner } from './PrimeBanner'; -import { store } from './store'; - -const slides: React.ReactNode[] = []; - -export const AdCarousel: React.FC = () => { - const doNotShowAdCarousel = store.use.doNotShowAdCarousel(); - const hideAdCarousel = store.use.hideAdCarousel(); - - const slidesDom = - slides.length > 1 ? ( - - {slides.map((slide, i) => ( - {slide} - ))} - - ) : ( - slides - ); - - if (doNotShowAdCarousel) { - return undefined; - } - - return ( -
- {slidesDom} - - -
- ); -}; diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/store/__tests__/index.spec.ts b/apps/evm/src/pages/Dashboard/AdCarousel/store/__tests__/index.spec.ts deleted file mode 100644 index 1e3b106f52..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/store/__tests__/index.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { store } from '..'; - -describe('store', () => { - describe('doNotShowAdCarousel', () => { - it('sets correct initial props', () => { - expect(store.getState().doNotShowAdCarousel).toMatchInlineSnapshot('false'); - }); - }); - - describe('hideAdCarousel', () => { - it('updates props correctly', () => { - store.getState().hideAdCarousel(); - - expect(store.getState().doNotShowAdCarousel).toMatchInlineSnapshot('true'); - }); - }); -}); diff --git a/apps/evm/src/pages/Dashboard/AdCarousel/store/index.ts b/apps/evm/src/pages/Dashboard/AdCarousel/store/index.ts deleted file mode 100644 index 330f2943ac..0000000000 --- a/apps/evm/src/pages/Dashboard/AdCarousel/store/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import createDeepMerge from '@fastify/deepmerge'; -import { createStoreSelectors } from 'utilities'; -import { create } from 'zustand'; -import { persist } from 'zustand/middleware'; - -export interface State { - doNotShowAdCarousel: boolean; - hideAdCarousel: () => void; -} - -const deepMerge = createDeepMerge({ all: true }); - -const useStore = create()( - persist( - set => ({ - doNotShowAdCarousel: false, - hideAdCarousel: () => - set(state => ({ - ...state, - doNotShowAdCarousel: true, - })), - }), - { - name: 'venus-ad-carousel-store', - merge: (persisted, current) => deepMerge(current, persisted) as never, - }, - ), -); - -export const store = createStoreSelectors(useStore); diff --git a/apps/evm/src/pages/Dashboard/Guide/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Dashboard/Guide/__tests__/__snapshots__/index.spec.tsx.snap index 452f22a4a8..cdb5374c62 100644 --- a/apps/evm/src/pages/Dashboard/Guide/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Dashboard/Guide/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Guide > renders UI if user has some steps left to complete 1`] = `"Supply / MigrateEarn yield by supplying assets or migrating your existing positions to the Venus protocol.CompletedBorrow / LeverageBorrow assets to power up your DeFi strategies, or leverage liquidities in one click.UncompletedStake XVSDeposit XVS into the vault and earn yield powered by the protocol's revenues.UncompletedSupply / MigrateEarn yield by supplying assets or migrating your existing positions to the Venus protocol.CompletedBorrow / LeverageBorrow assets to power up your DeFi strategies, or leverage liquidities in one click.UncompletedStake XVSDeposit XVS into the vault and earn yield powered by the protocol's revenues.Uncompleted"`; +exports[`Guide > renders UI if user has some steps left to complete 1`] = `"Supply / MigrateEarn yield by supplying any asset or moving your existing positions from another protocol to Venus.CompletedBorrow / LeverageBorrow any asset to power your DeFi strategies, and boost it in one simple click.UncompletedStake XVSDeposit XVS to our vault and earn yield powered by Venus' revenue.UncompletedSupply / MigrateEarn yield by supplying any asset or moving your existing positions from another protocol to Venus.CompletedBorrow / LeverageBorrow any asset to power your DeFi strategies, and boost it in one simple click.UncompletedStake XVSDeposit XVS to our vault and earn yield powered by Venus' revenue.Uncompleted"`; exports[`Guide > renders UI if user is not connected 1`] = `""`; diff --git a/apps/evm/src/pages/Dashboard/Guide/index.tsx b/apps/evm/src/pages/Dashboard/Guide/index.tsx index 188412476a..ff8f4892a8 100644 --- a/apps/evm/src/pages/Dashboard/Guide/index.tsx +++ b/apps/evm/src/pages/Dashboard/Guide/index.tsx @@ -2,15 +2,16 @@ import { useGetPool, useGetVaults } from 'clients/api'; import { Carousel, CarouselItem, Icon } from 'components'; import { routes } from 'constants/routing'; import { useChain } from 'hooks/useChain'; -import { useGetMarketsPagePath } from 'hooks/useGetMarketsPagePath'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; +import { useUserChainSettings } from 'hooks/useUserChainSettings'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; -import { useState } from 'react'; +import { store } from 'store'; import { StepCard, type StepCardProps } from './StepCard'; export const Guide: React.FC = () => { const { t } = useTranslation(); - const { marketsPagePath } = useGetMarketsPagePath(); + const { marketsPagePath } = useMarketsPagePath(); const { corePoolComptrollerContractAddress } = useChain(); const { accountAddress } = useAccountAddress(); @@ -32,8 +33,15 @@ export const Guide: React.FC = () => { const isUserSupplying = pool?.userSupplyBalanceCents?.isGreaterThan(0) || false; const isUserBorrowing = pool?.userBorrowBalanceCents?.isGreaterThan(0) || false; - const [areItemsCollapsed, setAreItemsCollapsed] = useState(false); - const toggleCollapseItems = () => setAreItemsCollapsed(current => !current); + const [userChainSettings] = useUserChainSettings(); + const setUserSettings = store.use.setUserSettings(); + + const toggleCollapseItems = () => + setUserSettings({ + settings: { + doNotExpandGuide: !userChainSettings.doNotExpandGuide, + }, + }); const steps: Omit[] = [ { @@ -73,7 +81,7 @@ export const Guide: React.FC = () => { > {steps.map(step => ( - + ))} @@ -82,12 +90,12 @@ export const Guide: React.FC = () => {
{steps.map(step => ( - + ))}
diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/EModeButton/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/EModeButton/index.tsx new file mode 100644 index 0000000000..ed191786f0 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/EModeButton/index.tsx @@ -0,0 +1,60 @@ +import { cn } from '@venusprotocol/ui'; + +import lightningIllustrationSrc from 'assets/img/lightning.svg'; +import { Link } from 'containers/Link'; +import { useFormatTo } from 'hooks/useFormatTo'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; +import { TAB_PARAM_KEY } from 'hooks/useTabs'; +import { useTranslation } from 'libs/translations'; + +export interface EModeButtonProps { + eModeGroupName: string; + className?: string; +} + +export const EModeButton: React.FC = ({ eModeGroupName, className }) => { + const { t, Trans } = useTranslation(); + const { formatTo } = useFormatTo(); + + const { marketsPagePath } = useMarketsPagePath(); + + const to = formatTo({ + to: { + pathname: marketsPagePath, + search: `${TAB_PARAM_KEY}=e-mode`, + }, + }); + + return ( + +
+
+ {t('dashboard.marketBreakdown.tables.eModeButton.illustrationAltText')} +
+ +

+ , + }} + /> +

+
+ + ); +}; diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/Cell/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/Cell/index.tsx new file mode 100644 index 0000000000..560bf4d6c6 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/Cell/index.tsx @@ -0,0 +1,12 @@ +export interface CellProps { + label: string; + children: React.ReactNode; +} + +export const Cell: React.FC = ({ children, label }) => ( +
+

{label}

+ +
{children}
+
+); diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/index.tsx new file mode 100644 index 0000000000..b8b9937218 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/Recommendation/index.tsx @@ -0,0 +1,73 @@ +import type { Address } from 'viem'; + +import { Apy, Card, Delimiter, TokenIconWithSymbol } from 'components'; +import { Link } from 'containers/Link'; +import type { MarketTableProps } from 'containers/MarketTable'; +import { useMarketPageTo } from 'hooks/useMarketPageTo'; +import { useTranslation } from 'libs/translations'; +import type { Asset } from 'types'; +import { formatCentsToReadableValue } from 'utilities'; +import { Cell } from './Cell'; + +export interface RecommendationProps { + type: MarketTableProps['marketType']; + asset: Asset; + poolComptrollerContractAddress: Address; +} + +export const Recommendation: React.FC = ({ + type, + asset, + poolComptrollerContractAddress, +}) => { + const { t } = useTranslation(); + + const readableSupplyBalance = formatCentsToReadableValue({ + value: asset.supplyBalanceCents, + }); + + const { formatMarketPageTo } = useMarketPageTo(); + + const to = formatMarketPageTo({ + poolComptrollerContractAddress, + vTokenAddress: asset.vToken.address, + tabId: type, + }); + + return ( + + +
+

+ {t('markets.recommendations.recommendation.title')} +

+ +
+
+ +
+ + + +
+ + + + + + + + + +

{readableSupplyBalance}

+
+
+
+
+ +
+ ); +}; diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/index.tsx new file mode 100644 index 0000000000..d88f5935f7 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/RecommendationCarousel/index.tsx @@ -0,0 +1,32 @@ +import { Carousel, CarouselItem } from 'components'; +import type { Asset } from 'types'; +import { Recommendation, type RecommendationProps } from './Recommendation'; + +export interface RecommendationCarouselProps + extends Pick { + assets: Asset[]; + className?: string; +} + +export const RecommendationCarousel: React.FC = ({ + type, + className, + poolComptrollerContractAddress, + assets, +}) => ( + + {assets.map(asset => ( + + + + ))} + +); diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/index.tsx new file mode 100644 index 0000000000..bfdb7247f5 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/TabContent/index.tsx @@ -0,0 +1,109 @@ +import { cn } from '@venusprotocol/ui'; + +import { type ColumnKey, MarketTable } from 'containers/MarketTable'; +import { useMarketsPagePath } from 'hooks/useMarketsPagePath'; +import { useTranslation } from 'libs/translations'; +import type { Pool } from 'types'; +import { compareBigNumbers, getCombinedDistributionApys } from 'utilities'; +import { Placeholder } from '../../../Placeholder'; +import { RecommendationCarousel } from './RecommendationCarousel'; + +export interface TabContentProps { + type: 'supply' | 'borrow'; + pool: Pool; +} + +export const TabContent: React.FC = ({ type, pool }) => { + const { t } = useTranslation(); + const { marketsPagePath } = useMarketsPagePath(); + + const assets = pool.assets.filter(asset => + type === 'supply' + ? asset.userSupplyBalanceTokens.isGreaterThan(0) || asset.isCollateralOfUser + : asset.userBorrowBalanceTokens.isGreaterThan(0), + ); + + const columns: ColumnKey[] = + type === 'supply' + ? ['asset', 'supplyApy', 'userSupplyBalance', 'collateral'] + : ['asset', 'borrowApy', 'userBorrowBalance', 'userBorrowLimitSharePercentage']; + + const recommendedAssets = [...pool.assets] + .filter(asset => { + if (type === 'supply') { + return ( + asset.userWalletBalanceTokens.isGreaterThan(0) && + !asset.disabledTokenActions.includes('supply') + ); + } + + return ( + pool.userBorrowLimitCents?.isGreaterThan(0) && + asset.isBorrowableByUser && + !asset.disabledTokenActions.includes('borrow') + ); + }) + .sort((assetA, assetB) => { + if (type === 'supply') { + const assetASupplyApy = assetA.supplyApyPercentage.plus( + getCombinedDistributionApys({ asset: assetA }).totalSupplyApyBoostPercentage, + ); + const assetBSupplyApy = assetB.supplyApyPercentage.plus( + getCombinedDistributionApys({ asset: assetB }).totalSupplyApyBoostPercentage, + ); + + return compareBigNumbers(assetASupplyApy, assetBSupplyApy, 'desc'); + } + + const assetABorrowApy = assetA.borrowApyPercentage.minus( + getCombinedDistributionApys({ asset: assetA }).totalBorrowApyBoostPercentage, + ); + const assetBBorrowApy = assetB.borrowApyPercentage.minus( + getCombinedDistributionApys({ asset: assetB }).totalBorrowApyBoostPercentage, + ); + + return compareBigNumbers(assetABorrowApy, assetBBorrowApy, 'asc'); + }) + .slice(0, 3); + + return ( +
+ {assets.length > 0 ? ( + + ) : ( + + )} + + {recommendedAssets.length > 0 && ( + + )} +
+ ); +}; diff --git a/apps/evm/src/pages/Dashboard/Markets/Tabs/index.tsx b/apps/evm/src/pages/Dashboard/Markets/Tabs/index.tsx new file mode 100644 index 0000000000..be973171cf --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/Tabs/index.tsx @@ -0,0 +1,52 @@ +import { ButtonGroup } from 'components'; +import { type Tab, useTabs } from 'hooks/useTabs'; +import { useTranslation } from 'libs/translations'; +import type { Pool } from 'types'; +import { EModeButton } from './EModeButton'; +import { TabContent } from './TabContent'; + +export interface TabsProps { + pool: Pool; +} + +export const Tabs: React.FC = ({ pool }) => { + const { t } = useTranslation(); + + const tabs: Tab[] = [ + { + id: 'supplied', + title: t('dashboard.markets.suppliedTabTitle'), + content: , + }, + { + id: 'borrowed', + title: t('dashboard.markets.borrowTabTitle'), + content: , + }, + ]; + + const { activeTab, setActiveTab } = useTabs({ + tabs, + }); + + return ( +
+
+
+

{t('dashboard.markets.title')}

+ + {pool?.userEModeGroup && } +
+ + title)} + activeButtonIndex={tabs.findIndex(tab => activeTab.id === tab.id)} + onButtonClick={index => setActiveTab(tabs[index])} + buttonClassName="md:px-12" + /> +
+ + {activeTab.content} +
+ ); +}; diff --git a/apps/evm/src/pages/Dashboard/Markets/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Dashboard/Markets/__tests__/__snapshots__/index.spec.tsx.snap new file mode 100644 index 0000000000..15d5978023 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/__tests__/__snapshots__/index.spec.tsx.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Markets > shows the markets tabs when the user has positions 1`] = `"AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralHoldings-based recommendationUSDTBNB testnetSupply APY4.01%5.76%Borrow APY-5.49%-6.51%Supply balance$10.98THoldings-based recommendationXVSBNB testnetSupply APY0.16%Borrow APY-6.48%Supply balance$2.78M"`; + +exports[`Markets > shows the placeholder when the user has no positions 1`] = `"AssetsSuppliedBorrowedAssetAPY Balancesorted descendingCollateralXVS0.16%90 XVS$115.08USDT4.01%5.76%100 USDT$100USDC5.99%100 USDC$99.99Sort bySupply balanceXVSAPY 0.16%Balance90 XVS$115.08CollateralUSDTAPY 4.01%5.76%Balance100 USDT$100CollateralUSDCAPY 5.99%Balance100 USDC$99.99CollateralHoldings-based recommendationUSDTBNB testnetSupply APY4.01%5.76%Borrow APY-5.49%-6.51%Supply balance$10.98THoldings-based recommendationXVSBNB testnetSupply APY0.16%Borrow APY-6.48%Supply balance$2.78M"`; diff --git a/apps/evm/src/pages/Dashboard/Markets/__tests__/index.spec.tsx b/apps/evm/src/pages/Dashboard/Markets/__tests__/index.spec.tsx new file mode 100644 index 0000000000..502e06deb2 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/__tests__/index.spec.tsx @@ -0,0 +1,54 @@ +import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import BigNumber from 'bignumber.js'; +import type { Mock } from 'vitest'; + +import { poolData } from '__mocks__/models/pools'; +import { useGetPool } from 'clients/api'; +import { en } from 'libs/translations'; +import { renderComponent } from 'testUtils/render'; +import type { Pool } from 'types'; +import { Markets } from '..'; + +vi.mock('hooks/useCollateral'); + +describe('Markets', () => { + it('shows the placeholder when the user has no positions', () => { + const emptyPool: Pool = { + ...poolData[0], + userSupplyBalanceCents: new BigNumber(0), + }; + + (useGetPool as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + pool: emptyPool, + }, + })); + + const { container } = renderComponent(); + + expect(container.textContent).toMatchSnapshot(); + }); + + it('shows the markets tabs when the user has positions', async () => { + (useGetPool as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + pool: poolData[0], + }, + })); + + const user = userEvent.setup(); + const { container } = renderComponent(); + + await waitFor(() => expect(screen.getByText(en.dashboard.markets.title)).toBeInTheDocument()); + + expect(container.textContent).toMatchSnapshot(); + + // Go to Borrow tab + await user.click(screen.getByRole('button', { name: en.dashboard.markets.borrowTabTitle })); + + expect(container.textContent).toContain('40 USDT'); + }); +}); diff --git a/apps/evm/src/pages/Dashboard/Markets/index.tsx b/apps/evm/src/pages/Dashboard/Markets/index.tsx new file mode 100644 index 0000000000..3db5e3bc02 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Markets/index.tsx @@ -0,0 +1,22 @@ +import { useGetPool } from 'clients/api'; +import { Spinner } from 'components'; +import { useChain } from 'hooks/useChain'; +import { useAccountAddress } from 'libs/wallet'; +import { Tabs } from './Tabs'; + +export const Markets: React.FC = () => { + const { accountAddress } = useAccountAddress(); + const { corePoolComptrollerContractAddress } = useChain(); + + const { data: getPoolData } = useGetPool({ + poolComptrollerAddress: corePoolComptrollerContractAddress, + accountAddress, + }); + const pool = getPoolData?.pool; + + if (!pool) { + return ; + } + + return ; +}; diff --git a/apps/evm/src/pages/Dashboard/Overview/GridCellGroup/index.tsx b/apps/evm/src/pages/Dashboard/Overview/GridCellGroup/index.tsx new file mode 100644 index 0000000000..fbc939ab60 --- /dev/null +++ b/apps/evm/src/pages/Dashboard/Overview/GridCellGroup/index.tsx @@ -0,0 +1,13 @@ +import { Cell, type CellProps } from 'components'; + +export interface GridCellGroupProps { + cells: CellProps[]; +} + +export const GridCellGroup: React.FC = ({ cells }) => ( +
+ {cells.map(cell => ( + + ))} +
+); diff --git a/apps/evm/src/pages/Dashboard/Overview/PerformanceChart/index.tsx b/apps/evm/src/pages/Dashboard/Overview/PerformanceChart/index.tsx index 8a47cad00d..50e36d320e 100644 --- a/apps/evm/src/pages/Dashboard/Overview/PerformanceChart/index.tsx +++ b/apps/evm/src/pages/Dashboard/Overview/PerformanceChart/index.tsx @@ -26,7 +26,7 @@ export const PerformanceChart: React.FC = ({ const { t } = useTranslation(); const isSmOrUp = useBreakpointUp('sm'); - const chartInterval = isSmOrUp ? 5 : 4; + const chartInterval = isSmOrUp ? 5 : 3; return (
diff --git a/apps/evm/src/pages/Dashboard/Overview/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Dashboard/Overview/__tests__/__snapshots__/index.spec.tsx.snap index 0c83403eaf..98511ced27 100644 --- a/apps/evm/src/pages/Dashboard/Overview/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Dashboard/Overview/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,9 +1,9 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Overview > displays correctly when user is connected 1`] = `"Net worth$1.23MToday's change+$1.23M99.99%Absolute performance+$1.23M"`; +exports[`Overview > displays correctly when user is connected 1`] = `"Net worth$1.23M15.62HealthyToday's change+$1.23M99.99%Absolute performance+$1.23M"`; -exports[`Overview > displays correctly when user is connected and accordion is expanded 1`] = `"Net worth$1.23MToday's change+$1.23M99.99%Absolute performance+$1.23M30D6M1YSummaryNet APY0.03%Daily earnings$1.08Total supply$1.23MTotal borrow$123.33Total vault stake$233Minted VAI$10"`; +exports[`Overview > displays correctly when user is connected and accordion is expanded 1`] = `"Net worth$1.23M15.62HealthyToday's change+$1.23M99.99%Absolute performance+$1.23M30D6M1YSummaryNet APY0.03%Daily earnings$1.08Total supply$1.23MTotal borrow$123.33Total vault stake$233Minted VAI$10Borrow limit used:6.4%Limit:$1.92K."`; exports[`Overview > displays correctly when user is not connected 1`] = `"Net worth$1.23MToday's change-Absolute performance-"`; -exports[`Overview > hides user balances when doNotShowUserBalances setting is true 1`] = `"Net worth******Today's change******Absolute performance******"`; +exports[`Overview > hides user balances when doNotShowUserBalances setting is true 1`] = `"Net worth******15.62HealthyToday's change******Absolute performance******"`; diff --git a/apps/evm/src/pages/Dashboard/Overview/index.tsx b/apps/evm/src/pages/Dashboard/Overview/index.tsx index a91a5cfe0f..cdbf8ef26a 100644 --- a/apps/evm/src/pages/Dashboard/Overview/index.tsx +++ b/apps/evm/src/pages/Dashboard/Overview/index.tsx @@ -12,12 +12,15 @@ import { } from 'clients/api'; import { AccordionAnimatedContent, + AccountHealthBar, ButtonGroup, - Cell, CellGroup, type CellProps, + Delimiter, + HealthFactorPill, Icon, InfoIcon, + Tooltip, } from 'components'; import { NULL_ADDRESS } from 'constants/address'; import { HidableUserBalance } from 'containers/HidableUserBalance'; @@ -34,6 +37,7 @@ import { formatPercentageToReadableValue, } from 'utilities'; import { DollarValueChange } from './DollarValueChange'; +import { GridCellGroup } from './GridCellGroup'; import { PerformanceChart } from './PerformanceChart'; import { testIds } from './testIds'; import { useExtractData } from './useExtractData'; @@ -285,7 +289,7 @@ export const Overview: React.FC = ({ ...otherProps }) => {
)} -
+

{readableNetWorth}

@@ -296,6 +300,14 @@ export const Overview: React.FC = ({ ...otherProps }) => { className="size-5" /> + + {!!accountAddress && + pool?.userHealthFactor !== undefined && + pool?.userBorrowBalanceCents?.isGreaterThan(0) && ( + + + + )}
@@ -333,33 +345,42 @@ export const Overview: React.FC = ({ ...otherProps }) => { {shouldShowAccountBreakdown && ( - - p.label)} - className="gap-x-1 inline-flex ml-auto" - buttonClassName="h-8 font-normal" - activeButtonIndex={periodOptions.findIndex(p => p.value === selectedPeriod)} - onButtonClick={index => setSelectedPeriod(periodOptions[index].value)} - /> - - - -
-

{t('dashboard.overview.summary.title')}

- -
- {summaryCells.map(cell => ( - - ))} + +
+
+ p.label)} + className="gap-x-1 inline-flex ml-auto" + buttonClassName="h-8 font-normal" + activeButtonIndex={periodOptions.findIndex(p => p.value === selectedPeriod)} + onButtonClick={index => setSelectedPeriod(periodOptions[index].value)} + /> + +
+ +
+

{t('dashboard.overview.summary.title')}

+ + +
+ + {pool?.userBorrowBalanceCents && pool?.userBorrowLimitCents && ( + + )}
+ +
)}
diff --git a/apps/evm/src/pages/Dashboard/Placeholder/index.tsx b/apps/evm/src/pages/Dashboard/Placeholder/index.tsx index 0ce52bf3a3..77c9b641fd 100644 --- a/apps/evm/src/pages/Dashboard/Placeholder/index.tsx +++ b/apps/evm/src/pages/Dashboard/Placeholder/index.tsx @@ -1,30 +1,38 @@ +import { cn } from '@venusprotocol/ui'; + import { ButtonWrapper, Card, Icon, type IconName } from 'components'; +import { ConnectWallet } from 'containers/ConnectWallet'; import { Link } from 'containers/Link'; import { useTranslation } from 'libs/translations'; +import { useAccountAddress } from 'libs/wallet'; export interface PlaceholderProps { iconName: IconName; title: string; to?: string; + className?: string; } -export const Placeholder: React.FC = ({ iconName, title, to }) => { +export const Placeholder: React.FC = ({ iconName, title, to, className }) => { const { t } = useTranslation(); + const { accountAddress } = useAccountAddress(); return ( - -
-
- + +
+
+
-

{title}

+

{title}

- {!!to && ( - + {!!accountAddress && !!to && ( + {t('dashboard.placeholder.buttonLabel')} )} + + {!accountAddress && }
); diff --git a/apps/evm/src/pages/Dashboard/Staking/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/pages/Dashboard/Staking/__tests__/__snapshots__/index.spec.tsx.snap index 41d75720a4..1425479983 100644 --- a/apps/evm/src/pages/Dashboard/Staking/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/pages/Dashboard/Staking/__tests__/__snapshots__/index.spec.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Staking > displays content correctly 1`] = `"VAIVAI Stake APR> 10,000%Total staked415VAI Stake APR> 10,000%Total staked415XVSXVS Stake APR12.92%Total staked400MXVS Stake APR12.92%Total staked400MNo staking positions yetBrowse"`; +exports[`Staking > displays content correctly 1`] = `"VAIVAI Stake APR> 10,000%Total staked415VAI Stake APR> 10,000%Total staked415XVSXVS Stake APR12.92%Total staked400MXVS Stake APR12.92%Total staked400MNo staking positions yetConnect wallet"`; exports[`Staking > displays content correctly when user is connected 1`] = `"You are staking233XVS Stake APR12.92%Daily emission144XVS Stake APR12.92%Daily emission144"`; diff --git a/apps/evm/src/pages/Dashboard/Staking/__tests__/index.spec.tsx b/apps/evm/src/pages/Dashboard/Staking/__tests__/index.spec.tsx index 687bf47629..462b2938eb 100644 --- a/apps/evm/src/pages/Dashboard/Staking/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Dashboard/Staking/__tests__/index.spec.tsx @@ -19,9 +19,7 @@ describe('Staking', () => { isLoading: false, })); - const { container, queryAllByText, debug } = renderComponent(); - - debug(); + const { container, queryAllByText } = renderComponent(); await waitFor(() => expect(queryAllByText(en.vault.totalStaked).length).not.toBe(0)); @@ -29,12 +27,10 @@ describe('Staking', () => { }); it('displays content correctly when user is connected', async () => { - const { container, queryAllByText, debug } = renderComponent(, { + const { container, queryAllByText } = renderComponent(, { accountAddress: fakeAccountAddress, }); - debug(); - await waitFor(() => expect(queryAllByText(en.vault.dailyEmission).length).not.toBe(0)); expect(container.textContent).toMatchSnapshot(); diff --git a/apps/evm/src/pages/Dashboard/TopMarkets/TypeButton/index.tsx b/apps/evm/src/pages/Dashboard/TopMarkets/TypeButton/index.tsx index cdd55434db..6697d00532 100644 --- a/apps/evm/src/pages/Dashboard/TopMarkets/TypeButton/index.tsx +++ b/apps/evm/src/pages/Dashboard/TopMarkets/TypeButton/index.tsx @@ -12,7 +12,7 @@ export const TypeButton: React.FC = ({ }) => (