Mp 3442 sage feedback pre mars v 2 trade page (#520)

* MP-3442: set autolend to true by default

* MP-3442: remember margin setting and toggle it on by default

* MP-3442: add balances to AssetSelector

* MP-3442: close assetOverlay on clickAway

* fix: fixed TradeModule z-index

* MP-3442: set priority of balance and cap

* fix: overlayState

* fix: adjusted to comments
This commit is contained in:
Linkie Link 2023-10-05 09:15:32 +02:00 committed by GitHub
parent 8a71f4664a
commit 17f13b6d7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 109 additions and 35 deletions

View File

@ -11,7 +11,7 @@ NEXT_PUBLIC_RED_BANK=osmo1vxpdcw092n9rngvekve8g324c2yjx56496ck98ag4sdxr4p4zd4q0w
NEXT_PUBLIC_CREDIT_MANAGER=osmo1m83kw2vehyt9urxf79qa9rxk8chgs4464e5h8s37yhnw3pwauuqq7lux8r NEXT_PUBLIC_CREDIT_MANAGER=osmo1m83kw2vehyt9urxf79qa9rxk8chgs4464e5h8s37yhnw3pwauuqq7lux8r
NEXT_PUBLIC_INCENTIVES=osmo1r9w7k774vcxeuvq6ctq0z2j6wkkxpskucgjkqt0uu7u07l03s3js6ukge4 NEXT_PUBLIC_INCENTIVES=osmo1r9w7k774vcxeuvq6ctq0z2j6wkkxpskucgjkqt0uu7u07l03s3js6ukge4
NEXT_PUBLIC_ZAPPER=osmo1q4kkvuy8wc9fs8sfm7zyeh4k25vssd0l68nrph8s7unvq5jdq67swrepj4 NEXT_PUBLIC_ZAPPER=osmo1q4kkvuy8wc9fs8sfm7zyeh4k25vssd0l68nrph8s7unvq5jdq67swrepj4
NEXT_PUBLIC_SWAPPER=osmo1xmhhdxgk9e83n4kmtlluzx38mya8q9r4hku5nys8cr7jg7sgpx5s8zkkg2 NEXT_PUBLIC_SWAPPER=osmo1wee0z8c7tcawyl647eapqs4a88q8jpa7ddy6nn2nrs7t47p2zhxswetwla
NEXT_PUBLIC_PARAMS=osmo1pzszwkyy0x9cu6p2uknwa3wccr79xwmqn9gj66fnjnayr28tzp6qh2n4qg NEXT_PUBLIC_PARAMS=osmo1pzszwkyy0x9cu6p2uknwa3wccr79xwmqn9gj66fnjnayr28tzp6qh2n4qg
NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7 NEXT_PUBLIC_PYTH=osmo13ge29x4e2s63a8ytz2px8gurtyznmue4a69n5275692v3qn3ks8q7cwck7
NEXT_PUBLIC_API=http://localhost:3000/api NEXT_PUBLIC_API=http://localhost:3000/api

View File

@ -111,11 +111,12 @@ export default function Index(props: Props) {
) )
const formattedAmount = formatAmountToPrecision(amount, MAX_AMOUNT_DECIMALS) const formattedAmount = formatAmountToPrecision(amount, MAX_AMOUNT_DECIMALS)
const lowAmount = formattedAmount === 0 ? 0 : Math.max(formattedAmount, MIN_AMOUNT)
return ( return (
<FormattedNumber <FormattedNumber
className={className} className={className}
smallerThanThreshold={formattedAmount < MIN_AMOUNT} smallerThanThreshold={formattedAmount !== 0 && formattedAmount < MIN_AMOUNT}
amount={formattedAmount < MIN_AMOUNT ? MIN_AMOUNT : formattedAmount} amount={lowAmount}
options={{ options={{
maxDecimals: MAX_AMOUNT_DECIMALS, maxDecimals: MAX_AMOUNT_DECIMALS,
minDecimals: 0, minDecimals: 0,

View File

@ -1,16 +1,20 @@
import AssetImage from 'components/Asset/AssetImage' import AssetImage from 'components/Asset/AssetImage'
import DisplayCurrency from 'components/DisplayCurrency' import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import { StarFilled, StarOutlined } from 'components/Icons' import { StarFilled, StarOutlined } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import { FAVORITE_ASSETS_KEY } from 'constants/localStore' import { FAVORITE_ASSETS_KEY } from 'constants/localStore'
import { BN_ONE } from 'constants/math' import { BN_ONE, BN_ZERO, MAX_AMOUNT_DECIMALS, MIN_AMOUNT } from 'constants/math'
import useLocalStorage from 'hooks/useLocalStorage' import useLocalStorage from 'hooks/useLocalStorage'
import { BNCoin } from 'types/classes/BNCoin' import { BNCoin } from 'types/classes/BNCoin'
import { byDenom } from 'utils/array'
import { demagnify, formatAmountToPrecision } from 'utils/formatters'
interface Props { interface Props {
asset: Asset asset: Asset
onSelectAsset: (asset: Asset) => void onSelectAsset: (asset: Asset) => void
depositCap?: DepositCap depositCap?: DepositCap
balances: BNCoin[]
} }
export default function AssetItem(props: Props) { export default function AssetItem(props: Props) {
@ -19,6 +23,9 @@ export default function AssetItem(props: Props) {
FAVORITE_ASSETS_KEY, FAVORITE_ASSETS_KEY,
[], [],
) )
const amount = demagnify(props.balances.find(byDenom(asset.denom))?.amount ?? BN_ZERO, asset)
const formattedAmount = formatAmountToPrecision(amount, MAX_AMOUNT_DECIMALS)
const lowAmount = formattedAmount === 0 ? 0 : Math.max(formattedAmount, MIN_AMOUNT)
function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, MouseEvent>) { function handleToggleFavorite(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
event.stopPropagation() event.stopPropagation()
@ -50,6 +57,30 @@ export default function AssetItem(props: Props) {
<Text size='xs'>{asset.symbol}</Text> <Text size='xs'>{asset.symbol}</Text>
</div> </div>
</div> </div>
{props.balances.length > 0 && (
<div className='flex gap-1'>
<span className='text-xs text-left text-white/80'>Balance: </span>
{amount >= 1 ? (
<FormattedNumber
className='text-xs text-left text-white/80'
amount={amount}
options={{ abbreviated: true, maxDecimals: MAX_AMOUNT_DECIMALS }}
animate
/>
) : (
<FormattedNumber
className='text-xs text-left text-white/80'
smallerThanThreshold={formattedAmount !== 0 && formattedAmount < MIN_AMOUNT}
amount={lowAmount}
options={{
maxDecimals: MAX_AMOUNT_DECIMALS,
minDecimals: 0,
}}
animate
/>
)}
</div>
)}
{props.depositCap && ( {props.depositCap && (
<div className='flex gap-1'> <div className='flex gap-1'>
<span className='text-xs text-left text-white/60'>Cap Left: </span> <span className='text-xs text-left text-white/60'>Cap Left: </span>

View File

@ -3,8 +3,12 @@ import classNames from 'classnames'
import { ChevronDown } from 'components/Icons' import { ChevronDown } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem' import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
import useCurrentAccount from 'hooks/useCurrentAccount'
import useMarketAssets from 'hooks/useMarketAssets' import useMarketAssets from 'hooks/useMarketAssets'
import { useMemo } from 'react'
import { getMergedBalancesForAsset } from 'utils/accounts'
import { byDenom } from 'utils/array' import { byDenom } from 'utils/array'
import { getEnabledMarketAssets } from 'utils/assets'
interface Props { interface Props {
type: 'buy' | 'sell' type: 'buy' | 'sell'
@ -15,12 +19,17 @@ interface Props {
} }
export default function AssetList(props: Props) { export default function AssetList(props: Props) {
const account = useCurrentAccount()
const { data: marketAssets } = useMarketAssets() const { data: marketAssets } = useMarketAssets()
const balances = useMemo(() => {
if (!account) return []
return getMergedBalancesForAsset(account, getEnabledMarketAssets())
}, [account])
return ( return (
<section> <section>
<button <button
className='flex w-full items-center justify-between bg-black/20 p-4' className='flex items-center justify-between w-full p-4 bg-black/20'
onClick={props.toggleOpen} onClick={props.toggleOpen}
> >
<Text>{props.type === 'buy' ? 'Buy asset' : 'Sell asset'}</Text> <Text>{props.type === 'buy' ? 'Buy asset' : 'Sell asset'}</Text>
@ -35,6 +44,7 @@ export default function AssetList(props: Props) {
<ul> <ul>
{props.assets.map((asset) => ( {props.assets.map((asset) => (
<AssetItem <AssetItem
balances={balances}
key={`${props.type}-${asset.symbol}`} key={`${props.type}-${asset.symbol}`}
asset={asset} asset={asset}
onSelectAsset={props.onChangeAsset} onSelectAsset={props.onChangeAsset}

View File

@ -8,8 +8,6 @@ import Text from 'components/Text'
import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList' import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList'
import useFilteredAssets from 'hooks/useFilteredAssets' import useFilteredAssets from 'hooks/useFilteredAssets'
export type OverlayState = 'buy' | 'sell' | 'closed'
interface Props { interface Props {
state: OverlayState state: OverlayState
buyAsset: Asset buyAsset: Asset
@ -21,7 +19,6 @@ interface Props {
export default function AssetOverlay(props: Props) { export default function AssetOverlay(props: Props) {
const { assets, searchString, onChangeSearch } = useFilteredAssets() const { assets, searchString, onChangeSearch } = useFilteredAssets()
const handleClose = useCallback(() => props.onChangeState('closed'), [props]) const handleClose = useCallback(() => props.onChangeState('closed'), [props])
const handleToggle = useCallback( const handleToggle = useCallback(
@ -51,8 +48,8 @@ export default function AssetOverlay(props: Props) {
} }
return ( return (
<Overlay className='w-full' show={props.state !== 'closed'} setShow={handleClose}> <Overlay className='inset-0 w-full' show={props.state !== 'closed'} setShow={handleClose}>
<div className='flex justify-between overflow-hidden p-4'> <div className='flex justify-between p-4 overflow-hidden'>
<Text>Select asset</Text> <Text>Select asset</Text>
<EscButton onClick={handleClose} enableKeyPress /> <EscButton onClick={handleClose} enableKeyPress />
</div> </div>

View File

@ -1,9 +1,10 @@
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect } from 'react'
import { SwapIcon } from 'components/Icons' import { SwapIcon } from 'components/Icons'
import Text from 'components/Text' import Text from 'components/Text'
import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton' import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay' import AssetOverlay from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
import useStore from 'store'
interface Props { interface Props {
buyAsset: Asset buyAsset: Asset
@ -14,7 +15,7 @@ interface Props {
export default function AssetSelector(props: Props) { export default function AssetSelector(props: Props) {
const { buyAsset, sellAsset, onChangeBuyAsset, onChangeSellAsset } = props const { buyAsset, sellAsset, onChangeBuyAsset, onChangeSellAsset } = props
const [overlayState, setOverlayState] = useState<OverlayState>('closed') const assetOverlayState = useStore((s) => s.assetOverlayState)
const handleSwapAssets = useCallback(() => { const handleSwapAssets = useCallback(() => {
onChangeBuyAsset(sellAsset) onChangeBuyAsset(sellAsset)
@ -24,7 +25,7 @@ export default function AssetSelector(props: Props) {
const handleChangeBuyAsset = useCallback( const handleChangeBuyAsset = useCallback(
(asset: Asset) => { (asset: Asset) => {
onChangeBuyAsset(asset) onChangeBuyAsset(asset)
setOverlayState('sell') useStore.setState({ assetOverlayState: 'sell' })
}, },
[onChangeBuyAsset], [onChangeBuyAsset],
) )
@ -32,38 +33,41 @@ export default function AssetSelector(props: Props) {
const handleChangeSellAsset = useCallback( const handleChangeSellAsset = useCallback(
(asset: Asset) => { (asset: Asset) => {
onChangeSellAsset(asset) onChangeSellAsset(asset)
setOverlayState('closed') useStore.setState({ assetOverlayState: 'closed' })
}, },
[onChangeSellAsset], [onChangeSellAsset],
) )
const handleChangeState = useCallback( const handleChangeState = useCallback((state: OverlayState) => {
(state: OverlayState) => { useStore.setState({ assetOverlayState: state })
setOverlayState(state) }, [])
},
[setOverlayState],
)
useEffect(() => { useEffect(() => {
if (overlayState === 'closed') { if (assetOverlayState === 'closed') {
onChangeBuyAsset(buyAsset) onChangeBuyAsset(buyAsset)
onChangeSellAsset(sellAsset) onChangeSellAsset(sellAsset)
} }
}, [onChangeBuyAsset, onChangeSellAsset, overlayState, buyAsset, sellAsset]) }, [onChangeBuyAsset, onChangeSellAsset, assetOverlayState, buyAsset, sellAsset])
return ( return (
<div className='grid-rows-auto relative grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'> <div className='grid-rows-auto grid grid-cols-[1fr_min-content_1fr] gap-y-2 bg-white/5 p-3'>
<Text size='sm'>Buy</Text> <Text size='sm'>Buy</Text>
<Text size='sm' className='col-start-3'> <Text size='sm' className='col-start-3'>
Sell Sell
</Text> </Text>
<AssetButton onClick={() => setOverlayState('buy')} asset={buyAsset} /> <AssetButton
onClick={() => useStore.setState({ assetOverlayState: 'buy' })}
asset={buyAsset}
/>
<button onClick={handleSwapAssets}> <button onClick={handleSwapAssets}>
<SwapIcon className='mx-2 w-4 place-self-center' /> <SwapIcon className='w-4 mx-2 place-self-center' />
</button> </button>
<AssetButton onClick={() => setOverlayState('sell')} asset={sellAsset} /> <AssetButton
onClick={() => useStore.setState({ assetOverlayState: 'sell' })}
asset={sellAsset}
/>
<AssetOverlay <AssetOverlay
state={overlayState} state={assetOverlayState}
onChangeState={handleChangeState} onChangeState={handleChangeState}
buyAsset={buyAsset} buyAsset={buyAsset}
sellAsset={sellAsset} sellAsset={sellAsset}

View File

@ -35,14 +35,15 @@ interface Props {
export default function SwapForm(props: Props) { export default function SwapForm(props: Props) {
const { buyAsset, sellAsset } = props const { buyAsset, sellAsset } = props
const useMargin = useStore((s) => s.useMargin)
const account = useCurrentAccount() const account = useCurrentAccount()
const swap = useStore((s) => s.swap) const swap = useStore((s) => s.swap)
const [slippage] = useLocalStorage(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage) const [slippage] = useLocalStorage(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage)
const { computeMaxSwapAmount } = useHealthComputer(account) const { computeMaxSwapAmount } = useHealthComputer(account)
const { data: borrowAssets } = useMarketBorrowings() const { data: borrowAssets } = useMarketBorrowings()
const { data: marketAssets } = useMarketAssets() const { data: marketAssets } = useMarketAssets()
const isBorrowEnabled = !!marketAssets.find(byDenom(sellAsset.denom))?.borrowEnabled
const [isMarginChecked, setMarginChecked] = useToggle() const [isMarginChecked, setMarginChecked] = useToggle(isBorrowEnabled ? useMargin : false)
const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO) const [buyAssetAmount, setBuyAssetAmount] = useState(BN_ZERO)
const [sellAssetAmount, setSellAssetAmount] = useState(BN_ZERO) const [sellAssetAmount, setSellAssetAmount] = useState(BN_ZERO)
const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO) const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(BN_ZERO)
@ -173,17 +174,33 @@ export default function SwapForm(props: Props) {
[simulateTrade, isAutoLendEnabled], [simulateTrade, isAutoLendEnabled],
) )
const handleMarginToggleChange = useCallback(
(isMargin: boolean) => {
if (isBorrowEnabled) useStore.setState({ useMargin: isMargin })
setMarginChecked(isBorrowEnabled ? isMargin : false)
},
[isBorrowEnabled, setMarginChecked],
)
useEffect(() => { useEffect(() => {
setBuyAssetAmount(BN_ZERO) setBuyAssetAmount(BN_ZERO)
setSellAssetAmount(BN_ZERO) setSellAssetAmount(BN_ZERO)
setMarginChecked(false) setMarginChecked(isBorrowEnabled ? useMargin : false)
simulateTrade( simulateTrade(
BNCoin.fromDenomAndBigNumber(buyAsset.denom, BN_ZERO), BNCoin.fromDenomAndBigNumber(buyAsset.denom, BN_ZERO),
BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO), BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO),
BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO), BNCoin.fromDenomAndBigNumber(sellAsset.denom, BN_ZERO),
isAutoLendEnabled ? 'lend' : 'deposit', isAutoLendEnabled ? 'lend' : 'deposit',
) )
}, [buyAsset.denom, sellAsset.denom, simulateTrade, isAutoLendEnabled, setMarginChecked]) }, [
isBorrowEnabled,
useMargin,
buyAsset.denom,
sellAsset.denom,
isAutoLendEnabled,
simulateTrade,
setMarginChecked,
])
useEffect(() => { useEffect(() => {
const removeDepositAmount = sellAssetAmount.isGreaterThanOrEqualTo(sellSideMarginThreshold) const removeDepositAmount = sellAssetAmount.isGreaterThanOrEqualTo(sellSideMarginThreshold)
@ -229,7 +246,7 @@ export default function SwapForm(props: Props) {
<Divider /> <Divider />
<MarginToggle <MarginToggle
checked={isMarginChecked} checked={isMarginChecked}
onChange={setMarginChecked} onChange={handleMarginToggleChange}
disabled={!borrowAsset?.isMarket} disabled={!borrowAsset?.isMarket}
borrowAssetSymbol={sellAsset.symbol} borrowAssetSymbol={sellAsset.symbol}
/> />

View File

@ -16,7 +16,7 @@ export default function TradeModule(props: Props) {
return ( return (
<div <div
className={classNames( className={classNames(
'relative isolate max-w-full overflow-hidden rounded-base pb-4', 'relative isolate max-w-full overflow-hidden rounded-base pb-4 z-50',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas', 'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
'h-full', 'h-full',
)} )}

View File

@ -3,7 +3,7 @@ import { ORACLE_DENOM } from 'constants/oracle'
export const DEFAULT_SETTINGS: Settings = { export const DEFAULT_SETTINGS: Settings = {
reduceMotion: false, reduceMotion: false,
lendAssets: false, lendAssets: true,
preferredAsset: ASSETS[0].denom, preferredAsset: ASSETS[0].denom,
displayCurrency: ORACLE_DENOM, displayCurrency: ORACLE_DENOM,
slippage: 0.02, slippage: 0.02,

View File

@ -4,12 +4,14 @@ import MigrationBanner from 'components/MigrationBanner'
import AccountDetailsCard from 'components/Trade/AccountDetailsCard' import AccountDetailsCard from 'components/Trade/AccountDetailsCard'
import TradeChart from 'components/Trade/TradeChart' import TradeChart from 'components/Trade/TradeChart'
import TradeModule from 'components/Trade/TradeModule' import TradeModule from 'components/Trade/TradeModule'
import useStore from 'store'
import { getEnabledMarketAssets } from 'utils/assets' import { getEnabledMarketAssets } from 'utils/assets'
export default function TradePage() { export default function TradePage() {
const enabledMarketAssets = getEnabledMarketAssets() const enabledMarketAssets = getEnabledMarketAssets()
const [buyAsset, setBuyAsset] = useState(enabledMarketAssets[0]) const [buyAsset, setBuyAsset] = useState(enabledMarketAssets[0])
const [sellAsset, setSellAsset] = useState(enabledMarketAssets[1]) const [sellAsset, setSellAsset] = useState(enabledMarketAssets[1])
const assetOverlayState = useStore((s) => s.assetOverlayState)
return ( return (
<div className='flex flex-col w-full h-full gap-4'> <div className='flex flex-col w-full h-full gap-4'>
@ -25,6 +27,13 @@ export default function TradePage() {
<div /> <div />
<AccountDetailsCard /> <AccountDetailsCard />
</div> </div>
{assetOverlayState !== 'closed' && (
<div
className='fixed top-0 left-0 z-40 block w-full h-full hover:cursor-pointer'
onClick={() => useStore.setState({ assetOverlayState: 'closed' })}
role='button'
/>
)}
</div> </div>
) )
} }

View File

@ -11,5 +11,6 @@ export default function createCommonSlice(set: SetState<CommonSlice>, get: GetSt
accountDetailsExpanded: false, accountDetailsExpanded: false,
migrationBanner: true, migrationBanner: true,
tutorial: true, tutorial: true,
useMargin: true,
} }
} }

View File

@ -15,5 +15,6 @@ export default function createModalSlice(set: SetState<ModalSlice>, get: GetStat
vaultModal: null, vaultModal: null,
walletAssetsModal: null, walletAssetsModal: null,
withdrawFromVaultsModal: null, withdrawFromVaultsModal: null,
assetOverlayState: 'closed' as OverlayState,
} }
} }

View File

@ -0,0 +1 @@
type OverlayState = 'buy' | 'sell' | 'closed'

View File

@ -10,6 +10,7 @@ interface CommonSlice {
accountDetailsExpanded: boolean accountDetailsExpanded: boolean
migrationBanner: boolean migrationBanner: boolean
tutorial: boolean tutorial: boolean
useMargin: boolean
} }
interface FocusComponent { interface FocusComponent {

View File

@ -11,6 +11,7 @@ interface ModalSlice {
vaultModal: VaultModal | null vaultModal: VaultModal | null
walletAssetsModal: WalletAssetModal | null walletAssetsModal: WalletAssetModal | null
withdrawFromVaultsModal: DepositedVault[] | null withdrawFromVaultsModal: DepositedVault[] | null
assetOverlayState: OverlayState
} }
interface AlertDialogButton { interface AlertDialogButton {