From 61b6b838ded4b6b2fdbbbd7a49f6cff3606f4a5c Mon Sep 17 00:00:00 2001 From: yessenia Date: Wed, 4 Mar 2026 15:20:08 +0800 Subject: [PATCH] feat: add utility functions for building search parameters and marketplace URLs --- .../marketplace/list/template-card.tsx | 4 +- .../search-box/search-dropdown/index.tsx | 30 ++------------ .../plugins/marketplace/utils.spec.ts | 41 +++++++++++++++++++ .../components/plugins/marketplace/utils.ts | 22 ++++++++++ 4 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 web/app/components/plugins/marketplace/utils.spec.ts diff --git a/web/app/components/plugins/marketplace/list/template-card.tsx b/web/app/components/plugins/marketplace/list/template-card.tsx index 755dec2813..da3a4956cf 100644 --- a/web/app/components/plugins/marketplace/list/template-card.tsx +++ b/web/app/components/plugins/marketplace/list/template-card.tsx @@ -13,7 +13,7 @@ import { cn } from '@/utils/classnames' import { getIconFromMarketPlace } from '@/utils/get-icon' import { formatUsedCount } from '@/utils/template' import { getMarketplaceUrl } from '@/utils/var' -import { getTemplateIconUrl } from '../utils' +import { buildSearchParamsString, getTemplateIconUrl } from '../utils' type TemplateCardProps = { template: Template @@ -45,7 +45,7 @@ const TemplateCardComponent = ({ } return includeSource ? getMarketplaceUrl(`/template/${publisher_handle}/${template_name}`, queryParams) - : `${MARKETPLACE_URL_PREFIX}/template/${publisher_handle}/${template_name}?${new URLSearchParams(queryParams).toString()}` + : `${MARKETPLACE_URL_PREFIX}/template/${publisher_handle}/${template_name}?${buildSearchParamsString(queryParams)}` }, [publisher_handle, template_name, theme, locale, id, includeSource]) const visibleDepsPlugins = deps_plugins?.slice(0, MAX_VISIBLE_DEPS_PLUGINS) || [] diff --git a/web/app/components/plugins/marketplace/search-box/search-dropdown/index.tsx b/web/app/components/plugins/marketplace/search-box/search-dropdown/index.tsx index d9e155ec66..1beaf4ddda 100644 --- a/web/app/components/plugins/marketplace/search-box/search-dropdown/index.tsx +++ b/web/app/components/plugins/marketplace/search-box/search-dropdown/index.tsx @@ -9,9 +9,8 @@ import { useCategories } from '@/app/components/plugins/hooks' import { useRenderI18nObject } from '@/hooks/use-i18n' import { cn } from '@/utils/classnames' import { formatUsedCount } from '@/utils/template' -import { getMarketplaceUrl } from '@/utils/var' import { MARKETPLACE_TYPE_ICON_COMPONENTS } from '../../plugin-type-icons' -import { getCreatorAvatarUrl, getPluginDetailLinkInMarketplace, getTemplateIconUrl } from '../../utils' +import { buildMarketplaceHref, getCreatorAvatarUrl, getPluginDetailLinkInMarketplace, getTemplateIconUrl } from '../../utils' const DROPDOWN_PANEL = 'w-[472px] max-h-[710px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xl backdrop-blur-sm' const ICON_BOX_BASE = 'flex shrink-0 items-center justify-center overflow-hidden border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge' @@ -79,29 +78,6 @@ const ItemMeta = ({ items }: { items: (React.ReactNode | string)[] }) => ( ) -const getSearchParamsString = (params?: Record) => { - const searchParams = new URLSearchParams() - if (params) { - Object.keys(params).forEach((key) => { - const value = params[key] - if (value !== undefined && value !== null) - searchParams.append(key, value) - }) - } - return searchParams.toString() -} - -const getDropdownMarketplaceUrl = ( - path: string, - params: Record | undefined, - includeSource: boolean, -) => { - if (includeSource) - return getMarketplaceUrl(path, params) - const query = getSearchParamsString(params) - return query ? `${path}?${query}` : path -} - type SearchDropdownProps = { query: string plugins: Plugin[] @@ -226,7 +202,7 @@ function TemplatesSection({ templates, includeSource, t }: { return (
{ + it('filters undefined and null values', () => { + const query = buildSearchParamsString({ + theme: undefined, + language: 'en-US', + templateId: 'tpl-1', + empty: null as unknown as string, + }) + expect(query).toBe('language=en-US&templateId=tpl-1') + expect(query).not.toContain('theme=undefined') + expect(query).not.toContain('empty=') + }) +}) + +describe('buildMarketplaceHref', () => { + it('returns relative path with filtered query when includeSource is false', () => { + const href = buildMarketplaceHref('/template/foo/bar', { + theme: undefined, + language: 'en-US', + templateId: 'tpl-1', + }, false) + + expect(href).toBe('/template/foo/bar?language=en-US&templateId=tpl-1') + expect(href).not.toContain('theme=undefined') + }) + + it('delegates to marketplace source URL when includeSource is true', () => { + const href = buildMarketplaceHref('/template/foo/bar', { + language: 'en-US', + templateId: 'tpl-1', + }, true) + + expect(href).toContain('/template/foo/bar?') + expect(href).toContain('source=') + expect(href).toContain('language=en-US') + expect(href).toContain('templateId=tpl-1') + }) +}) diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts index 06500d3aa4..fb1741be58 100644 --- a/web/app/components/plugins/marketplace/utils.ts +++ b/web/app/components/plugins/marketplace/utils.ts @@ -102,6 +102,28 @@ export const getPluginDetailLinkInMarketplace = (plugin: Plugin) => { return `/plugin/${plugin.org}/${plugin.name}` } +export const buildSearchParamsString = (params?: Record) => { + const searchParams = new URLSearchParams() + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) + searchParams.append(key, value) + } + } + return searchParams.toString() +} + +export const buildMarketplaceHref = ( + path: string, + params: Record | undefined, + includeSource: boolean, +) => { + if (includeSource) + return getMarketplaceUrl(path, params) + const query = buildSearchParamsString(params) + return query ? `${path}?${query}` : path +} + export const getMarketplacePluginsByCollectionId = async ( collectionId: string, query?: CollectionsAndPluginsSearchParams,