diff --git a/web/app/components/plugins/marketplace/__tests__/utils.spec.ts b/web/app/components/plugins/marketplace/__tests__/utils.spec.ts index 478a86ffa39..91c83e21194 100644 --- a/web/app/components/plugins/marketplace/__tests__/utils.spec.ts +++ b/web/app/components/plugins/marketplace/__tests__/utils.spec.ts @@ -122,6 +122,22 @@ describe('getPluginLinkInMarketplace', () => { }) }) +describe('getPluginDetailLinkInMarketplace', () => { + it('should return the local detail link for a regular plugin', async () => { + const { getPluginDetailLinkInMarketplace } = await import('../utils') + const plugin = createMockPlugin({ org: 'test-org', name: 'test-plugin', type: 'plugin' }) + + expect(getPluginDetailLinkInMarketplace(plugin)).toBe('/plugin/test-org/test-plugin') + }) + + it('should return the local detail link for a bundle', async () => { + const { getPluginDetailLinkInMarketplace } = await import('../utils') + const bundle = createMockPlugin({ org: 'test-org', name: 'test-bundle', type: 'bundle' }) + + expect(getPluginDetailLinkInMarketplace(bundle)).toBe('/bundles/test-org/test-bundle') + }) +}) + describe('getMarketplaceListCondition', () => { it('should return category condition for tool', async () => { const { getMarketplaceListCondition } = await import('../utils') diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 9c140b9d73a..5714dff3588 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -8,6 +8,7 @@ import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper' type MarketplaceProps = { showInstallButton?: boolean + linkToMarketplaceDetail?: boolean pluginTypeSwitchClassName?: string isMarketplacePlatform?: boolean marketplaceNav?: React.ReactNode @@ -19,6 +20,7 @@ type MarketplaceProps = { const Marketplace = async ({ showInstallButton = false, + linkToMarketplaceDetail = false, pluginTypeSwitchClassName, isMarketplacePlatform = false, marketplaceNav, @@ -35,7 +37,10 @@ const Marketplace = async ({ {!isMarketplacePlatform && ( )} - + diff --git a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx index 0e8d561f864..049b6d51157 100644 --- a/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx +++ b/web/app/components/plugins/marketplace/list/__tests__/card-wrapper.spec.tsx @@ -87,13 +87,19 @@ describe('CardWrapper', () => { , ) - it('renders a non-navigating card when install button is hidden', () => { + it('renders a non-navigating card by default when install button is hidden', () => { renderCardWrapper() expect(screen.queryByRole('link')).not.toBeInTheDocument() expect(screen.getByTestId('card-more-info')).toHaveTextContent('42:tag:search|tag:agent') }) + it('links the card to its marketplace detail when explicitly enabled', () => { + renderCardWrapper({ linkToMarketplaceDetail: true }) + + expect(screen.getByRole('link')).toHaveAttribute('href', '/detail/dify/plugin-a') + }) + it('renders install and marketplace detail actions when install button is shown', () => { renderCardWrapper({ showInstallButton: true }) diff --git a/web/app/components/plugins/marketplace/list/card-wrapper.tsx b/web/app/components/plugins/marketplace/list/card-wrapper.tsx index d9c4b8a7570..081e6d9c0c8 100644 --- a/web/app/components/plugins/marketplace/list/card-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/card-wrapper.tsx @@ -11,17 +11,20 @@ import CardMoreInfo from '@/app/components/plugins/card/card-more-info' import { useTags } from '@/app/components/plugins/hooks' import { useOptionalPluginInstallPermission } from '@/app/components/plugins/install-plugin/hooks/use-plugin-install-permission' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' -import { getPluginLinkInMarketplace } from '../utils' +import Link from '@/next/link' +import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '../utils' type CardWrapperProps = { plugin: Plugin showInstallButton?: boolean isInstalled?: boolean + linkToMarketplaceDetail?: boolean } const CardWrapperComponent = ({ plugin, showInstallButton, isInstalled = false, + linkToMarketplaceDetail = false, }: CardWrapperProps) => { const { t } = useTranslation() const { theme } = useTheme() @@ -102,7 +105,7 @@ const CardWrapperComponent = ({ ) } - return ( + const card = (
) + + if (!linkToMarketplaceDetail) return card + + return ( + + {card} + + ) } // Memoize the component to prevent unnecessary re-renders when props haven't changed diff --git a/web/app/components/plugins/marketplace/list/index.tsx b/web/app/components/plugins/marketplace/list/index.tsx index 94206e589f9..6d6c227b56e 100644 --- a/web/app/components/plugins/marketplace/list/index.tsx +++ b/web/app/components/plugins/marketplace/list/index.tsx @@ -15,6 +15,7 @@ type ListProps = { marketplaceCollectionPluginsMap: Record plugins?: Plugin[] showInstallButton?: boolean + linkToMarketplaceDetail?: boolean cardContainerClassName?: string cardRender?: (plugin: Plugin) => React.JSX.Element | null emptyClassName?: string @@ -25,6 +26,7 @@ const List = ({ marketplaceCollectionPluginsMap, plugins, showInstallButton, + linkToMarketplaceDetail, cardContainerClassName, cardRender, emptyClassName, @@ -62,6 +64,7 @@ const List = ({ marketplaceCollections={marketplaceCollections} marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap} showInstallButton={showInstallButton} + linkToMarketplaceDetail={linkToMarketplaceDetail} cardContainerClassName={cardContainerClassName} cardRender={cardRender} onCollectionMoreClick={onCollectionMoreClick} @@ -79,6 +82,7 @@ const List = ({ plugin={plugin} showInstallButton={showInstallButton} isInstalled={installedPluginIds.has(plugin.plugin_id)} + linkToMarketplaceDetail={linkToMarketplaceDetail} /> ) })} diff --git a/web/app/components/plugins/marketplace/list/list-with-collection.tsx b/web/app/components/plugins/marketplace/list/list-with-collection.tsx index 4fd44f00ece..42c03c9f6c5 100644 --- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx +++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx @@ -35,6 +35,7 @@ type ListWithCollectionProps = { marketplaceCollections: MarketplaceCollection[] marketplaceCollectionPluginsMap: Record showInstallButton?: boolean + linkToMarketplaceDetail?: boolean cardContainerClassName?: string cardRender?: (plugin: Plugin) => React.JSX.Element | null onCollectionMoreClick?: (searchParams?: SearchParamsFromCollection) => void @@ -46,13 +47,25 @@ type PluginCardProps = { showInstallButton?: boolean cardRender?: (plugin: Plugin) => React.JSX.Element | null isInstalled?: boolean + linkToMarketplaceDetail?: boolean } -const PluginCard = ({ plugin, showInstallButton, cardRender, isInstalled }: PluginCardProps) => { +const PluginCard = ({ + plugin, + showInstallButton, + cardRender, + isInstalled, + linkToMarketplaceDetail, +}: PluginCardProps) => { if (cardRender) return cardRender(plugin) return ( - + ) } @@ -60,6 +73,7 @@ const ListWithCollection = ({ marketplaceCollections, marketplaceCollectionPluginsMap, showInstallButton, + linkToMarketplaceDetail, cardContainerClassName, cardRender, onCollectionMoreClick, @@ -147,6 +161,7 @@ const ListWithCollection = ({ @@ -163,6 +178,7 @@ const ListWithCollection = ({ key={plugin.plugin_id} plugin={plugin} showInstallButton={showInstallButton} + linkToMarketplaceDetail={linkToMarketplaceDetail} cardRender={cardRender} isInstalled={installedPluginIds?.has(plugin.plugin_id)} /> diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx index 385bc9be9f4..63a1debe9db 100644 --- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx @@ -7,8 +7,9 @@ import List from './index' type ListWrapperProps = { showInstallButton?: boolean + linkToMarketplaceDetail?: boolean } -const ListWrapper = ({ showInstallButton }: ListWrapperProps) => { +const ListWrapper = ({ showInstallButton, linkToMarketplaceDetail }: ListWrapperProps) => { const { t } = useTranslation() const { @@ -45,6 +46,7 @@ const ListWrapper = ({ showInstallButton }: ListWrapperProps) => { marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap || {}} plugins={plugins} showInstallButton={showInstallButton} + linkToMarketplaceDetail={linkToMarketplaceDetail} /> )} diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts index affae9efa72..acc77a84ebf 100644 --- a/web/app/components/plugins/marketplace/utils.ts +++ b/web/app/components/plugins/marketplace/utils.ts @@ -60,6 +60,16 @@ export const getPluginLinkInMarketplace = ( return getMarketplaceUrl(`/plugins/${plugin.org}/${plugin.name}`, params) } +export const getPluginDetailLinkInMarketplace = ( + plugin: Pick, +) => { + const org = encodeURIComponent(plugin.org) + const name = encodeURIComponent(plugin.name) + + if (plugin.type === 'bundle') return `/bundles/${org}/${name}` + return `/plugin/${org}/${name}` +} + export const getMarketplaceCategoryUrl = ( category?: string, params?: Record,