feat: add utility functions for building search parameters and marketplace URLs

This commit is contained in:
yessenia 2026-03-04 15:20:08 +08:00
parent 7934b0222d
commit 61b6b838de
4 changed files with 68 additions and 29 deletions

View File

@ -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) || []

View File

@ -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)[] }) => (
</div>
)
const getSearchParamsString = (params?: Record<string, string | undefined>) => {
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<string, string | undefined> | 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 (
<DropdownItem
key={template.id}
href={getDropdownMarketplaceUrl(
href={buildMarketplaceHref(
`/template/${template.publisher_handle}/${template.template_name}`,
{ templateId: template.id },
includeSource,
@ -326,7 +302,7 @@ function CreatorsSection({ creators, includeSource, t }: {
<a
key={creator.unique_handle}
className="flex items-center gap-2 rounded-lg px-3 py-2 hover:bg-state-base-hover"
href={getDropdownMarketplaceUrl(`/creators/${creator.unique_handle}`, undefined, includeSource)}
href={buildMarketplaceHref(`/creators/${creator.unique_handle}`, undefined, includeSource)}
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full border-[0.5px] border-divider-regular">
<img

View File

@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest'
import { buildMarketplaceHref, buildSearchParamsString } from './utils'
describe('buildSearchParamsString', () => {
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')
})
})

View File

@ -102,6 +102,28 @@ export const getPluginDetailLinkInMarketplace = (plugin: Plugin) => {
return `/plugin/${plugin.org}/${plugin.name}`
}
export const buildSearchParamsString = (params?: Record<string, string | undefined>) => {
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<string, string | undefined> | 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,