diff --git a/apps/main/src/dex/components/PagePool/components/ContractComp.tsx b/apps/main/src/dex/components/PagePool/components/ContractComp.tsx index b0847393ce..fef89dcf01 100644 --- a/apps/main/src/dex/components/PagePool/components/ContractComp.tsx +++ b/apps/main/src/dex/components/PagePool/components/ContractComp.tsx @@ -1,92 +1,45 @@ -import { ReactNode } from 'react' +import { ComponentProps, ReactNode } from 'react' import { styled } from 'styled-components' import { StyledIconButton } from '@/dex/components/PagePool/PoolDetails/PoolStats/styles' import useStore from '@/dex/store/useStore' import { ChainId } from '@/dex/types/main.types' -import Box from '@ui/Box' import Icon from '@ui/Icon' import ExternalLink from '@ui/Link/ExternalLink' -import { copyToClipboard, shortenAddress } from '@ui-kit/utils' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' +import { AddressActionInfo } from '@ui-kit/shared/ui/AddressActionInfo' +import { copyToClipboard, ReleaseChannel, shortenAddress } from '@ui-kit/utils' const ContractComp = ({ address, rChainId, - isLargeNumber, label, showBottomBorder, - action, }: { address: string rChainId: ChainId - isLargeNumber?: boolean label: ReactNode showBottomBorder: boolean - action?: ReactNode }) => { const network = useStore((state) => state.networks.networks[rChainId]) - return ( - - - {isLargeNumber ? ( - - - {label} - - {shortenAddress(address)} - - - - copyToClipboard(address)}> - - - - ) : ( - <> - - - - {shortenAddress(address)} - - - copyToClipboard(address)}> - - - - - )} +
+ + + + + {shortenAddress(address)} + + + copyToClipboard(address)}> + + + - - {action} - +
) } -const Wrapper = styled.div<{ haveAction: boolean }>` - ${({ haveAction }) => { - if (haveAction) { - return ` - display: flex; - justify-content: space-between; - margin-bottom: 0.5rem; - width: 100%; - ` - } - }} -` - -const InnerWrapper = styled.div<{ isBorderBottom?: boolean; haveAction: boolean }>` - ${({ haveAction }) => { - if (!haveAction) { - return ` - align-items: center; - display: flex; - flex-wrap: wrap; - justify-content: space-between; - ` - } - }} - +const InnerWrapper = styled.div<{ isBorderBottom?: boolean }>` ${({ isBorderBottom }) => { if (isBorderBottom) { return 'border-bottom: 1px solid var(--border-600);' @@ -94,10 +47,6 @@ const InnerWrapper = styled.div<{ isBorderBottom?: boolean; haveAction: boolean }} ` -const LabelWrapper = styled(Box)` - width: 100%; -` - const Label = styled.span` margin-right: 0.5rem; ` @@ -113,4 +62,17 @@ const StyledExternalLink = styled(ExternalLink)` } ` -export default ContractComp +export default function DetailInfoAddressLookup({ + rChainId, + showBottomBorder, + label, + address, +}: ComponentProps) { + const [releaseChannel] = useReleaseChannel() + const network = useStore((state) => state.networks.networks[rChainId]) + return releaseChannel === ReleaseChannel.Beta ? ( + + ) : ( + + ) +} diff --git a/apps/main/src/dex/components/PagePool/components/DetailInfoSlippageTolerance.tsx b/apps/main/src/dex/components/PagePool/components/DetailInfoSlippageTolerance.tsx index f619c919d7..06e70aae9e 100644 --- a/apps/main/src/dex/components/PagePool/components/DetailInfoSlippageTolerance.tsx +++ b/apps/main/src/dex/components/PagePool/components/DetailInfoSlippageTolerance.tsx @@ -3,9 +3,11 @@ import DetailInfo from '@ui/DetailInfo' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton/IconButton' import { formatNumber } from '@ui/utils' -import { SlippageSettings } from '@ui-kit/features/slippage-settings' import { useUserProfileStore } from '@ui-kit/features/user-profile' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import { decimal, ReleaseChannel } from '@ui-kit/utils' +import { SlippageSettings, SlippageToleranceActionInfo } from '@ui-kit/widgets/SlippageSettings' type Props = { maxSlippage: string @@ -15,11 +17,17 @@ type Props = { const DetailInfoSlippageTolerance = ({ maxSlippage, stateKey, customLabel }: Props) => { const setMaxSlippage = useUserProfileStore((state) => state.setMaxSlippage) + const [releaseChannel] = useReleaseChannel() + const value = decimal(maxSlippage)! + + if (releaseChannel === ReleaseChannel.Beta) { + return + } return ( ( {formatNumber(maxSlippage, { style: 'percent', defaultValue: '-' })} diff --git a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoExchangeRate.tsx b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoExchangeRate.tsx index 4c205fda77..ccdfeaa41f 100644 --- a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoExchangeRate.tsx +++ b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoExchangeRate.tsx @@ -9,14 +9,12 @@ import { t } from '@ui-kit/lib/i18n' const DetailInfoExchangeRate = ({ exchangeRates, loading, - ...rest }: { exchangeRates?: ExchangeRate[] | null loading: boolean - isMultiLine?: boolean }) => ( 1} label={ <> {t`Exchange rate`} {t`(incl. fees):`} diff --git a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRoute.tsx b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRoute.tsx index 103109be43..39452fcd83 100644 --- a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRoute.tsx +++ b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRoute.tsx @@ -1,13 +1,24 @@ +import { ComponentProps } from 'react' import { styled } from 'styled-components' import DetailInfoTradeRouteRoute from '@/dex/components/PageRouterSwap/components/DetailInfoTradeRouteRoute' import type { Route } from '@/dex/components/PageRouterSwap/types' import { type NetworkUrlParams, TokensNameMapper } from '@/dex/types/main.types' +import List from '@mui/material/List' +import ListItem from '@mui/material/ListItem' +import Stack from '@mui/material/Stack' import Box from '@ui/Box' import { RCCircle } from '@ui/images' import Loader from '@ui/Loader' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { ReleaseChannel } from '@ui-kit/utils' +import { RouteTrack } from '@ui-kit/widgets/RouteTrack' -const DetailInfoTradeRoute = ({ +const { Spacing } = SizesAndSpaces + +const OldDetailInfoTradeRoute = ({ params, loading, routes, @@ -33,7 +44,7 @@ const DetailInfoTradeRoute = ({ padding={isMultiRoutes ? '0 0 0 var(--spacing-1)' : ''} > {loading ? ( - + ) : routesLength > 0 ? ( {routesLength > 1 && ( @@ -66,6 +77,43 @@ const DetailInfoTradeRoute = ({ ) } +export const NewDetailInfoTradeRoute = ({ + params, + loading, + routes, + tokensNameMapper, +}: ComponentProps) => ( + + {routes.length > 1 && } + + {routes.map((route) => ( + ({ whiteSpace: 'nowrap', padding: 0, ...t.typography.bodySRegular })} + > + + + ))} + + + ) : ( + '-' + ) + } + {...(routes && routes.length > 1 && { flexWrap: 'wrap', alignItems: 'start' })} + loading={loading && [180, 24]} + /> +) + const Item = styled.li` white-space: nowrap; text-align: left; @@ -103,4 +151,9 @@ const Wrapper = styled(Box)` font-size: var(--font-size-2); ` -export default DetailInfoTradeRoute +export default function DetailInfoTradeRoute(props: ComponentProps) { + const [releaseChannel] = useReleaseChannel() + const DetailInfoTradeRoute = + releaseChannel === ReleaseChannel.Beta ? NewDetailInfoTradeRoute : OldDetailInfoTradeRoute + return +} diff --git a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRouteRoute.tsx b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRouteRoute.tsx index 7ee221fd13..747325b453 100644 --- a/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRouteRoute.tsx +++ b/apps/main/src/dex/components/PageRouterSwap/components/DetailInfoTradeRouteRoute.tsx @@ -87,7 +87,7 @@ const RouteTokenNames = styled.span` const RouteName = styled(Link)` display: inline-block; - max-width: 130px; + max-width: 180px; color: inherit; overflow: hidden; text-overflow: ellipsis; diff --git a/apps/main/src/lend/components/DetailInfoSlippageTolerance.tsx b/apps/main/src/lend/components/DetailInfoSlippageTolerance.tsx index 393bafc77b..2abe867f8c 100644 --- a/apps/main/src/lend/components/DetailInfoSlippageTolerance.tsx +++ b/apps/main/src/lend/components/DetailInfoSlippageTolerance.tsx @@ -3,9 +3,12 @@ import DetailInfo from '@ui/DetailInfo' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton/IconButton' import { formatNumber } from '@ui/utils' -import { SlippageSettings } from '@ui-kit/features/slippage-settings' import { useUserProfileStore } from '@ui-kit/features/user-profile' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import { decimal, ReleaseChannel } from '@ui-kit/utils' +import { SlippageToleranceActionInfo } from '@ui-kit/widgets/SlippageSettings' +import { SlippageSettings } from '@ui-kit/widgets/SlippageSettings' type Props = { maxSlippage: string @@ -13,11 +16,17 @@ type Props = { const DetailInfoSlippageTolerance = ({ maxSlippage }: Props) => { const setMaxSlippage = useUserProfileStore((state) => state.setMaxSlippage) + const [releaseChannel] = useReleaseChannel() + const value = decimal(maxSlippage)! + + if (releaseChannel === ReleaseChannel.Beta) { + return + } return ( ( {formatNumber(maxSlippage, { style: 'percent', defaultValue: '-' })} diff --git a/apps/main/src/lend/components/DetailsMarket/components/DetailInfoAddressLookup.tsx b/apps/main/src/lend/components/DetailsMarket/components/DetailInfoAddressLookup.tsx index 801fa08e11..a1b0fc237d 100644 --- a/apps/main/src/lend/components/DetailsMarket/components/DetailInfoAddressLookup.tsx +++ b/apps/main/src/lend/components/DetailsMarket/components/DetailInfoAddressLookup.tsx @@ -1,21 +1,19 @@ -import { ReactNode } from 'react' import { styled } from 'styled-components' -import type { StatsProps } from '@/lend/components/DetailsMarket/styles' import { StyledStats } from '@/lend/components/DetailsMarket/styles' import networks from '@/lend/networks' import { ChainId } from '@/lend/types/lend.types' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton' import ExternalLink from '@ui/Link/ExternalLink' -import { copyToClipboard, shortenAddress } from '@ui-kit/utils' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' +import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { AddressActionInfo, AddressActionInfoProps } from '@ui-kit/shared/ui/AddressActionInfo' +import { copyToClipboard, ReleaseChannel, shortenAddress } from '@ui-kit/utils' -interface Props extends StatsProps { - chainId: ChainId - title: ReactNode - address: string | undefined -} +type Props = Omit & { chainId: ChainId } -const DetailInfoAddressLookup = ({ chainId, title, address, ...props }: Props) => { +const OldDetailInfoAddressLookup = ({ chainId, title, address, ...props }: Props) => { const isValidAddress = address ? address !== 'NaN' : true return ( @@ -92,4 +90,21 @@ const StyledExternalLink = styled(ExternalLink)<{ isValid: boolean }>` }} ` -export default DetailInfoAddressLookup +const NoGaugeActionInfo = ({ isBorderBottom, title }: Pick) => ( + `1px solid ${t.palette.divider}` } })} + /> +) + +export default function DetailInfoAddressLookup({ chainId, ...props }: Props) { + const [releaseChannel] = useReleaseChannel() + return releaseChannel !== ReleaseChannel.Beta ? ( + + ) : props.address === 'NaN' ? ( + + ) : ( + + ) +} diff --git a/apps/main/src/llamalend/features/borrow/components/BorrowActionInfoAccordion.tsx b/apps/main/src/llamalend/features/borrow/components/BorrowActionInfoAccordion.tsx index 86d6d8eda7..9abf457e8b 100644 --- a/apps/main/src/llamalend/features/borrow/components/BorrowActionInfoAccordion.tsx +++ b/apps/main/src/llamalend/features/borrow/components/BorrowActionInfoAccordion.tsx @@ -1,16 +1,15 @@ -import { formatPercent } from '@/llamalend/format.utils' import type { NetworkDict } from '@/llamalend/llamalend.types' import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' import Box from '@mui/material/Box' import Stack from '@mui/material/Stack' import { useTheme } from '@mui/material/styles' import { formatNumber } from '@ui/utils' -import { SlippageSettings } from '@ui-kit/features/slippage-settings' import { useSwitch } from '@ui-kit/hooks/useSwitch' import { t } from '@ui-kit/lib/i18n' -import { GearIcon } from '@ui-kit/shared/icons/GearIcon' import { Accordion } from '@ui-kit/shared/ui/Accordion' import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { Decimal, formatPercent } from '@ui-kit/utils' +import { SlippageToleranceActionInfo } from '@ui-kit/widgets/SlippageSettings' import { getHealthValueColor } from '../../market-position-details/utils' import { useLoanToValue } from '../hooks/useLoanToValue' import { useMarketRates } from '../queries/borrow-apy.query' @@ -42,7 +41,7 @@ export const BorrowActionInfoAccordion = ({ borrowToken: Token | undefined tooMuchDebt: boolean networks: NetworkDict - onSlippageChange: (newSlippage: string) => void + onSlippageChange: (newSlippage: Decimal) => void }) => { const [isOpen, , , toggle] = useSwitch(false) const { data: health, isLoading: healthLoading, error: healthError } = useBorrowHealth(params, !tooMuchDebt) // visible when !isOpen @@ -119,19 +118,7 @@ export const BorrowActionInfoAccordion = ({ valueTooltip={gas?.createLoanApprove?.tooltip} loading={gasLoading} /> - } - maxSlippage={`${slippage}`} - onSave={onSlippageChange} - /> - } - testId="borrow-slippage" - /> + diff --git a/apps/main/src/llamalend/features/borrow/components/BorrowLeverageActionInfos.tsx b/apps/main/src/llamalend/features/borrow/components/BorrowLeverageActionInfos.tsx index 7c422f7ec9..8f2c1cc6f2 100644 --- a/apps/main/src/llamalend/features/borrow/components/BorrowLeverageActionInfos.tsx +++ b/apps/main/src/llamalend/features/borrow/components/BorrowLeverageActionInfos.tsx @@ -2,6 +2,7 @@ import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' import { formatNumber } from '@ui/utils' import { t } from '@ui-kit/lib/i18n' import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { Decimal } from '@ui-kit/utils' import { useBorrowExpectedCollateral } from '../queries/borrow-expected-collateral.query' import { useMaxBorrowReceive } from '../queries/borrow-max-receive.query' import { useBorrowPriceImpact } from '../queries/borrow-price-impact.query' @@ -19,7 +20,7 @@ export const BorrowLeverageActionInfos = ({ params: BorrowFormQueryParams isOpen: boolean collateralToken: Token | undefined - slippage: number + slippage: Decimal }) => { const { data: expectedCollateral, @@ -41,7 +42,7 @@ export const BorrowLeverageActionInfos = ({ isLoading: priceImpactPercentLoading, error: priceImpactPercentError, } = useBorrowPriceImpact(params, isOpen) - const isHighImpact = priceImpactPercent != null && priceImpactPercent > slippage + const isHighImpact = priceImpactPercent != null && priceImpactPercent > +slippage return ( <> diff --git a/apps/main/src/llamalend/features/borrow/components/BorrowTabContents.tsx b/apps/main/src/llamalend/features/borrow/components/BorrowTabContents.tsx index aa03a1bd24..68fd2c77b9 100644 --- a/apps/main/src/llamalend/features/borrow/components/BorrowTabContents.tsx +++ b/apps/main/src/llamalend/features/borrow/components/BorrowTabContents.tsx @@ -151,7 +151,7 @@ export const BorrowTabContents = ({ borrowToken={borrowToken} tooMuchDebt={tooMuchDebt} networks={networks} - onSlippageChange={(value) => form.setValue('slippage', +value, setValueOptions)} + onSlippageChange={(value) => form.setValue('slippage', value, setValueOptions)} /> diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow-expected-collateral.query.ts b/apps/main/src/llamalend/features/borrow/queries/borrow-expected-collateral.query.ts index e7ae6867e9..3f730bdbc4 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow-expected-collateral.query.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow-expected-collateral.query.ts @@ -57,12 +57,12 @@ export const { useQuery: useBorrowExpectedCollateral, queryKey: borrowExpectedCo const market = getLlamaMarket(poolId) if (market instanceof LendMarketTemplate) { return convertNumbers( - await market.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, slippage), + await market.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), ) } if (market.leverageV2.hasLeverage()) { return convertNumbers( - await market.leverageV2.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, slippage), + await market.leverageV2.createLoanExpectedCollateral(userCollateral, userBorrowed, debt, +slippage), ) } diff --git a/apps/main/src/llamalend/features/borrow/queries/borrow.validation.ts b/apps/main/src/llamalend/features/borrow/queries/borrow.validation.ts index 88a7b62b2c..ffe0afa715 100644 --- a/apps/main/src/llamalend/features/borrow/queries/borrow.validation.ts +++ b/apps/main/src/llamalend/features/borrow/queries/borrow.validation.ts @@ -2,6 +2,7 @@ import { enforce, group, test } from 'vest' import { createValidationSuite, type FieldsOf } from '@ui-kit/lib' import { chainValidationGroup } from '@ui-kit/lib/model/query/chain-validation' import { llamaApiValidationGroup } from '@ui-kit/lib/model/query/curve-api-validation' +import { Decimal } from '@ui-kit/utils' import { BORROW_PRESET_RANGES } from '../constants' import { type BorrowForm, type BorrowFormQueryParams } from '../types' @@ -22,7 +23,7 @@ const validateDebt = (debt: number | undefined | null, required: boolean) => } }) -const validateSlippage = (slippage: number | null | undefined) => +const validateSlippage = (slippage: Decimal | null | undefined) => test('slippage', 'Slippage must be a number between 0 and 100', () => { enforce(slippage).isNumeric().gte(0).lte(100) }) diff --git a/apps/main/src/llamalend/features/borrow/queries/create-loan.mutation.ts b/apps/main/src/llamalend/features/borrow/queries/create-loan.mutation.ts index 6a46e8f67b..11e6cd62bc 100644 --- a/apps/main/src/llamalend/features/borrow/queries/create-loan.mutation.ts +++ b/apps/main/src/llamalend/features/borrow/queries/create-loan.mutation.ts @@ -61,7 +61,7 @@ export const useCreateLoanMutation = ({ chainId, poolId }: CreateLoanOptions) => notify(t`Approved loan creation`, 'success') } - const loanTxHash = (await createLoan(userCollateral, userBorrowed, debt, range, slippage)) as Address + const loanTxHash = (await createLoan(userCollateral, userBorrowed, debt, range, +slippage)) as Address await waitForTransactionReceipt(config, { hash: loanTxHash }) return loanTxHash }, diff --git a/apps/main/src/llamalend/features/borrow/types.ts b/apps/main/src/llamalend/features/borrow/types.ts index aa6f225f85..2bbe928fba 100644 --- a/apps/main/src/llamalend/features/borrow/types.ts +++ b/apps/main/src/llamalend/features/borrow/types.ts @@ -2,6 +2,7 @@ import type { IChainId, INetworkName } from '@curvefi/llamalend-api/lib/interfac import type { FieldsOf } from '@ui-kit/lib' import type { PoolQuery } from '@ui-kit/lib/model' import type { MakeOptional } from '@ui-kit/types/util' +import { Decimal } from '@ui-kit/utils' /** Complete borrow creation form with all fields already filled in (after validation) */ export type CompleteBorrowForm = { @@ -9,7 +10,7 @@ export type CompleteBorrowForm = { userBorrowed: number // currently hidden and always 0 debt: number range: number - slippage: number + slippage: Decimal leverageEnabled: boolean } diff --git a/apps/main/src/llamalend/features/borrow/useBorrowForm.tsx b/apps/main/src/llamalend/features/borrow/useBorrowForm.tsx index bb49481b46..92d0cecbbb 100644 --- a/apps/main/src/llamalend/features/borrow/useBorrowForm.tsx +++ b/apps/main/src/llamalend/features/borrow/useBorrowForm.tsx @@ -9,9 +9,9 @@ import type { IChainId } from '@curvefi/llamalend-api/lib/interfaces' import { notFalsy, recordEntries } from '@curvefi/prices-api/objects.util' import { vestResolver } from '@hookform/resolvers/vest' import type { BaseConfig } from '@ui/utils' -import { SLIPPAGE_PRESETS } from '@ui-kit/features/slippage-settings/ui/slippage.utils' import { useDebouncedValue } from '@ui-kit/hooks/useDebounce' import { formDefaultOptions } from '@ui-kit/lib/model' +import { SLIPPAGE_PRESETS } from '@ui-kit/widgets/SlippageSettings/slippage.utils' import { BORROW_PRESET_RANGES } from './constants' import { useMaxTokenValues } from './hooks/useMaxTokenValues' import { borrowFormValidationSuite } from './queries/borrow.validation' diff --git a/apps/main/src/llamalend/features/market-list/LendingMarketsFilters.tsx b/apps/main/src/llamalend/features/market-list/LendingMarketsFilters.tsx index 1e58736edf..dc5bd22ab4 100644 --- a/apps/main/src/llamalend/features/market-list/LendingMarketsFilters.tsx +++ b/apps/main/src/llamalend/features/market-list/LendingMarketsFilters.tsx @@ -3,11 +3,11 @@ import { useMemo } from 'react' import { LlamaMarket } from '@/llamalend/entities/llama-markets' import Grid from '@mui/material/Grid' import Typography from '@mui/material/Typography' -import { formatNumber } from '@ui/utils' import { t } from '@ui-kit/lib/i18n' import { ChainIcon } from '@ui-kit/shared/icons/ChainIcon' import { TokenLabel } from '@ui-kit/shared/ui/TokenLabel' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { formatPercent, formatUsd } from '@ui-kit/utils' import { LlamaMarketColumnId } from './columns.enum' import { MultiSelectFilter } from './filters/MultiSelectFilter' import { RangeSliderFilter } from './filters/RangeSliderFilter' @@ -27,9 +27,6 @@ const Token = ({ symbol, data, field }: { symbol: string; data: LlamaMarket[]; f return } -const formatUsd = (value: number) => formatNumber(value, { currency: 'USD', decimals: 0 }) -const formatPercent = (value: number) => formatNumber(value, { style: 'percent' }) - /** * Filters for the lending markets table. Includes filters for chain, collateral token, debt token, liquidity, and utilization. */ diff --git a/apps/main/src/llamalend/features/market-list/cells/LtvCell.tsx b/apps/main/src/llamalend/features/market-list/cells/LtvCell.tsx index 4e1f321689..f638a2a2b0 100644 --- a/apps/main/src/llamalend/features/market-list/cells/LtvCell.tsx +++ b/apps/main/src/llamalend/features/market-list/cells/LtvCell.tsx @@ -1,6 +1,5 @@ import { useUserMarketStats } from '@/llamalend/entities/llama-market-stats' import { LlamaMarket } from '@/llamalend/entities/llama-markets' -import { formatPercent } from '@/llamalend/format.utils' import { CurrentLTVTooltipContent } from '@/llamalend/widgets/tooltips/CurrentLTVTooltipContent' import { Box } from '@mui/material' import Skeleton from '@mui/material/Skeleton' @@ -8,6 +7,7 @@ import Typography from '@mui/material/Typography' import type { CellContext } from '@tanstack/react-table' import { t } from '@ui-kit/lib/i18n' import { Tooltip } from '@ui-kit/shared/ui/Tooltip' +import { formatPercent } from '@ui-kit/utils' import { LlamaMarketColumnId } from '../columns.enum' export const LtvCell = ({ row }: CellContext) => { diff --git a/apps/main/src/llamalend/features/market-list/cells/PercentCell.tsx b/apps/main/src/llamalend/features/market-list/cells/PercentCell.tsx index 3a05d882d4..996693f389 100644 --- a/apps/main/src/llamalend/features/market-list/cells/PercentCell.tsx +++ b/apps/main/src/llamalend/features/market-list/cells/PercentCell.tsx @@ -1,6 +1,6 @@ -import { formatPercent } from '@/llamalend/format.utils' import Typography from '@mui/material/Typography' import type { CellContext } from '@tanstack/react-table' +import { formatPercent } from '@ui-kit/utils' export const PercentCell = ({ getValue }: CellContext) => ( diff --git a/apps/main/src/llamalend/features/market-list/cells/RateCell/RateCell.tsx b/apps/main/src/llamalend/features/market-list/cells/RateCell/RateCell.tsx index cc3250ce50..5a4b453b9e 100644 --- a/apps/main/src/llamalend/features/market-list/cells/RateCell/RateCell.tsx +++ b/apps/main/src/llamalend/features/market-list/cells/RateCell/RateCell.tsx @@ -1,11 +1,11 @@ import { LlamaMarket } from '@/llamalend/entities/llama-markets' -import { formatPercent } from '@/llamalend/format.utils' import Box from '@mui/material/Box' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' import { CellContext } from '@tanstack/react-table' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { LlamaMarketType, MarketRateType } from '@ui-kit/types/market' +import { formatPercent } from '@ui-kit/utils' import { LlamaMarketColumnId } from '../../columns.enum' import { BorrowRateTooltip } from './BorrowRateTooltip' import { RewardsIcons } from './RewardsIcons' diff --git a/apps/main/src/llamalend/features/market-list/cells/UtilizationCell.tsx b/apps/main/src/llamalend/features/market-list/cells/UtilizationCell.tsx index 7a8ac5ba94..4ea05bc3b7 100644 --- a/apps/main/src/llamalend/features/market-list/cells/UtilizationCell.tsx +++ b/apps/main/src/llamalend/features/market-list/cells/UtilizationCell.tsx @@ -1,6 +1,5 @@ import { ReactElement } from 'react' import { type LlamaMarket } from '@/llamalend/entities/llama-markets' -import { formatPercent } from '@/llamalend/format.utils' import { TooltipItem, TooltipItems } from '@/llamalend/widgets/tooltips/TooltipComponents' import type { Chain } from '@curvefi/prices-api' import Stack from '@mui/material/Stack' @@ -13,6 +12,7 @@ import { TokenIcon } from '@ui-kit/shared/ui/TokenIcon' import { Tooltip } from '@ui-kit/shared/ui/Tooltip' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import { LlamaMarketType } from '@ui-kit/types/market' +import { formatPercent } from '@ui-kit/utils' const { Spacing } = SizesAndSpaces diff --git a/apps/main/src/llamalend/format.utils.ts b/apps/main/src/llamalend/format.utils.ts deleted file mode 100644 index af9b5760bc..0000000000 --- a/apps/main/src/llamalend/format.utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { formatNumber } from '@ui-kit/utils' - -/** Common percentage formatter across the board for most llamalend percentages */ -export const formatPercent = (value: number | null | undefined) => - formatNumber(value || 0, { - unit: 'percentage', - abbreviate: true, - // Disregard the precision edge case around 0 and 1 and force using 2 decimals - minimumFractionDigits: 2, - maximumSignificantDigits: undefined, - }) diff --git a/apps/main/src/llamalend/widgets/tooltips/MarketBorrowRateTooltipContent.tsx b/apps/main/src/llamalend/widgets/tooltips/MarketBorrowRateTooltipContent.tsx index 752669a3f4..a2a282e4d5 100644 --- a/apps/main/src/llamalend/widgets/tooltips/MarketBorrowRateTooltipContent.tsx +++ b/apps/main/src/llamalend/widgets/tooltips/MarketBorrowRateTooltipContent.tsx @@ -1,14 +1,14 @@ -import { formatPercent } from '@/llamalend/format.utils' import { + TooltipDescription, TooltipItem, TooltipItems, TooltipWrapper, - TooltipDescription, } from '@/llamalend/widgets/tooltips/TooltipComponents' import Stack from '@mui/material/Stack' import type { CampaignPoolRewards } from '@ui-kit/entities/campaigns' import { t } from '@ui-kit/lib/i18n' import { LlamaMarketType } from '@ui-kit/types/market' +import { formatPercent } from '@ui-kit/utils' import { RewardsTooltipItems } from './RewardTooltipItems' export type MarketBorrowRateTooltipContentProps = { diff --git a/apps/main/src/llamalend/widgets/tooltips/MarketSupplyRateTooltipContent.tsx b/apps/main/src/llamalend/widgets/tooltips/MarketSupplyRateTooltipContent.tsx index b7ea01d7be..51f55a407c 100644 --- a/apps/main/src/llamalend/widgets/tooltips/MarketSupplyRateTooltipContent.tsx +++ b/apps/main/src/llamalend/widgets/tooltips/MarketSupplyRateTooltipContent.tsx @@ -1,5 +1,4 @@ import { useMarketExtraIncentives } from '@/llamalend/features/market-list/hooks/useMarketExtraIncentives' -import { formatPercent } from '@/llamalend/format.utils' import { TooltipDescription, TooltipItem, @@ -10,6 +9,7 @@ import Stack from '@mui/material/Stack' import type { CampaignPoolRewards } from '@ui-kit/entities/campaigns' import { t } from '@ui-kit/lib/i18n' import { ExtraIncentive, MarketRateType } from '@ui-kit/types/market' +import { formatPercent } from '@ui-kit/utils' import { RewardsTooltipItems } from './RewardTooltipItems' export type MarketSupplyRateTooltipContentProps = { diff --git a/apps/main/src/llamalend/widgets/tooltips/RewardTooltipItems.tsx b/apps/main/src/llamalend/widgets/tooltips/RewardTooltipItems.tsx index ed8c7d2d32..c2b507d5b0 100644 --- a/apps/main/src/llamalend/widgets/tooltips/RewardTooltipItems.tsx +++ b/apps/main/src/llamalend/widgets/tooltips/RewardTooltipItems.tsx @@ -1,4 +1,3 @@ -import { formatPercent } from '@/llamalend/format.utils' import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward' import { Stack } from '@mui/material' import Link from '@mui/material/Link' @@ -6,6 +5,7 @@ import { CampaignPoolRewards } from '@ui-kit/entities/campaigns' import { t } from '@ui-kit/lib/i18n' import { TransitionFunction } from '@ui-kit/themes/design/0_primitives' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { formatPercent } from '@ui-kit/utils' import type { RewardsAction } from '@external-rewards' import { TooltipItem } from './TooltipComponents' diff --git a/apps/main/src/loan/components/DetailInfoSlippageTolerance.tsx b/apps/main/src/loan/components/DetailInfoSlippageTolerance.tsx index c464c0fd47..34e63ae6fb 100644 --- a/apps/main/src/loan/components/DetailInfoSlippageTolerance.tsx +++ b/apps/main/src/loan/components/DetailInfoSlippageTolerance.tsx @@ -3,9 +3,12 @@ import DetailInfo from '@ui/DetailInfo' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton/IconButton' import { formatNumber } from '@ui/utils' -import { SlippageSettings } from '@ui-kit/features/slippage-settings' import { useUserProfileStore } from '@ui-kit/features/user-profile' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import { decimal, ReleaseChannel } from '@ui-kit/utils' +import { SlippageToleranceActionInfo } from '@ui-kit/widgets/SlippageSettings' +import { SlippageSettings } from '@ui-kit/widgets/SlippageSettings' type Props = { maxSlippage: string @@ -14,11 +17,17 @@ type Props = { const DetailInfoSlippageTolerance = ({ maxSlippage, noLabel }: Props) => { const setMaxSlippage = useUserProfileStore((state) => state.setMaxSlippage) + const [releaseChannel] = useReleaseChannel() + const value = decimal(maxSlippage)! + + if (releaseChannel === ReleaseChannel.Beta) { + return + } return ( ( {formatNumber(maxSlippage, { style: 'percent', defaultValue: '-' })} diff --git a/apps/main/src/loan/components/LoanInfoLlamma/components/DetailInfoAddressLookup.tsx b/apps/main/src/loan/components/LoanInfoLlamma/components/DetailInfoAddressLookup.tsx index aec0e613b5..a2a7ae42cb 100644 --- a/apps/main/src/loan/components/LoanInfoLlamma/components/DetailInfoAddressLookup.tsx +++ b/apps/main/src/loan/components/LoanInfoLlamma/components/DetailInfoAddressLookup.tsx @@ -1,20 +1,17 @@ import { styled } from 'styled-components' -import type { StatsProps } from '@/loan/components/LoanInfoLlamma/styles' import { StyledStats } from '@/loan/components/LoanInfoLlamma/styles' import networks from '@/loan/networks' import { ChainId } from '@/loan/types/loan.types' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton' import ExternalLink from '@ui/Link/ExternalLink' -import { copyToClipboard, shortenAddress } from '@ui-kit/utils' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' +import { AddressActionInfo, AddressActionInfoProps } from '@ui-kit/shared/ui/AddressActionInfo' +import { copyToClipboard, ReleaseChannel, shortenAddress } from '@ui-kit/utils' -interface Props extends StatsProps { - chainId: ChainId - title: string - address: string -} +type Props = Omit & { chainId: ChainId; address: string } -const DetailInfoAddressLookup = ({ chainId, title, address, ...props }: Props) => ( +const OldDetailInfoAddressLookup = ({ chainId, title, address, ...props }: Props) => ( {title} @@ -54,4 +51,11 @@ const StyledExternalLink = styled(ExternalLink)` } ` -export default DetailInfoAddressLookup +export default function DetailInfoAddressLookup({ chainId, ...props }: Props) { + const [releaseChannel] = useReleaseChannel() + return releaseChannel === ReleaseChannel.Beta ? ( + + ) : ( + + ) +} diff --git a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/components/DetailInfoTradeRoutes.tsx b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/components/DetailInfoTradeRoutes.tsx index 8894abadfd..c17663577f 100644 --- a/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/components/DetailInfoTradeRoutes.tsx +++ b/apps/main/src/loan/components/PageLoanCreate/LoanFormCreate/components/DetailInfoTradeRoutes.tsx @@ -1,10 +1,20 @@ import { useMemo } from 'react' import { styled } from 'styled-components' +import Stack from '@mui/material/Stack' +import Typography from '@mui/material/Typography' import Box from '@ui/Box' import { RCCircle } from '@ui/images' import Loader from '@ui/Loader' import { formatNumber } from '@ui/utils' +import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { WithWrapper } from '@ui-kit/shared/ui/WithWrapper' +import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { ReleaseChannel } from '@ui-kit/utils' +import { RouteTrack } from '@ui-kit/widgets/RouteTrack' + +const { Spacing } = SizesAndSpaces const DetailInfoTradeRoutes = ({ input, @@ -23,18 +33,61 @@ const DetailInfoTradeRoutes = ({ outputSymbol: string routes: string }) => { - const parsedRoutes = useMemo(() => { - if (routes) { - const delimiterPattern = /\s*-->\s*|\s*->\s*/ - return routes.split(delimiterPattern) - } else { - return [] - } - }, [routes]) - + const [releaseChannel] = useReleaseChannel() + const parsedRoutes = useMemo(() => routes?.split(/\s*-->\s*|\s*->\s*/) || [], [routes]) const routesLength = parsedRoutes.length - const isMultiRoutes = routesLength > 1 - const detailRowJustifyContent = isMultiRoutes ? 'flex-start' : 'flex-end' + const isMultiRoutes = parsedRoutes.length > 1 + + if (releaseChannel === ReleaseChannel.Beta) { + return ( + + {isMultiRoutes && } + + {parsedRoutes.map((route, i) => { + const from = !i && input && `${formatNumber(input)} ${inputSymbol}` + const to = + i === parsedRoutes.length - 1 && output && `receive ${formatNumber(output)} ${outputSymbol}` + return ( + + + {route} + + {(isMultiRoutes && from) || to} + + ) + })} + + + ) : ( + '-' + ) + } + {...(parsedRoutes && { + alignItems: 'start', + sx: { + flexWrap: 'wrap', + // for some reason the routes are not taking 100% width, and we need to override the ActionInfo styles + '& .ActionInfo-valueGroup': { + flexBasis: '100%', + '& .ActionInfo-value': { flexBasis: '100%', '& > .MuiTypography-root': { width: '100%' } }, + }, + }, + })} + loading={loading && [180, 24]} + /> + ) + } return ( @@ -48,7 +101,7 @@ const DetailInfoTradeRoutes = ({ gridAutoFlow="column" gridColumnGap="1" gridTemplateColumns={routesLength === 1 ? '1fr' : 'auto 1fr'} - flexJustifyContent={detailRowJustifyContent} + flexJustifyContent={isMultiRoutes ? 'flex-start' : 'flex-end'} isMultiRoutes={isMultiRoutes} > {routesLength > 1 && ( diff --git a/packages/curve-ui-kit/src/features/slippage-settings/index.tsx b/packages/curve-ui-kit/src/features/slippage-settings/index.tsx deleted file mode 100644 index 6e61308940..0000000000 --- a/packages/curve-ui-kit/src/features/slippage-settings/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { SlippageSettings } from './ui/SlippageSettings' diff --git a/packages/curve-ui-kit/src/features/slippage-settings/ui/slippage.utils.ts b/packages/curve-ui-kit/src/features/slippage-settings/ui/slippage.utils.ts deleted file mode 100644 index fbdab662a7..0000000000 --- a/packages/curve-ui-kit/src/features/slippage-settings/ui/slippage.utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const SLIPPAGE_PRESETS = { - STABLE: 0.1, - CRYPTO: 0.5, -} -export const MIN_SLIPPAGE = 0.01 -export const MAX_RECOMMENDED_SLIPPAGE = 5 diff --git a/packages/curve-ui-kit/src/shared/icons/CircleIcon.tsx b/packages/curve-ui-kit/src/shared/icons/CircleIcon.tsx new file mode 100644 index 0000000000..5d1228551d --- /dev/null +++ b/packages/curve-ui-kit/src/shared/icons/CircleIcon.tsx @@ -0,0 +1,8 @@ +import { createSvgIcon } from '@mui/material/utils' + +export const CircleIcon = createSvgIcon( + + + , + 'Circle', +) diff --git a/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx b/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx index c508cf1eb4..6f475bdb13 100644 --- a/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx +++ b/packages/curve-ui-kit/src/shared/ui/ActionInfo.tsx @@ -18,7 +18,6 @@ import { Duration } from '@ui-kit/themes/design/0_primitives' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' import type { TypographyVariantKey } from '@ui-kit/themes/typography' import { copyToClipboard } from '@ui-kit/utils' -import { Tooltip } from './Tooltip' import { WithSkeleton } from './WithSkeleton' const { Spacing, IconSize } = SizesAndSpaces @@ -65,6 +64,8 @@ export type ActionInfoProps = { testId?: string /** Additional styles */ sx?: StackProps['sx'] + /** CSS align-items property */ + alignItems?: StackProps['alignItems'] } const labelSize = { @@ -90,17 +91,18 @@ const ActionInfo = ({ labelColor, prevValue, prevValueColor, - value, + value: value, valueColor, valueLeft, valueRight, - valueTooltip = '', + valueTooltip, link, size = 'medium', copyValue, copiedTitle, loading = false, error = false, + alignItems = 'center', testId = 'action-info', sx, }: ActionInfoProps) => { @@ -113,12 +115,12 @@ const ActionInfo = ({ const errorMessage = (typeof error === 'object' && error?.message) || (typeof error === 'string' && error) return ( - + {label} - + {prevValue && ( )} - + {/** Additional stack to add some space between left (icon), value and right (icon) */} - + {valueLeft} - + {loading ? (typeof loading === 'string' ? loading : MOCK_SKELETON) : value} {valueRight} - + {error && ( diff --git a/packages/curve-ui-kit/src/shared/ui/AddressActionInfo.tsx b/packages/curve-ui-kit/src/shared/ui/AddressActionInfo.tsx new file mode 100644 index 0000000000..827b85679e --- /dev/null +++ b/packages/curve-ui-kit/src/shared/ui/AddressActionInfo.tsx @@ -0,0 +1,37 @@ +import { ReactNode } from 'react' +import Button from '@mui/material/Button' +import LinkMui from '@mui/material/Link' +import { BaseConfig } from '@ui/utils' +import { shortenAddress } from '../../utils' +import { ArrowTopRightIcon } from '../icons/ArrowTopRightIcon' +import ActionInfo from './ActionInfo' + +export interface AddressActionInfoProps { + network: BaseConfig | undefined + title: ReactNode + address: string | undefined + isBorderBottom?: boolean +} + +export const AddressActionInfo = ({ network, title, address, isBorderBottom }: AddressActionInfoProps) => ( + } + size="extraSmall" + > + {shortenAddress(address)} + + ) + } + copyValue={address} + {...(isBorderBottom && { sx: { borderBottom: (t) => `1px solid ${t.palette.divider}` } })} + /> +) diff --git a/packages/curve-ui-kit/src/shared/ui/WithTooltip.tsx b/packages/curve-ui-kit/src/shared/ui/WithTooltip.tsx index 8a6a53c4b3..4cdfbadb4d 100644 --- a/packages/curve-ui-kit/src/shared/ui/WithTooltip.tsx +++ b/packages/curve-ui-kit/src/shared/ui/WithTooltip.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { default as Tooltip, TooltipProps } from '@mui/material/Tooltip' import { WithWrapper } from '@ui-kit/shared/ui/WithWrapper' diff --git a/packages/curve-ui-kit/src/shared/ui/WithWrapper.tsx b/packages/curve-ui-kit/src/shared/ui/WithWrapper.tsx index 0dd321aec2..138eaf7b15 100644 --- a/packages/curve-ui-kit/src/shared/ui/WithWrapper.tsx +++ b/packages/curve-ui-kit/src/shared/ui/WithWrapper.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react' +import { ReactNode } from 'react' type WithWrapperProps = Props & { /** Whether wrapper should be applied */ diff --git a/packages/curve-ui-kit/src/utils/number.ts b/packages/curve-ui-kit/src/utils/number.ts index 4f93ebf09c..87049f0fcb 100644 --- a/packages/curve-ui-kit/src/utils/number.ts +++ b/packages/curve-ui-kit/src/utils/number.ts @@ -165,7 +165,7 @@ export type NumberFormatOptions = { abbreviate: boolean /** Optional formatter for value */ formatter?: (value: Amount) => string -} & Omit +} & Omit /** * Decomposes a number into its formatted parts including prefix, main value, suffix, and scale suffix. @@ -255,3 +255,15 @@ export const formatNumber = (value: Amount, options: NumberFormatOptions) => { const decomposed = decomposeNumber(value, options) return [decomposed.prefix, decomposed.mainValue, decomposed.scaleSuffix, decomposed.suffix].filter(Boolean).join('') } + +/** Common percentage formatter across the board for most llamalend percentages */ +export const formatPercent = (value?: Amount | null) => + formatNumber(value || 0, { + unit: 'percentage', + abbreviate: true, + // Disregard the precision edge case around 0 and 1 and force using 2 decimals + minimumFractionDigits: 2, + maximumSignificantDigits: undefined, + }) + +export const formatUsd = (value: Amount) => formatNumber(value, { unit: 'dollar', abbreviate: true }) diff --git a/packages/curve-ui-kit/src/widgets/RouteTrack.tsx b/packages/curve-ui-kit/src/widgets/RouteTrack.tsx new file mode 100644 index 0000000000..d46934214e --- /dev/null +++ b/packages/curve-ui-kit/src/widgets/RouteTrack.tsx @@ -0,0 +1,20 @@ +import Stack from '@mui/material/Stack' +import { CircleIcon } from '../shared/icons/CircleIcon' + +const Circle = () => + +/** + * A vertical track with two circles at the ends and a dotted line in between, used to represent a route. + */ +export const RouteTrack = () => ( + + + + + +) diff --git a/packages/curve-ui-kit/src/features/slippage-settings/SlippageSettings.stories.tsx b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettings.stories.tsx similarity index 95% rename from packages/curve-ui-kit/src/features/slippage-settings/SlippageSettings.stories.tsx rename to packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettings.stories.tsx index ae3f90a80f..6e385fb306 100644 --- a/packages/curve-ui-kit/src/features/slippage-settings/SlippageSettings.stories.tsx +++ b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettings.stories.tsx @@ -3,7 +3,8 @@ import type { Meta, StoryObj } from '@storybook/react-vite' import Icon from '@ui/Icon' import IconButton from '@ui/IconButton/IconButton' import { formatNumber } from '@ui/utils' -import { SlippageSettings } from './' +import { Decimal } from '@ui-kit/utils' +import { SlippageSettings } from './SlippageSettings' const SlippageSettingsComponent = ({ maxSlippage: initialMaxSlippage, @@ -12,7 +13,7 @@ const SlippageSettingsComponent = ({ const [maxSlippage, setMaxSlippage] = useState(initialMaxSlippage) // Mock the setMaxSlippage function - const handleSave = (newSlippage: string) => { + const handleSave = (newSlippage: Decimal) => { setMaxSlippage(newSlippage) console.info('Max slippage updated:', newSlippage) } diff --git a/packages/curve-ui-kit/src/features/slippage-settings/ui/SlippageSettings.tsx b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettings.tsx similarity index 100% rename from packages/curve-ui-kit/src/features/slippage-settings/ui/SlippageSettings.tsx rename to packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettings.tsx diff --git a/packages/curve-ui-kit/src/features/slippage-settings/ui/SlippageSettingsModal.tsx b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettingsModal.tsx similarity index 87% rename from packages/curve-ui-kit/src/features/slippage-settings/ui/SlippageSettingsModal.tsx rename to packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettingsModal.tsx index 33a258ba74..2cf742a8ce 100644 --- a/packages/curve-ui-kit/src/features/slippage-settings/ui/SlippageSettingsModal.tsx +++ b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageSettingsModal.tsx @@ -1,4 +1,5 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' +import { recordValues } from '@curvefi/prices-api/objects.util' import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' import Alert from '@mui/material/Alert' import AlertTitle from '@mui/material/AlertTitle' @@ -17,6 +18,7 @@ import { t } from '@ui-kit/lib/i18n' import { ModalDialog } from '@ui-kit/shared/ui/ModalDialog' import { Tooltip } from '@ui-kit/shared/ui/Tooltip' import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces' +import { decimal, Decimal } from '@ui-kit/utils' import { MAX_RECOMMENDED_SLIPPAGE, MIN_SLIPPAGE, SLIPPAGE_PRESETS } from './slippage.utils' const { Spacing, IconSize } = SizesAndSpaces @@ -24,7 +26,7 @@ const { Spacing, IconSize } = SizesAndSpaces type Error = keyof typeof inputErrorMapper type FormValues = { - selected: string + selected: Decimal | 'custom' customValue: string error?: Error } @@ -63,8 +65,8 @@ function validateCustomValue(value: string): FormValues['error'] { const numValue = Number(value) if (numValue === 0) return undefined // empty value, not really an error - if (numValue > MAX_RECOMMENDED_SLIPPAGE) return 'too-high' - if (numValue < MIN_SLIPPAGE) return 'too-low' + if (numValue > +MAX_RECOMMENDED_SLIPPAGE) return 'too-high' + if (numValue < +MIN_SLIPPAGE) return 'too-low' return undefined } @@ -78,8 +80,8 @@ function validateCustomValue(value: string): FormValues['error'] { * - customValue: The custom slippage value if not one of the preset options * - error: Validation error if the custom value is invalid */ -function initFormValues(maxSlippage: string): FormValues { - const isCustomValue = !Object.values(SLIPPAGE_PRESETS).includes(Number(maxSlippage)) +function initFormValues(maxSlippage: Decimal): FormValues { + const isCustomValue = !recordValues(SLIPPAGE_PRESETS).includes(maxSlippage) return { selected: isCustomValue ? 'custom' : maxSlippage, @@ -92,14 +94,14 @@ export type SlippageSettingsProps = { /** Whether the modal is currently open */ isOpen: boolean /** Current maximum slippage value as a string */ - maxSlippage: string + maxSlippage: Decimal } export type SlippageSettingsCallbacks = { /** Function to close the modal */ onClose: () => void /** Callback function when slippage value is saved */ - onSave: (slippage: string) => void + onSave: (slippage: Decimal) => void } export type Props = SlippageSettingsProps & SlippageSettingsCallbacks @@ -131,11 +133,16 @@ export const SlippageSettingsModal = ({ isOpen, maxSlippage, onSave, onClose }: } }, [error]) + const onButtonClick = useCallback(() => { + const value = decimal(selected === 'custom' ? customValue : selected) + return value != null && onSave(value) + }, [onSave, selected, customValue]) + // To save: require a selected value, and if 'custom', the custom value must be non-empty and pass validation // Allow 'too-high' error as it's discouraged but sometimes necessary for low-liquidity pools const canSave = selected && (selected !== 'custom' || (customValue && (!error || error === 'too-high'))) const footer = ( - ) @@ -202,23 +209,15 @@ export const SlippageSettingsModal = ({ isOpen, maxSlippage, onSave, onClose }: setFormValues({ ...formValues, selected: e.target.value })} + onChange={(e) => setFormValues({ ...formValues, selected: e.target.value as Decimal })} sx={{ flexGrow: 1, justifyContent: { mobile: 'space-between', tablet: 'start' }, gap: Spacing.xs, }} > - } - /> - } - /> + } /> + } /> diff --git a/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageToleranceActionInfo.tsx b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageToleranceActionInfo.tsx new file mode 100644 index 0000000000..15ee793527 --- /dev/null +++ b/packages/curve-ui-kit/src/widgets/SlippageSettings/SlippageToleranceActionInfo.tsx @@ -0,0 +1,27 @@ +import { t } from '@ui-kit/lib/i18n' +import { GearIcon } from '@ui-kit/shared/icons/GearIcon' +import ActionInfo from '@ui-kit/shared/ui/ActionInfo' +import { Decimal, formatPercent as formatPercent } from '@ui-kit/utils' +import { SlippageSettings } from '@ui-kit/widgets/SlippageSettings/SlippageSettings' + +export const SlippageToleranceActionInfo = ({ + maxSlippage, + onSave, +}: { + maxSlippage: Decimal + onSave: (newSlippage: Decimal) => void +}) => ( + } + maxSlippage={`${maxSlippage}`} + onSave={onSave} + /> + } + testId="borrow-slippage" + /> +) diff --git a/packages/curve-ui-kit/src/widgets/SlippageSettings/index.tsx b/packages/curve-ui-kit/src/widgets/SlippageSettings/index.tsx new file mode 100644 index 0000000000..ca88b9d848 --- /dev/null +++ b/packages/curve-ui-kit/src/widgets/SlippageSettings/index.tsx @@ -0,0 +1,2 @@ +export { SlippageSettings } from './SlippageSettings' +export { SlippageToleranceActionInfo } from './SlippageToleranceActionInfo' diff --git a/packages/curve-ui-kit/src/widgets/SlippageSettings/slippage.utils.ts b/packages/curve-ui-kit/src/widgets/SlippageSettings/slippage.utils.ts new file mode 100644 index 0000000000..75dd4c07ba --- /dev/null +++ b/packages/curve-ui-kit/src/widgets/SlippageSettings/slippage.utils.ts @@ -0,0 +1,9 @@ +import { Decimal } from '@ui-kit/utils' + +export const SLIPPAGE_PRESETS = { + STABLE: '0.1' as Decimal, + CRYPTO: '0.5' as Decimal, +} + +export const MIN_SLIPPAGE = `0.01` as const +export const MAX_RECOMMENDED_SLIPPAGE = `5` as const diff --git a/packages/ui/src/DetailInfo/DetailInfo.tsx b/packages/ui/src/DetailInfo/DetailInfo.tsx index 9a0f8555e7..cc9e695b6f 100644 --- a/packages/ui/src/DetailInfo/DetailInfo.tsx +++ b/packages/ui/src/DetailInfo/DetailInfo.tsx @@ -56,7 +56,17 @@ const OldDetailInfo = ({ isBold, isDivider, label, loading, loadingSkeleton, too ) -const NewDetailInfo = ({ isBold, isDivider, label, loading, loadingSkeleton, tooltip, variant, children }: Props) => ( +const NewDetailInfo = ({ + isBold, + isDivider, + label, + loading, + loadingSkeleton, + tooltip, + variant, + isMultiLine, + children, +}: Props) => ( <> {isDivider && } ) diff --git a/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts b/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts index eca518f985..d012ea3ea8 100644 --- a/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts +++ b/tests/cypress/e2e/llamalend/llamalend-markets.cy.ts @@ -128,7 +128,7 @@ describe(`LlamaLend Markets`, () => { it(`should allow filtering by using a slider`, () => { const [columnId, initialFilterText] = oneOf( ['liquidityUsd', 'Liquidity: $0 -'], - ['tvl', 'TVL: $10,000 -'], + ['tvl', 'TVL: $10k -'], ['utilizationPercent', 'Utilization: 0% -'], ) cy.viewport(1200, 800) // use fixed viewport to have consistent slider width