fix: use query for main app

This commit is contained in:
Stephen Zhou
2026-02-13 11:39:31 +08:00
parent 600c373ef2
commit b9ddd7047c
4 changed files with 80 additions and 16 deletions

View File

@ -7,6 +7,8 @@ import { useCallback, useMemo } from 'react'
import { CATEGORY_ALL, DEFAULT_PLUGIN_SORT, DEFAULT_TEMPLATE_SORT, getValidatedPluginCategory, getValidatedTemplateCategory, PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants'
import { CREATION_TYPE, marketplaceSearchParamsParsers } from './search-params'
export const isMarketplacePlatformAtom = atom<boolean>(false)
const marketplacePluginSortAtom = atom<PluginsSort>(DEFAULT_PLUGIN_SORT)
export function useMarketplacePluginSort() {
return useAtom(marketplacePluginSortAtom)
@ -33,33 +35,59 @@ export function useSearchText() {
return useQueryState('q', marketplaceSearchParamsParsers.q)
}
export function useActivePluginCategory() {
const isAtMarketplace = useAtomValue(isMarketplacePlatformAtom)
const [category, setCategory] = useQueryState('category', marketplaceSearchParamsParsers.category)
const router = useRouter()
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)
const categoryFromPath = segments[1] || CATEGORY_ALL
const validatedCategory = getValidatedPluginCategory(categoryFromPath)
const handleChange = (newCategory: string) => {
router.push(`/plugins/${newCategory}`)
const handleChange = useCallback(
(newCategory: string) => {
router.push(`/plugins/${newCategory}`)
},
[router],
)
if (isAtMarketplace) {
return [validatedCategory, handleChange] as const
}
return [validatedCategory, handleChange] as const
return [getValidatedPluginCategory(category), setCategory] as const
}
export function useActiveTemplateCategory() {
const isAtMarketplace = useAtomValue(isMarketplacePlatformAtom)
const [category, setCategory] = useQueryState('category', marketplaceSearchParamsParsers.category)
const router = useRouter()
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)
const categoryFromPath = segments[1] || CATEGORY_ALL
const validatedCategory = getValidatedTemplateCategory(categoryFromPath)
const handleChange = (newCategory: string) => {
router.push(`/${CREATION_TYPE.templates}/${newCategory}`)
const handleChange = useCallback(
(newCategory: string) => {
router.push(`/${CREATION_TYPE.templates}/${newCategory}`)
},
[router],
)
if (isAtMarketplace) {
return [validatedCategory, handleChange] as const
}
return [validatedCategory, handleChange] as const
return [getValidatedTemplateCategory(category), setCategory] as const
}
export function useFilterPluginTags() {
return useQueryState('tags', marketplaceSearchParamsParsers.tags)
}
export function useSearchTab() {
const isAtMarketplace = useAtomValue(isMarketplacePlatformAtom)
const state = useQueryState('searchTab', marketplaceSearchParamsParsers.searchTab)
const router = useRouter()
// /search/[searchTab]
const { searchTab } = useParams()
@ -71,16 +99,27 @@ export function useSearchTab() {
},
[router],
)
return [searchTab, handleChange] as const
if (isAtMarketplace) {
return [searchTab, handleChange] as const
}
return state
}
export function useCreationType() {
const isAtMarketplace = useAtomValue(isMarketplacePlatformAtom)
const [creationType] = useQueryState('creationType', marketplaceSearchParamsParsers.creationType)
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)
if (segments[0] === CREATION_TYPE.templates || segments[0] === 'template')
return CREATION_TYPE.templates
return CREATION_TYPE.plugins
if (isAtMarketplace) {
if (segments[0] === CREATION_TYPE.templates || segments[0] === 'template')
return CREATION_TYPE.templates
return CREATION_TYPE.plugins
}
return creationType
}
// Search-page-specific filter hooks (separate from list-page category/tags)

View File

@ -0,0 +1,17 @@
'use client'
import { useHydrateAtoms } from 'jotai/utils'
import { isMarketplacePlatformAtom } from './atoms'
export function HydrateClient({
isMarketplacePlatform = false,
children,
}: {
isMarketplacePlatform?: boolean
children?: React.ReactNode
}) {
useHydrateAtoms([
[isMarketplacePlatformAtom, isMarketplacePlatform],
])
return <>{children}</>
}

View File

@ -2,6 +2,7 @@ import type { SearchParams } from 'nuqs'
import type { Awaitable } from './hydration-server'
import { TanstackQueryInitializer } from '@/context/query-client'
import { cn } from '@/utils/classnames'
import { HydrateClient } from './hydration-client'
import { HydrateQueryClient } from './hydration-server'
import MarketplaceContent from './marketplace-content'
import MarketplaceHeader from './marketplace-header'
@ -20,7 +21,7 @@ type MarketplaceProps = {
marketplaceNav?: React.ReactNode
}
const Marketplace = async ({
const Marketplace = ({
showInstallButton = true,
params,
searchParams,
@ -30,10 +31,12 @@ const Marketplace = async ({
return (
<TanstackQueryInitializer>
<HydrateQueryClient searchParams={searchParams} params={params}>
<MarketplaceHeader descriptionClassName={cn('mx-12 mt-1', isMarketplacePlatform && 'top-0 mx-0 mt-0 rounded-none')} marketplaceNav={marketplaceNav} />
<MarketplaceContent
showInstallButton={showInstallButton}
/>
<HydrateClient isMarketplacePlatform={isMarketplacePlatform}>
<MarketplaceHeader descriptionClassName={cn('mx-12 mt-1', isMarketplacePlatform && 'top-0 mx-0 mt-0 rounded-none')} marketplaceNav={marketplaceNav} />
<MarketplaceContent
showInstallButton={showInstallButton}
/>
</HydrateClient>
</HydrateQueryClient>
</TanstackQueryInitializer>
)

View File

@ -1,4 +1,4 @@
import { parseAsArrayOf, parseAsString } from 'nuqs/server'
import { parseAsArrayOf, parseAsString, parseAsStringEnum } from 'nuqs/server'
export const CREATION_TYPE = {
plugins: 'plugins',
@ -17,4 +17,9 @@ export const marketplaceSearchParamsParsers = {
searchLanguages: parseAsArrayOf(parseAsString).withDefault([]).withOptions({ history: 'replace' }),
searchType: parseAsString.withDefault('all').withOptions({ history: 'replace' }),
searchTags: parseAsArrayOf(parseAsString).withDefault([]).withOptions({ history: 'replace' }),
// In marketplace, we use path instead of query
category: parseAsString.withDefault('all').withOptions({ history: 'replace', clearOnDefault: false }),
creationType: parseAsStringEnum<CreationType>([CREATION_TYPE.plugins, CREATION_TYPE.templates]).withDefault(CREATION_TYPE.plugins).withOptions({ history: 'replace' }),
searchTab: parseAsStringEnum<SearchTab>(['all', 'plugins', 'templates', 'creators']).withDefault('').withOptions({ history: 'replace' }),
}