diff --git a/web/app/components/plugins/marketplace/atoms.ts b/web/app/components/plugins/marketplace/atoms.ts index cb51244787..7342140425 100644 --- a/web/app/components/plugins/marketplace/atoms.ts +++ b/web/app/components/plugins/marketplace/atoms.ts @@ -1,10 +1,10 @@ +import type { SearchTab } from './search-params' import type { PluginsSort, SearchParamsFromCollection } from './types' import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' import { useQueryState } from 'nuqs' import { useCallback } from 'react' import { DEFAULT_SORT, getValidatedPluginCategory, getValidatedTemplateCategory, PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants' -import type { SearchTab } from './search-params' -import { marketplaceSearchParamsParsers } from './search-params' +import { CREATION_TYPE, marketplaceSearchParamsParsers } from './search-params' const marketplaceSortAtom = atom(DEFAULT_SORT) export function useMarketplaceSort() { @@ -37,6 +37,10 @@ export function useSearchTab() { return useQueryState('searchTab', marketplaceSearchParamsParsers.searchTab) } +export function useCreationType() { + return useQueryState('creationType', marketplaceSearchParamsParsers.creationType) +} + /** * Not all categories have collections, so we need to * force the search mode for those categories. @@ -44,14 +48,17 @@ export function useSearchTab() { export const searchModeAtom = atom(null) export function useMarketplaceSearchMode() { - // const [searchText] = useSearchText() + const [creationType] = useCreationType() + const [searchText] = useSearchText() const [searchTab] = useSearchTab() - // const [filterPluginTags] = useFilterPluginTags() + const [filterPluginTags] = useFilterPluginTags() const [activePluginCategory] = useActivePluginCategory() + const isPluginsView = creationType === CREATION_TYPE.plugins const searchMode = useAtomValue(searchModeAtom) - const isSearchMode = searchTab - || (searchMode ?? (!PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginCategory))) + const isSearchMode = searchTab || searchText + || (isPluginsView && filterPluginTags.length > 0) + || (searchMode ?? (isPluginsView && !PLUGIN_CATEGORY_WITH_COLLECTIONS.has(activePluginCategory))) return isSearchMode } diff --git a/web/app/components/plugins/marketplace/description/index.tsx b/web/app/components/plugins/marketplace/description/index.tsx index ddfaf2e629..a64c215946 100644 --- a/web/app/components/plugins/marketplace/description/index.tsx +++ b/web/app/components/plugins/marketplace/description/index.tsx @@ -7,8 +7,9 @@ import { useEffect, useLayoutEffect, useRef } from 'react' import marketPlaceBg from '@/public/marketplace/hero-bg.jpg' import marketplaceGradientNoise from '@/public/marketplace/hero-gradient-noise.svg' import { cn } from '@/utils/classnames' +import { useCreationType } from '../atoms' import { PluginCategorySwitch, TemplateCategorySwitch } from '../category-switch/index' -import { useMarketplaceData } from '../state' +import { CREATION_TYPE } from '../search-params' type DescriptionProps = { className?: string @@ -29,8 +30,8 @@ export const Description = ({ marketplaceNav, }: DescriptionProps) => { const { t } = useTranslation('plugin') - const { creationType } = useMarketplaceData() - const isTemplatesView = creationType === 'templates' + const [creationType] = useCreationType() + const isTemplatesView = creationType === CREATION_TYPE.templates const heroTitleKey = isTemplatesView ? 'marketplace.templatesHeroTitle' : 'marketplace.pluginsHeroTitle' const heroSubtitleKey = isTemplatesView ? 'marketplace.templatesHeroSubtitle' : 'marketplace.pluginsHeroSubtitle' const rafRef = useRef(null) diff --git a/web/app/components/plugins/marketplace/hydration-server.tsx b/web/app/components/plugins/marketplace/hydration-server.tsx index 379ac7dd44..881c825f4a 100644 --- a/web/app/components/plugins/marketplace/hydration-server.tsx +++ b/web/app/components/plugins/marketplace/hydration-server.tsx @@ -4,7 +4,7 @@ import { createLoader } from 'nuqs/server' import { getQueryClientServer } from '@/context/query-client-server' import { marketplaceQuery } from '@/service/client' import { getValidatedPluginCategory, PLUGIN_CATEGORY_WITH_COLLECTIONS } from './constants' -import { marketplaceSearchParamsParsers } from './search-params' +import { CREATION_TYPE, marketplaceSearchParamsParsers } from './search-params' import { getCollectionsParams, getMarketplaceCollectionsAndPlugins, getMarketplaceTemplateCollectionsAndTemplates } from './utils' // The server side logic should move to marketplace's codebase so that we can get rid of Next.js @@ -17,7 +17,7 @@ async function getDehydratedState(searchParams?: Promise) { const params = await loadSearchParams(searchParams) const queryClient = getQueryClientServer() - if (params.creationType === 'templates') { + if (params.creationType === CREATION_TYPE.templates) { await queryClient.prefetchQuery({ queryKey: marketplaceQuery.templateCollections.list.queryKey({ input: { query: undefined } }), queryFn: () => getMarketplaceTemplateCollectionsAndTemplates(), diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx index 29ba4e53a9..1e76a29dcf 100644 --- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx @@ -1,7 +1,7 @@ 'use client' import Loading from '@/app/components/base/loading' -import { useMarketplaceData } from '../state' +import { isPluginsData, useMarketplaceData } from '../state' import FlatList from './flat-list' import ListWithCollection from './list-with-collection' @@ -11,50 +11,33 @@ type ListWrapperProps = { const ListWrapper = ({ showInstallButton }: ListWrapperProps) => { const marketplaceData = useMarketplaceData() - const { creationType, isLoading, page, isFetchingNextPage } = marketplaceData - - const isPluginView = creationType === 'plugins' + const { isLoading, page, isFetchingNextPage } = marketplaceData const renderContent = () => { - if (!isPluginView) { - const { templateCollections, templateCollectionTemplatesMap, templates } = marketplaceData - if (templates !== undefined) { - return ( - + : ( + + ) + } + + const { templateCollections, templateCollectionTemplatesMap, templates } = marketplaceData + return templates !== undefined + ? + : ( + ) - } - - return ( - - ) - } - - const { pluginCollections, pluginCollectionPluginsMap, plugins } = marketplaceData - if (plugins !== undefined) { - return ( - - ) - } - - return ( - - ) } return ( diff --git a/web/app/components/plugins/marketplace/search-params.ts b/web/app/components/plugins/marketplace/search-params.ts index 5d1edeabc4..4ef6912ba5 100644 --- a/web/app/components/plugins/marketplace/search-params.ts +++ b/web/app/components/plugins/marketplace/search-params.ts @@ -1,12 +1,17 @@ import { parseAsArrayOf, parseAsString, parseAsStringEnum } from 'nuqs/server' -export type CreationType = 'plugins' | 'templates' +export const CREATION_TYPE = { + plugins: 'plugins', + templates: 'templates', +} as const + +export type CreationType = typeof CREATION_TYPE[keyof typeof CREATION_TYPE] export const marketplaceSearchParamsParsers = { category: parseAsString.withDefault('all').withOptions({ history: 'replace', clearOnDefault: false }), q: parseAsString.withDefault('').withOptions({ history: 'replace' }), tags: parseAsArrayOf(parseAsString).withDefault([]).withOptions({ history: 'replace' }), - creationType: parseAsStringEnum(['plugins', 'templates']).withDefault('plugins').withOptions({ history: 'replace' }), + creationType: parseAsStringEnum([CREATION_TYPE.plugins, CREATION_TYPE.templates]).withDefault(CREATION_TYPE.plugins).withOptions({ history: 'replace' }), searchTab: parseAsStringEnum(['all', 'plugins', 'templates', 'creators']).withDefault('').withOptions({ history: 'replace' }), } diff --git a/web/app/components/plugins/marketplace/state.ts b/web/app/components/plugins/marketplace/state.ts index 947d0486e0..81e77cfb1e 100644 --- a/web/app/components/plugins/marketplace/state.ts +++ b/web/app/components/plugins/marketplace/state.ts @@ -1,11 +1,11 @@ import type { PluginsSearchParams, TemplateSearchParams } from './types' import { useDebounce } from 'ahooks' -import { useSearchParams } from 'next/navigation' import { useCallback, useMemo } from 'react' -import { useActivePluginCategory, useActiveTemplateCategory, useFilterPluginTags, useMarketplaceSearchMode, useMarketplaceSortValue, useSearchText } from './atoms' +import { useActivePluginCategory, useActiveTemplateCategory, useCreationType, useFilterPluginTags, useMarketplaceSearchMode, useMarketplaceSortValue, useSearchText } from './atoms' import { CATEGORY_ALL } from './constants' import { useMarketplaceContainerScroll } from './hooks' import { useMarketplaceCollectionsAndPlugins, useMarketplacePlugins, useMarketplaceTemplateCollectionsAndTemplates, useMarketplaceTemplates } from './query' +import { CREATION_TYPE } from './search-params' import { getCollectionsParams, getPluginFilterType, mapTemplateDetailToTemplate } from './utils' const getCategory = (category: string) => { @@ -121,31 +121,22 @@ export function useTemplatesMarketplaceData(enabled = true) { } } -type PluginsMarketplaceData = ReturnType -type TemplatesMarketplaceData = ReturnType -type MarketplaceData - = ({ creationType: 'plugins' } & PluginsMarketplaceData) - | ({ creationType: 'templates' } & TemplatesMarketplaceData) +export type PluginsMarketplaceData = ReturnType +export type TemplatesMarketplaceData = ReturnType +export type MarketplaceData = PluginsMarketplaceData | TemplatesMarketplaceData + +export function isPluginsData(data: MarketplaceData): data is PluginsMarketplaceData { + return 'pluginCollections' in data +} /** * Main hook that routes to appropriate data based on creationType * Returns either plugins or templates data based on URL parameter */ export function useMarketplaceData(): MarketplaceData { - const searchParams = useSearchParams() - const creationType = (searchParams.get('creationType') || 'plugins') as 'plugins' | 'templates' + const [creationType] = useCreationType() - const pluginsData = usePluginsMarketplaceData(creationType === 'plugins') - const templatesData = useTemplatesMarketplaceData(creationType === 'templates') - if (creationType === 'templates') { - return { - creationType, - ...templatesData, - } - } - - return { - creationType, - ...pluginsData, - } + const pluginsData = usePluginsMarketplaceData(creationType === CREATION_TYPE.plugins) + const templatesData = useTemplatesMarketplaceData(creationType === CREATION_TYPE.templates) + return creationType === CREATION_TYPE.templates ? templatesData : pluginsData } diff --git a/web/app/components/plugins/plugin-page/nav-operations.tsx b/web/app/components/plugins/plugin-page/nav-operations.tsx index 8193f20723..6a819cc94b 100644 --- a/web/app/components/plugins/plugin-page/nav-operations.tsx +++ b/web/app/components/plugins/plugin-page/nav-operations.tsx @@ -1,7 +1,6 @@ 'use client' import { RiAddLine, RiBookOpenLine, RiGithubLine } from '@remixicon/react' import Link from 'next/link' -import { useSearchParams } from 'next/navigation' import { useState } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' @@ -12,8 +11,10 @@ import { PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' +import { CREATION_TYPE } from '@/app/components/plugins/marketplace/search-params' import { useDocLink } from '@/context/i18n' import { cn } from '@/utils/classnames' +import { useCreationType } from '../marketplace/atoms' type DropdownItemProps = { href: string @@ -80,23 +81,18 @@ export const SubmitRequestDropdown = () => { ) } -type CreationTypeTabsProps = { - creationType?: string -} - -export const CreationTypeTabs = ({ creationType: creationTypeProp }: CreationTypeTabsProps = {}) => { +export const CreationTypeTabs = () => { const { t } = useTranslation() - const searchParams = useSearchParams() - const creationType = creationTypeProp || searchParams.get('creationType') || 'plugins' + const [creationType] = useCreationType() return (
@@ -105,11 +101,11 @@ export const CreationTypeTabs = ({ creationType: creationTypeProp }: CreationTyp