-
- {name}
-
-
- by
- {' '}
- {author}
+ {/* Title */}
+
+
{name}
+
+
+ by
+ {author}
+
+ {formattedUsedCount && (
+ <>
+ ยท
+
+ {formattedUsedCount}
+ {' '}
+ used
+
+ >
+ )}
{/* Description */}
-
- {descriptionText}
+
- {/* Tags */}
- {tags && tags.length > 0 && (
-
- {tags.slice(0, 3).map(tag => (
-
- {tag}
-
- ))}
- {tags.length > 3 && (
-
- +
- {tags.length - 3}
-
- )}
-
- )}
+ {/* Bottom Info Bar - Tags as icons */}
+
+ {tags && tags.length > 0 && (
+ <>
+ {visibleTags.map((tag, index) => (
+
+ {tag}
+
+ ))}
+ {remainingTagsCount > 0 && (
+
+
+ +
+ {remainingTagsCount}
+
+
+ )}
+ >
+ )}
+
)
}
diff --git a/web/app/components/plugins/marketplace/list/template-search-list.tsx b/web/app/components/plugins/marketplace/list/template-search-list.tsx
new file mode 100644
index 0000000000..086db7afa1
--- /dev/null
+++ b/web/app/components/plugins/marketplace/list/template-search-list.tsx
@@ -0,0 +1,27 @@
+'use client'
+
+import type { Template } from '../types'
+import Empty from '../empty'
+import TemplateCard from './template-card'
+
+type TemplateSearchListProps = {
+ templates: Template[]
+}
+
+const TemplateSearchList = ({ templates }: TemplateSearchListProps) => {
+ if (templates.length === 0) {
+ return
+ }
+
+ return (
+
+ {templates.map(template => (
+
+
+
+ ))}
+
+ )
+}
+
+export default TemplateSearchList
diff --git a/web/app/components/plugins/marketplace/state.ts b/web/app/components/plugins/marketplace/state.ts
index 303ae168aa..89fa7637bf 100644
--- a/web/app/components/plugins/marketplace/state.ts
+++ b/web/app/components/plugins/marketplace/state.ts
@@ -6,7 +6,7 @@ import { useActivePluginType, useFilterPluginTags, useMarketplaceSearchMode, use
import { PLUGIN_TYPE_SEARCH_MAP } from './constants'
import { useMarketplaceContainerScroll } from './hooks'
import { useMarketplaceCollectionsAndPlugins, useMarketplacePlugins, useMarketplaceTemplateCollectionsAndTemplates, useMarketplaceTemplates } from './query'
-import { getCollectionsParams, getMarketplaceListFilterType } from './utils'
+import { getCollectionsParams, getMarketplaceListFilterType, mapTemplateDetailToTemplate } from './utils'
/**
* Hook for plugins marketplace data
@@ -122,13 +122,18 @@ export function useTemplatesMarketplaceData() {
}
}, [templateCollectionsQuery.data?.templateCollectionTemplatesMap])
+ const searchTemplates = useMemo(() => {
+ const rawTemplates = templatesQuery.data?.pages.flatMap(page => page.templates) || []
+ return rawTemplates.map(mapTemplateDetailToTemplate)
+ }, [templatesQuery.data])
+
// Return search results when in search mode, otherwise return collection data
if (isSearchMode) {
return {
isSearchMode,
templateCollections: undefined,
templateCollectionTemplatesMap: undefined,
- templates: templatesQuery.data?.pages.flatMap(page => page.templates),
+ templates: searchTemplates,
templatesTotal: templatesQuery.data?.pages[0]?.total,
page: templatesQuery.data?.pages.length || 1,
isLoading: templatesQuery.isLoading,
diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts
index 34c287a86d..41995bfdc6 100644
--- a/web/app/components/plugins/marketplace/utils.ts
+++ b/web/app/components/plugins/marketplace/utils.ts
@@ -116,6 +116,23 @@ export const getMarketplaceCollectionsAndPlugins = async (
}
}
+export function mapTemplateDetailToTemplate(template: TemplateDetail): Template {
+ const descriptionText = template.overview || template.readme || ''
+ return {
+ template_id: template.id,
+ name: template.template_name,
+ description: {
+ en_US: descriptionText,
+ zh_Hans: descriptionText,
+ },
+ icon: template.icon || '',
+ tags: template.categories || [],
+ author: template.publisher_unique_handle || template.creator_email || '',
+ created_at: template.created_at,
+ updated_at: template.updated_at,
+ }
+}
+
export const getMarketplaceTemplateCollectionsAndTemplates = async (
query?: { page?: number, page_size?: number, condition?: string },
options?: MarketplaceFetchOptions,
@@ -133,7 +150,7 @@ export const getMarketplaceTemplateCollectionsAndTemplates = async (
}, {
signal: options?.signal,
})
- templateCollections = res.data || []
+ templateCollections = res.data?.collections || []
await Promise.all(templateCollections.map(async (collection) => {
try {
@@ -141,7 +158,8 @@ export const getMarketplaceTemplateCollectionsAndTemplates = async (
params: { collectionName: collection.name },
body: { limit: 20 },
}, { signal: options?.signal })
- templateCollectionTemplatesMap[collection.name] = (templatesRes.data || []) as Template[]
+ const templatesData = templatesRes.data?.templates || []
+ templateCollectionTemplatesMap[collection.name] = templatesData.map(mapTemplateDetailToTemplate)
}
catch {
templateCollectionTemplatesMap[collection.name] = []
diff --git a/web/contract/marketplace.ts b/web/contract/marketplace.ts
index 4736e2c3f5..97d936ab31 100644
--- a/web/contract/marketplace.ts
+++ b/web/contract/marketplace.ts
@@ -10,7 +10,6 @@ import type {
MarketplaceCollection,
PluginsSearchParams,
SyncCreatorProfileRequest,
- Template,
TemplateCollection,
TemplateDetail,
TemplateSearchParams,
@@ -88,11 +87,13 @@ export const templateCollectionsContract = base
)
.output(
type<{
- data?: TemplateCollection[]
- has_more?: boolean
- limit?: number
- page?: number
- total?: number
+ data?: {
+ collections?: TemplateCollection[]
+ has_more?: boolean
+ limit?: number
+ page?: number
+ total?: number
+ }
}>(),
)
@@ -151,7 +152,7 @@ export const getCollectionTemplatesContract = base
)
.output(
type<{
- data?: Template[]
+ data?: TemplatesListResponse
}>(),
)