Skip to content

Commit

Permalink
feat: add market page filters and sort (#8006)
Browse files Browse the repository at this point in the history
* feat: add market page filters and sort

* fix: continue

* fix: portals request

* fix: portals request

* feat: review feedbacks

* feat: review feedbacks
  • Loading branch information
NeOMakinG authored Oct 31, 2024
1 parent d8c2efe commit 52f70dd
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 106 deletions.
6 changes: 6 additions & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"common": {
"ascending": "Ascending",
"descending": "Descending",
"free": "Free",
"trade": "Trade",
"trades": "Trades",
Expand Down Expand Up @@ -159,6 +161,9 @@
"import": "Import",
"accountsLoading": "Loading Accounts",
"noAccounts": "No accounts for the selected filters.",
"sortBy": "Sort By",
"orderBy": "Order By",
"filterBy": "Filter By",
"carousel": {
"next": "Next",
"prev": "Previous",
Expand Down Expand Up @@ -235,6 +240,7 @@
"balance": "Balance",
"price": "Price",
"value": "Value",
"apy": "APY",
"priceChange": "Price Change",
"allocation": "Allocation",
"marketCap": "Market Cap",
Expand Down
64 changes: 64 additions & 0 deletions src/components/OrderDropdown/OrderDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ChevronDownIcon } from '@chakra-ui/icons'
import type { ButtonProps } from '@chakra-ui/react'
import {
Button,
Flex,
Menu,
MenuButton,
MenuItemOption,
MenuList,
MenuOptionGroup,
Text,
} from '@chakra-ui/react'
import { useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'

import { OrderDirection } from './types'

type OrderDropdownProps = {
value: OrderDirection
onClick: (arg: OrderDirection) => void
buttonProps?: ButtonProps
}

const width = { base: 'full', md: 'auto' }

const chevronDownIcon = <ChevronDownIcon />

export const OrderDropdown: React.FC<OrderDropdownProps> = ({ onClick, value, buttonProps }) => {
const translate = useTranslate()

const renderOptions = useMemo(() => {
return Object.values(OrderDirection).map(option => (
<MenuItemOption value={option} key={option}>
{translate(`common.${option}`)}
</MenuItemOption>
))
}, [translate])

const onChange = useCallback(
(value: string | string[]) => onClick(value as OrderDirection),
[onClick],
)

const selectedLabel = useMemo(() => {
const selectedOption = Object.values(OrderDirection).find(option => option === value)
return selectedOption ? translate(`common.${selectedOption}`) : ''
}, [translate, value])

return (
<Flex alignItems='center' mx={2}>
<Text me={4}>{translate('common.orderBy')}</Text>
<Menu>
<MenuButton width={width} as={Button} rightIcon={chevronDownIcon} {...buttonProps}>
{selectedLabel}
</MenuButton>
<MenuList zIndex='banner'>
<MenuOptionGroup type='radio' value={value} onChange={onChange}>
{renderOptions}
</MenuOptionGroup>
</MenuList>
</Menu>
</Flex>
)
}
4 changes: 4 additions & 0 deletions src/components/OrderDropdown/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum OrderDirection {
Ascending = 'ascending',
Descending = 'descending',
}
70 changes: 70 additions & 0 deletions src/components/SortDropdown/SortDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ChevronDownIcon } from '@chakra-ui/icons'
import type { ButtonProps } from '@chakra-ui/react'
import {
Button,
Flex,
Menu,
MenuButton,
MenuItemOption,
MenuList,
MenuOptionGroup,
Text,
} from '@chakra-ui/react'
import { useCallback, useMemo } from 'react'
import { useTranslate } from 'react-polyglot'

import type { SortOptionsKeys } from './types'

type SortDropdownProps = {
value: SortOptionsKeys
onClick: (arg: SortOptionsKeys) => void
buttonProps?: ButtonProps
options: SortOptionsKeys[]
}

const width = { base: 'full', md: 'auto' }

const chevronDownIcon = <ChevronDownIcon />

export const SortDropdown: React.FC<SortDropdownProps> = ({
onClick,
value,
buttonProps,
options,
}) => {
const translate = useTranslate()

const renderOptions = useMemo(() => {
return options.map(option => (
<MenuItemOption value={option} key={option}>
{translate(`dashboard.portfolio.${option}`)}
</MenuItemOption>
))
}, [translate, options])

const onChange = useCallback(
(value: string | string[]) => onClick(value as SortOptionsKeys),
[onClick],
)

const selectedLabel = useMemo(() => {
const selectedOption = options.find(option => option === value)
return selectedOption ? translate(`dashboard.portfolio.${selectedOption}`) : ''
}, [translate, value, options])

return (
<Flex alignItems='center' mx={2}>
<Text me={4}>{translate('common.sortBy')}</Text>
<Menu>
<MenuButton width={width} as={Button} rightIcon={chevronDownIcon} {...buttonProps}>
{selectedLabel}
</MenuButton>
<MenuList zIndex='banner'>
<MenuOptionGroup type='radio' value={value} onChange={onChange}>
{renderOptions}
</MenuOptionGroup>
</MenuList>
</Menu>
</Flex>
)
}
6 changes: 6 additions & 0 deletions src/components/SortDropdown/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum SortOptionsKeys {
Apy = 'apy',
Volume = 'volume',
MarketCap = 'marketCap',
PriceChange = 'priceChange',
}
8 changes: 7 additions & 1 deletion src/lib/coingecko/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,13 @@ export const getCoingeckoRecentlyAdded = async (): Promise<CoingeckoAsset[]> =>
}

export const getCoingeckoMarkets = async (
order: 'market_cap_desc' | 'volume_desc',
order:
| 'market_cap_asc'
| 'market_cap_desc'
| 'volume_desc'
| 'volume_asc'
| 'price_change_percentage_24h_desc'
| 'price_change_percentage_24h_asc',
): Promise<CoingeckoAsset[]> => {
const { data } = await axios.get<CoinGeckoMarketCap[]>(
`${coingeckoBaseUrl}/coins/markets?vs_currency=usd&order=${order}`,
Expand Down
22 changes: 19 additions & 3 deletions src/pages/Markets/Category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { Main } from 'components/Layout/Main'
import { SEO } from 'components/Layout/Seo'

import { MarketsRow } from './components/MarketsRow'
import type { MARKETS_CATEGORIES } from './constants'
import type { MarketsCategories } from './constants'
import { sortOptionsByCategory } from './constants'
import { useRows } from './hooks/useRows'
import { MarketsHeader } from './MarketsHeader'

Expand All @@ -15,25 +16,40 @@ const containerPaddingX = { base: 4, xl: 0 }
const ASSETS_LIMIT = 100

export const Category: React.FC = () => {
const params = useParams<{ category: MARKETS_CATEGORIES }>()
const params = useParams<{ category: MarketsCategories }>()
const translate = useTranslate()
const headerComponent = useMemo(() => <MarketsHeader />, [])

const allRows = useRows({ limit: ASSETS_LIMIT })
const row = allRows[params.category]

const shouldShowSortFilter = useMemo(() => {
if (!sortOptionsByCategory[params.category]) return false

return true
}, [params.category])

const body = useMemo(
() => (
<MarketsRow
title={row.title}
subtitle={row.subtitle}
supportedChainIds={row.supportedChainIds}
category={row.category}
showOrderFilter
showSortFilter={shouldShowSortFilter}
>
{row.component}
</MarketsRow>
),
[row.category, row.component, row.subtitle, row.supportedChainIds, row.title],
[
row.category,
row.component,
row.subtitle,
row.supportedChainIds,
row.title,
shouldShowSortFilter,
],
)

return (
Expand Down
16 changes: 10 additions & 6 deletions src/pages/Markets/components/AssetGridWithData.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useInView } from 'react-intersection-observer'
import type { OrderDirection } from 'components/OrderDropdown/types'
import type { SortOptionsKeys } from 'components/SortDropdown/types'

import type { MARKETS_CATEGORIES } from '../constants'
import type { MarketsCategories } from '../constants'
import { CATEGORY_TO_QUERY_HOOK } from '../hooks/useCoingeckoData'
import type { RowProps } from '../hooks/useRows'
import { AssetsGrid } from './AssetsGrid'
Expand All @@ -12,14 +14,16 @@ export const AssetGridWithData = ({
showMarketCap,
category,
orderBy,
sortBy,
}: RowProps & {
limit: number
category: Exclude<
MARKETS_CATEGORIES,
MARKETS_CATEGORIES.ONE_CLICK_DEFI | MARKETS_CATEGORIES.THORCHAIN_DEFI
MarketsCategories,
MarketsCategories.OneClickDefi | MarketsCategories.ThorchainDefi
>
showMarketCap?: boolean
orderBy?: 'market_cap_desc' | 'volume_desc'
orderBy?: OrderDirection
sortBy?: SortOptionsKeys
}) => {
const { ref, inView } = useInView()
const dataQuery = CATEGORY_TO_QUERY_HOOK[category]
Expand All @@ -30,8 +34,8 @@ export const AssetGridWithData = ({
isPending: isMarketCapDataPending,
} = dataQuery({
enabled: inView,
// ts isn't smart enough to narrow this down, and we don't want to pass it for all categories where this does not apply
orderBy: orderBy!,
orderBy,
sortBy,
})

return (
Expand Down
Loading

0 comments on commit 52f70dd

Please sign in to comment.