feat: marketplace sort
This commit is contained in:
parent
7e378e219c
commit
62fa90e30e
4
web/app/components/plugins/marketplace/constants.ts
Normal file
4
web/app/components/plugins/marketplace/constants.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const DEFAULT_SORT = {
|
||||
sortBy: 'install_count',
|
||||
sortOrder: 'DESC',
|
||||
}
|
@ -12,8 +12,12 @@ import {
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
|
||||
import type { Plugin } from '../types'
|
||||
import type { PluginsSearchParams } from './types'
|
||||
import type {
|
||||
PluginsSearchParams,
|
||||
PluginsSort,
|
||||
} from './types'
|
||||
import { getMarketplacePlugins } from './utils'
|
||||
import { DEFAULT_SORT } from './constants'
|
||||
|
||||
export type MarketplaceContextValue = {
|
||||
intersected: boolean
|
||||
@ -26,6 +30,8 @@ export type MarketplaceContextValue = {
|
||||
handleActivePluginTypeChange: (type: string) => void
|
||||
plugins?: Plugin[]
|
||||
setPlugins?: (plugins: Plugin[]) => void
|
||||
sort: PluginsSort
|
||||
handleSortChange: (sort: PluginsSort) => void
|
||||
}
|
||||
|
||||
export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
||||
@ -39,6 +45,8 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
||||
handleActivePluginTypeChange: () => {},
|
||||
plugins: undefined,
|
||||
setPlugins: () => {},
|
||||
sort: DEFAULT_SORT,
|
||||
handleSortChange: () => {},
|
||||
})
|
||||
|
||||
type MarketplaceContextProviderProps = {
|
||||
@ -57,6 +65,7 @@ export const MarketplaceContextProvider = ({
|
||||
const [filterPluginTags, setFilterPluginTags] = useState<string[]>([])
|
||||
const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
const [sort, setSort] = useState(DEFAULT_SORT)
|
||||
|
||||
const handleUpdatePlugins = useCallback(async (query: PluginsSearchParams) => {
|
||||
const { marketplacePlugins } = await getMarketplacePlugins(query)
|
||||
@ -82,6 +91,10 @@ export const MarketplaceContextProvider = ({
|
||||
setActivePluginType(type)
|
||||
}, [])
|
||||
|
||||
const handleSortChange = useCallback((sort: PluginsSort) => {
|
||||
setSort(sort)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MarketplaceContext.Provider
|
||||
value={{
|
||||
@ -95,6 +108,8 @@ export const MarketplaceContextProvider = ({
|
||||
handleActivePluginTypeChange,
|
||||
plugins,
|
||||
setPlugins,
|
||||
sort,
|
||||
handleSortChange,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -3,6 +3,7 @@ import type { Plugin } from '../../types'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import List from './index'
|
||||
import SortDropdown from '../sort-dropdown'
|
||||
|
||||
type ListWrapperProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
@ -16,6 +17,11 @@ const ListWrapper = ({
|
||||
|
||||
return (
|
||||
<div className='px-12 py-2 bg-background-default-subtle'>
|
||||
<div className='flex items-center'>
|
||||
<div className='title-xl-semi-bold text-text-primary'>134 results</div>
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
<SortDropdown />
|
||||
</div>
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
|
@ -13,7 +13,7 @@ const SearchBox = () => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'sticky top-3 flex items-center m-auto p-1.5 w-[640px] h-11 border border-components-chat-input-border bg-components-panel-bg-blur rounded-xl shadow-md z-[11]',
|
||||
'sticky top-3 flex items-center mx-auto p-1.5 w-[640px] h-11 border border-components-chat-input-border bg-components-panel-bg-blur rounded-xl shadow-md z-[11]',
|
||||
!intersected && 'w-[508px] transition-[width] duration-300',
|
||||
)}
|
||||
>
|
||||
|
@ -0,0 +1,88 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'install_count',
|
||||
order: 'DESC',
|
||||
text: 'Most Popular',
|
||||
},
|
||||
{
|
||||
value: 'version_updated_at',
|
||||
order: 'DESC',
|
||||
text: 'Recently Updated',
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'DESC',
|
||||
text: 'Newly Released',
|
||||
},
|
||||
{
|
||||
value: 'created_at',
|
||||
order: 'ASC',
|
||||
text: 'First Released',
|
||||
},
|
||||
]
|
||||
|
||||
const SortDropdown = () => {
|
||||
const sort = useMarketplaceContext(v => v.sort)
|
||||
const handleSortChange = useMarketplaceContext(v => v.handleSortChange)
|
||||
const [open, setOpen] = useState(false)
|
||||
const selectedOption = options.find(option => option.value === sort.sortBy && option.order === sort.sortOrder)!
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
placement='bottom-start'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 0,
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className='flex items-center px-2 pr-3 h-8 rounded-lg bg-state-base-hover-alt cursor-pointer'>
|
||||
<span className='mr-1 system-sm-regular'>
|
||||
Sort by
|
||||
</span>
|
||||
<span className='mr-1 system-sm-medium'>
|
||||
{selectedOption.text}
|
||||
</span>
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent>
|
||||
<div className='p-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={`${option.value}-${option.order}`}
|
||||
className='flex items-center justify-between px-3 pr-2 h-8 cursor-pointer system-md-regular'
|
||||
onClick={() => handleSortChange({ sortBy: option.value, sortOrder: option.order })}
|
||||
>
|
||||
{option.text}
|
||||
{
|
||||
sort.sortBy === option.value && sort.sortOrder === option.order && (
|
||||
<RiCheckLine className='ml-2 w-4 h-4 text-text-accent' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default SortDropdown
|
@ -27,3 +27,8 @@ export type PluginsSearchParams = {
|
||||
category?: string
|
||||
tag?: string
|
||||
}
|
||||
|
||||
export type PluginsSort = {
|
||||
sortBy: string
|
||||
sortOrder: string
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
|
||||
let marketplacePlugins = [] as Plugin[]
|
||||
try {
|
||||
const marketplacePluginsData = await globalThis.fetch(
|
||||
`${MARKETPLACE_API_PREFIX}/plugins`,
|
||||
`${MARKETPLACE_API_PREFIX}/plugins/search/basic`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
Loading…
Reference in New Issue
Block a user