+
)}
diff --git a/src/components/Earn/vault/Vaults.tsx b/src/components/Earn/vault/Vaults.tsx
index 496e2be2..5a476a32 100644
--- a/src/components/Earn/vault/Vaults.tsx
+++ b/src/components/Earn/vault/Vaults.tsx
@@ -42,6 +42,14 @@ function Content(props: Props) {
if (!vaultsToDisplay.length) return null
+ if (props.type === 'deposited') {
+ return (
+
+
+
+ )
+ }
+
return
}
diff --git a/src/components/Icons/StarFilled.svg b/src/components/Icons/StarFilled.svg
new file mode 100644
index 00000000..b548bbfc
--- /dev/null
+++ b/src/components/Icons/StarFilled.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/components/Icons/StarOutlined.svg b/src/components/Icons/StarOutlined.svg
new file mode 100644
index 00000000..ba632907
--- /dev/null
+++ b/src/components/Icons/StarOutlined.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Icons/SwapIcon.svg b/src/components/Icons/SwapIcon.svg
new file mode 100644
index 00000000..2d2cce1c
--- /dev/null
+++ b/src/components/Icons/SwapIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts
index 591adf49..dae48f31 100644
--- a/src/components/Icons/index.ts
+++ b/src/components/Icons/index.ts
@@ -33,7 +33,10 @@ export { default as Shield } from 'components/Icons/Shield.svg'
export { default as SortAsc } from 'components/Icons/SortAsc.svg'
export { default as SortDesc } from 'components/Icons/SortDesc.svg'
export { default as SortNone } from 'components/Icons/SortNone.svg'
+export { default as StarFilled } from 'components/Icons/StarFilled.svg'
+export { default as StarOutlined } from 'components/Icons/StarOutlined.svg'
export { default as Subtract } from 'components/Icons/Subtract.svg'
+export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
export { default as Wallet } from 'components/Icons/Wallet.svg'
// @endindex
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx
index 44963ce5..46742eb6 100644
--- a/src/components/Modal.tsx
+++ b/src/components/Modal.tsx
@@ -1,10 +1,9 @@
import classNames from 'classnames'
import { ReactNode, useEffect, useRef } from 'react'
-import Button from 'components/Button'
import Card from 'components/Card'
-import { Cross } from 'components/Icons'
-import Text from 'components/Text'
+
+import EscButton from './Button/EscButton'
interface Props {
header: string | ReactNode
@@ -63,11 +62,7 @@ export default function Modal(props: Props) {
>
{props.header}
- {!props.hideCloseBtn && (
- } iconClassName='h-3 w-3' color='tertiary'>
- ESC
-
- )}
+ {!props.hideCloseBtn && }
{props.children ? props.children : props.content}
diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx
index 5ad619d3..afff4d54 100644
--- a/src/components/SearchBar.tsx
+++ b/src/components/SearchBar.tsx
@@ -1,15 +1,16 @@
import classNames from 'classnames'
-import { ChangeEvent } from 'react'
+import { ChangeEvent, forwardRef } from 'react'
import { Search } from 'components/Icons'
interface Props {
value: string
placeholder: string
+ autofocus?: boolean
onChange: (value: string) => void
}
-export default function SearchBar(props: Props) {
+const SearchBar = (props: Props) => {
function onChange(event: ChangeEvent) {
props.onChange(event.target.value)
}
@@ -28,7 +29,10 @@ export default function SearchBar(props: Props) {
className='h-full w-full bg-transparent text-xs placeholder-white/30 outline-none'
placeholder={props.placeholder}
onChange={(event) => onChange(event)}
+ autoFocus={props.autofocus}
/>
)
}
+
+export default forwardRef(SearchBar)
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetButton.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetButton.tsx
new file mode 100644
index 00000000..22531fab
--- /dev/null
+++ b/src/components/Trade/TradeModule/AssetSelector/AssetButton.tsx
@@ -0,0 +1,22 @@
+import AssetImage from 'components/AssetImage'
+import Button from 'components/Button'
+
+interface Props {
+ asset: Asset
+ onClick: () => void
+}
+
+export default function AssetButton(props: Props) {
+ return (
+ }
+ text={props.asset.symbol}
+ color='tertiary'
+ variant='transparent'
+ className='w-full border border-white/20'
+ size='md'
+ hasSubmenu
+ {...props}
+ />
+ )
+}
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx
new file mode 100644
index 00000000..1f0ea464
--- /dev/null
+++ b/src/components/Trade/TradeModule/AssetSelector/AssetItem.tsx
@@ -0,0 +1,59 @@
+import AssetImage from 'components/AssetImage'
+import DisplayCurrency from 'components/DisplayCurrency'
+import { StarFilled, StarOutlined } from 'components/Icons'
+import Text from 'components/Text'
+import { FAVORITE_ASSETS } from 'constants/localStore'
+import { BNCoin } from 'types/classes/BNCoin'
+import { BN } from 'utils/helpers'
+
+interface Props {
+ asset: Asset
+ onSelectAsset: (asset: Asset) => void
+}
+
+export default function AssetItem(props: Props) {
+ const asset = props.asset
+
+ function handleToggleFavorite(event: React.MouseEvent) {
+ event.stopPropagation()
+ const favoriteAssets: string[] = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
+ if (favoriteAssets) {
+ if (favoriteAssets.includes(asset.denom)) {
+ localStorage.setItem(
+ FAVORITE_ASSETS,
+ JSON.stringify(favoriteAssets.filter((item: string) => item !== asset.denom)),
+ )
+ } else {
+ localStorage.setItem(FAVORITE_ASSETS, JSON.stringify([...favoriteAssets, asset.denom]))
+ }
+ window.dispatchEvent(new Event('storage'))
+ }
+ }
+ return (
+
+
+
+ )
+}
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetList.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetList.tsx
new file mode 100644
index 00000000..e03c6f03
--- /dev/null
+++ b/src/components/Trade/TradeModule/AssetSelector/AssetList.tsx
@@ -0,0 +1,43 @@
+import classNames from 'classnames'
+
+import { ChevronDown } from 'components/Icons'
+import Text from 'components/Text'
+import AssetItem from 'components/Trade/TradeModule/AssetSelector/AssetItem'
+
+interface Props {
+ type: 'buy' | 'sell'
+ assets: Asset[]
+ isOpen: boolean
+ toggleOpen: () => void
+ onChangeAsset: (asset: Asset) => void
+}
+
+export default function AssetList(props: Props) {
+ return (
+
+
+ {props.isOpen &&
+ (props.assets.length === 0 ? (
+
+ No available assets found
+
+ ) : (
+
+ {props.assets.map((asset) => (
+
+ ))}
+
+ ))}
+
+ )
+}
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx
new file mode 100644
index 00000000..3fef650d
--- /dev/null
+++ b/src/components/Trade/TradeModule/AssetSelector/AssetOverlay.tsx
@@ -0,0 +1,86 @@
+import { useCallback, useMemo, useRef } from 'react'
+
+import EscButton from 'components/Button/EscButton'
+import Divider from 'components/Divider'
+import Overlay from 'components/Overlay'
+import SearchBar from 'components/SearchBar'
+import Text from 'components/Text'
+import AssetList from 'components/Trade/TradeModule/AssetSelector/AssetList'
+import useFilteredAssets from 'hooks/useFilteredAssets'
+
+export type OverlayState = 'buy' | 'sell' | 'closed'
+
+interface Props {
+ state: OverlayState
+ buyAsset: Asset
+ sellAsset: Asset
+ onChangeBuyAsset: (asset: Asset) => void
+ onChangeSellAsset: (asset: Asset) => void
+ onChangeState: (state: OverlayState) => void
+}
+
+export default function AssetOverlay(props: Props) {
+ const { assets, searchString, onChangeSearch } = useFilteredAssets()
+
+ const handleClose = useCallback(() => props.onChangeState('closed'), [props])
+
+ const handleToggle = useCallback(
+ () => props.onChangeState(props.state === 'buy' ? 'sell' : 'buy'),
+ [props],
+ )
+
+ const buyAssets = useMemo(
+ () => assets.filter((asset) => asset.denom !== props.sellAsset.denom),
+ [assets, props.sellAsset],
+ )
+
+ const sellAssets = useMemo(
+ () => assets.filter((asset) => asset.denom !== props.buyAsset.denom),
+ [assets, props.buyAsset],
+ )
+
+ function onChangeBuyAsset(asset: Asset) {
+ props.onChangeBuyAsset(asset)
+ props.onChangeState('sell')
+ onChangeSearch('')
+ }
+
+ function onChangeSellAsset(asset: Asset) {
+ props.onChangeSellAsset(asset)
+ onChangeSearch('')
+ }
+
+ return (
+
+
+ Select asset
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/Trade/TradeModule/AssetSelector/AssetSelector.tsx b/src/components/Trade/TradeModule/AssetSelector/AssetSelector.tsx
new file mode 100644
index 00000000..5fb29d81
--- /dev/null
+++ b/src/components/Trade/TradeModule/AssetSelector/AssetSelector.tsx
@@ -0,0 +1,67 @@
+import { useCallback, useMemo, useState } from 'react'
+
+import { SwapIcon } from 'components/Icons'
+import Text from 'components/Text'
+import { ASSETS } from 'constants/assets'
+import AssetButton from 'components/Trade/TradeModule/AssetSelector/AssetButton'
+import AssetOverlay, { OverlayState } from 'components/Trade/TradeModule/AssetSelector/AssetOverlay'
+
+export default function AssetSelector() {
+ const [overlayState, setOverlayState] = useState('closed')
+ const [buyAsset, setBuyAsset] = useState(ASSETS[0])
+ const [sellAsset, setSellAsset] = useState(ASSETS[1])
+
+ function handleSwapAssets() {
+ setBuyAsset(sellAsset)
+ setSellAsset(buyAsset)
+ }
+
+ const handleChangeBuyAsset = useCallback(
+ (asset: Asset) => {
+ setBuyAsset(asset)
+ setOverlayState('sell')
+ },
+ [setBuyAsset],
+ )
+
+ const handleChangeSellAsset = useCallback(
+ (asset: Asset) => {
+ setSellAsset(asset)
+ setOverlayState('closed')
+ },
+ [setSellAsset],
+ )
+ const handleChangeState = useCallback(
+ (state: OverlayState) => {
+ setOverlayState(state)
+ },
+ [setOverlayState],
+ )
+
+ const buyAssets = useMemo(
+ () => ASSETS.filter((asset) => asset.denom !== sellAsset.denom),
+ [sellAsset],
+ )
+
+ return (
+
+
Buy
+
+ Sell
+
+
setOverlayState('buy')} asset={buyAsset} />
+
+ setOverlayState('sell')} asset={sellAsset} />
+
+
+ )
+}
diff --git a/src/components/Trade/TradeModule.tsx b/src/components/Trade/TradeModule/index.tsx
similarity index 59%
rename from src/components/Trade/TradeModule.tsx
rename to src/components/Trade/TradeModule/index.tsx
index b4b69818..d3e85edb 100644
--- a/src/components/Trade/TradeModule.tsx
+++ b/src/components/Trade/TradeModule/index.tsx
@@ -1,9 +1,11 @@
-import { Suspense } from 'react'
import { useParams } from 'react-router-dom'
+import classNames from 'classnames'
-import Card from 'components/Card'
import Loading from 'components/Loading'
import Text from 'components/Text'
+import Divider from 'components/Divider'
+
+import AssetSelector from './AssetSelector/AssetSelector'
function Content() {
const params = useParams()
@@ -24,14 +26,15 @@ function Fallback() {
export default function TradeModule() {
return (
-
- }>
-
-
-
+
+
+
)
}
diff --git a/src/constants/localStore.ts b/src/constants/localStore.ts
index 47bbe975..4705f93a 100644
--- a/src/constants/localStore.ts
+++ b/src/constants/localStore.ts
@@ -1,2 +1,3 @@
export const DISPLAY_CURRENCY_KEY = 'displayCurrency'
export const ENABLE_ANIMATIONS_KEY = 'enableAnimations'
+export const FAVORITE_ASSETS = 'favoriteAssets'
diff --git a/src/hooks/useAssets.ts b/src/hooks/useAssets.ts
new file mode 100644
index 00000000..8ca8dc20
--- /dev/null
+++ b/src/hooks/useAssets.ts
@@ -0,0 +1,33 @@
+import { useCallback, useEffect, useState } from 'react'
+
+import { ASSETS } from 'constants/assets'
+import { FAVORITE_ASSETS } from 'constants/localStore'
+
+export default function useAssets() {
+ const [assets, setAssets] = useState(ASSETS)
+
+ const getFavoriteAssets = useCallback(() => {
+ const favoriteAssets = JSON.parse(localStorage.getItem(FAVORITE_ASSETS) || '[]')
+ const assets = ASSETS.map((asset) => ({
+ ...asset,
+ isFavorite: favoriteAssets.includes(asset.denom),
+ })).sort((a, b) => {
+ if (a.isFavorite && !b.isFavorite) return -1
+ if (!a.isFavorite && b.isFavorite) return 1
+ return 0
+ })
+
+ setAssets(assets)
+ }, [])
+
+ useEffect(() => {
+ getFavoriteAssets()
+ window.addEventListener('storage', getFavoriteAssets)
+
+ return () => {
+ window.removeEventListener('storage', getFavoriteAssets)
+ }
+ }, [getFavoriteAssets])
+
+ return assets
+}
diff --git a/src/hooks/useFilteredAssets.ts b/src/hooks/useFilteredAssets.ts
new file mode 100644
index 00000000..2bf1ec18
--- /dev/null
+++ b/src/hooks/useFilteredAssets.ts
@@ -0,0 +1,29 @@
+import { useCallback, useMemo, useState } from 'react'
+
+import useAssets from 'hooks/useAssets'
+
+export default function useFilteredAssets() {
+ const [searchString, setSearchString] = useState('')
+
+ const allAssets = useAssets()
+
+ const assets = useMemo(
+ () =>
+ allAssets.filter(
+ (asset) =>
+ asset.denom.toLocaleLowerCase().includes(searchString.toLowerCase()) ||
+ asset.symbol.toLocaleLowerCase().includes(searchString.toLowerCase()) ||
+ asset.name.toLocaleLowerCase().includes(searchString.toLowerCase()),
+ ),
+ [searchString, allAssets],
+ )
+
+ const onChangeSearch = useCallback(
+ (string: string) => {
+ setSearchString(string)
+ },
+ [setSearchString],
+ )
+
+ return { assets, searchString, onChangeSearch }
+}
diff --git a/src/hooks/useToggle.tsx b/src/hooks/useToggle.tsx
index ff7197ed..57923d21 100644
--- a/src/hooks/useToggle.tsx
+++ b/src/hooks/useToggle.tsx
@@ -1,14 +1,14 @@
-import { useState } from 'react'
+import { useCallback, useState } from 'react'
export default function useToggle(
defaultValue?: boolean,
): [boolean, (isToggled?: boolean) => void] {
const [toggle, setToggle] = useState(defaultValue ?? false)
- function handleToggle(isToggled?: boolean) {
+ const handleToggle = useCallback((isToggled?: boolean) => {
if (isToggled !== undefined) return setToggle(isToggled)
- return setToggle(!toggle)
- }
+ return setToggle((isToggled) => !isToggled)
+ }, [])
return [toggle, handleToggle]
}
diff --git a/src/pages/TradePage.tsx b/src/pages/TradePage.tsx
index 939e9653..d3ba4a12 100644
--- a/src/pages/TradePage.tsx
+++ b/src/pages/TradePage.tsx
@@ -4,7 +4,7 @@ import TradingView from 'components/Trade/TradingView'
export default function TradePage() {
return (
-
+
diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts
index def93ee0..a20c7229 100644
--- a/src/types/interfaces/asset.d.ts
+++ b/src/types/interfaces/asset.d.ts
@@ -14,6 +14,7 @@ interface Asset {
isMarket: boolean
isDisplayCurrency?: boolean
isStable?: boolean
+ isFavorite?: boolean
}
interface OtherAsset extends Omit
{
diff --git a/tailwind.config.js b/tailwind.config.js
index ccf1ea56..005ad064 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -157,6 +157,10 @@ module.exports = {
},
minHeight: {
3: '12px',
+ 5: '20px',
+ 8: '32px',
+ 10: '40px',
+ 14: '56px',
},
maxWidth: {
content: '1024px',