From f61bd6bfbbda174d316e484f67c63abb665e6f27 Mon Sep 17 00:00:00 2001 From: Matteo Date: Thu, 21 May 2026 20:22:07 +0200 Subject: [PATCH] store: align Adapter Store card design with the marketing-site Marketplace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The in-app /connectors/store rendered minimal cards (name + description + region tag) with no brand logo, while anythingmcp.com/marketplace shipped a richer design: branded tile, region flag, category/auth-type chips, log-scale tool-count meter, install + docs buttons. Same product surface, two faces — bring the cloud app in line: Backend - Expose connector.authType on each AdapterMeta so the card can render the auth-type chip without a per-row /api/adapters/:slug round-trip. Frontend - New renders the brand SVG from /logos/connectors/.svg with onError fallback to a coloured monogram tile (deterministic from slug). Mirrors the marketing-site BrandTile pixel-for-pixel. - Card redesign: logo + name + region flag header, category·region meta, line-clamp-3 description, dashed-border footer with tools · [bar viz] · [auth chip] · [docs ↗] · [Install →] - Auth chip uses an emerald accent for NONE (public APIs) and a neutral pill for everything else, mirroring the marketing card. Lock/Sparkles icons inline. No behaviour change: the install button still routes through the existing credentials modal + McpAssignModal. --- packages/backend/src/adapters/catalog.ts | 4 + .../src/app/connectors/store/page.tsx | 252 ++++++++++++++---- 2 files changed, 202 insertions(+), 54 deletions(-) diff --git a/packages/backend/src/adapters/catalog.ts b/packages/backend/src/adapters/catalog.ts index e1eb7de..ebe823b 100644 --- a/packages/backend/src/adapters/catalog.ts +++ b/packages/backend/src/adapters/catalog.ts @@ -180,6 +180,9 @@ export interface AdapterMeta { docsUrl: string; requiredEnvVars: string[]; toolCount: number; + /** Surfaced on cards so the UI can render an auth-type chip without an + * extra round-trip to /api/adapters/:slug. */ + authType?: string; /** When true, surfaced in the marketing site's "Featured" rail. */ featured?: boolean; /** Higher = ranked earlier in catalog listings. Default 0. */ @@ -422,6 +425,7 @@ export function listAdapters(): AdapterMeta[] { docsUrl: adapter.docsUrl, requiredEnvVars: adapter.requiredEnvVars, toolCount: adapter.tools.length, + authType: adapter.connector.authType, featured: adapter.featured, priority: adapter.priority, })); diff --git a/packages/frontend/src/app/connectors/store/page.tsx b/packages/frontend/src/app/connectors/store/page.tsx index c079783..c6ff851 100644 --- a/packages/frontend/src/app/connectors/store/page.tsx +++ b/packages/frontend/src/app/connectors/store/page.tsx @@ -17,6 +17,78 @@ const REGION_LABELS: Record = { intl: 'International', }; +const REGION_FLAGS: Record = { + de: '🇩🇪', + eu: '🇪🇺', + global: '🌐', + intl: '🌐', + uk: '🇬🇧', + gb: '🇬🇧', + in: '🇮🇳', + br: '🇧🇷', + ng: '🇳🇬', + jp: '🇯🇵', +}; + +/* Deterministic colour palette for the monogram fallback when no SVG exists. + Mirrors lib/adapters.ts on the marketing site for visual consistency. */ +const MONOGRAM_PALETTE = [ + '#2563eb', '#7c3aed', '#0ea5e9', '#10b981', '#f59e0b', + '#ef4444', '#ec4899', '#14b8a6', '#6366f1', '#84cc16', + '#0891b2', '#a855f7', '#f97316', '#06b6d4', '#22c55e', +]; + +function monogramOf(name: string): string { + const stripped = name.replace(/[().]/g, '').trim(); + const parts = stripped.split(/\s+/); + if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase(); + return stripped.slice(0, 2).toUpperCase(); +} + +function brandColor(slug: string): string { + let h = 0; + for (let i = 0; i < slug.length; i++) h = (h * 31 + slug.charCodeAt(i)) >>> 0; + return MONOGRAM_PALETTE[h % MONOGRAM_PALETTE.length]; +} + +/* Brand logo or coloured monogram fallback. Matches the marketing-site + Marketplace card visual exactly so the in-app store feels like the same + product surface. */ +function BrandTile({ adapter, size = 44 }: { adapter: AdapterItem; size?: number }) { + const [failed, setFailed] = useState(false); + if (adapter.icon && !failed) { + return ( +
+ {adapter.name} setFailed(true)} + /> +
+ ); + } + return ( +
= 56 ? 22 : 14, + }} + > + {monogramOf(adapter.name)} +
+ ); +} + const CATEGORY_LABELS: Record = { logistics: 'Logistics', finance: 'Finance', @@ -54,7 +126,10 @@ const AUTH_LABELS: Record = { BEARER_TOKEN: 'Bearer Token', OAUTH2: 'OAuth 2.0', BASIC: 'Basic Auth', - NONE: 'None (Public API)', + BASIC_AUTH: 'Basic Auth', + QUERY_AUTH: 'Query Param Auth', + LOGIN_TOKEN: 'Login Token', + NONE: 'Public API', }; interface AdapterItem { @@ -67,6 +142,7 @@ interface AdapterItem { docsUrl: string; requiredEnvVars: string[]; toolCount: number; + authType?: string; } interface AdapterDetail extends AdapterItem { @@ -301,62 +377,103 @@ function AdapterStoreContent() { ) : (
- {filtered.map((adapter) => ( -
-
-

{adapter.name}

-
- {adapter.region && ( - - {REGION_LABELS[adapter.region] || adapter.region} - - )} + {filtered.map((adapter) => { + const isPublic = adapter.authType === 'NONE'; + const isImporting = importing === adapter.slug; + /* log-ish 1..10 segment scale, same as the marketing-site card */ + const fillCount = Math.max( + 1, + Math.min(10, Math.round(Math.log2(adapter.toolCount + 1) * 2.2)), + ); + return ( +
+
+ +
+
+ + {adapter.name} + + + {REGION_FLAGS[adapter.region] || '🌐'} + +
+
+ + {CATEGORY_LABELS[adapter.category] || adapter.category} + + · + {REGION_LABELS[adapter.region] || adapter.region} +
+
-
-

- {adapter.description} -

+

+ {adapter.description} +

-
-
- {adapter.category && ( - {CATEGORY_LABELS[adapter.category] || adapter.category} - )} - {adapter.toolCount} tool{adapter.toolCount !== 1 ? 's' : ''} +
+
+ + {adapter.toolCount} + + tool{adapter.toolCount !== 1 ? 's' : ''} + + {Array.from({ length: 10 }, (_, i) => ( + + ))} + +
+
+ {adapter.authType && ( + + {isPublic ? : } + {AUTH_LABELS[adapter.authType] || adapter.authType} + + )} + {adapter.docsUrl && ( + + + + )} + +
- - -
- - {adapter.docsUrl && ( - - API Documentation - - )} -
- ))} + + ); + })}
)} @@ -509,9 +626,36 @@ function CloseIcon() { function LockIcon() { return ( - + ); } + +function SparklesIcon() { + return ( + + + + ); +} + +function ExternalLinkIcon() { + return ( + + + + + + ); +} + +function ArrowRightIcon() { + return ( + + + + + ); +}