'use client'; import { useMemo, useState } from 'react'; import Link from 'next/link'; import { ArrowRight, Boxes, Star } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; import { Card } from '@/components/ui/Card'; import { buttonVariants } from '@/components/ui/Button'; import { Stagger, StaggerItem } from '@/components/effects/Stagger'; import { SectionEyebrow } from '@/components/landing/MarketingSection'; import { useAuthStore } from '@/stores/auth'; import { domainColor } from '@/lib/agent-colors'; import { moduleColor } from '@/lib/module-colors'; import { cn } from '@/lib/cn'; import type { MarketplaceItemPreview } from '@/types'; const ALL_DOMAINS = 'all'; // `buttonVariants` has no `outline` variant — that treatment lives on the Button // component's module path. Rebuild it here for an anchor: structural base // classes (variant: null skips the lime default) plus the marketplace hue. const cardCtaClass = cn( buttonVariants({ variant: null }), moduleColor('marketplace').btnOutline, 'w-full justify-center gap-1.5', ); interface ShowcaseGridProps { items: MarketplaceItemPreview[]; } /** * The two showcase bands with a domain filter above them. Anonymous visitors get * a sign-up call to action: installing a team requires an account, since it is * copied into the caller's own agents. */ export function ShowcaseGrid({ items }: ShowcaseGridProps) { const [domain, setDomain] = useState(ALL_DOMAINS); const hydrated = useAuthStore((s) => s.hydrated); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const signedIn = hydrated && isAuthenticated; // Only offer filters that would actually match something. const domains = useMemo( () => Array.from(new Set(items.map((item) => item.domain))).sort(), [items], ); const visible = useMemo( () => (domain === ALL_DOMAINS ? items : items.filter((i) => i.domain === domain)), [items, domain], ); const featured = visible.filter((item) => item.featured); const community = visible.filter((item) => !item.featured); return (
{domains.length > 1 && (
setDomain(ALL_DOMAINS)} > All {domains.map((d) => ( setDomain(d)}> {d} ))}
)} {items.length === 0 && (

> No agent teams have been published yet.

)} {featured.length > 0 && ( )} {community.length > 0 && ( )} {items.length > 0 && community.length === 0 && domain === ALL_DOMAINS && (

> No community teams yet — publish the first one.

)}
); } function Band({ eyebrow, title, subtitle, children, }: { eyebrow: string; title: string; subtitle: string; children: React.ReactNode; }) { return (
{eyebrow}

{title}

{subtitle}

{children}
); } function ShowcaseCards({ items, signedIn, }: { items: MarketplaceItemPreview[]; signedIn: boolean; }) { return ( {items.map((item) => { const dc = domainColor(item.domain); return (

{item.name}

{item.domain}
{/* The clamp needs its own element: a flex item is blockified, which turns `-webkit-box` into `flow-root` and silently kills it. */}

{item.description}

{item.featured && ( )} {item.author_label} {item.installs} installs
{signedIn ? 'Deploy' : 'Sign up to install'}
); })}
); } function FilterChip({ domain, active, onClick, children, }: { domain?: string; active: boolean; onClick: () => void; children: React.ReactNode; }) { const dc = domain ? domainColor(domain) : undefined; return ( ); }