"use client"; import { CheckIcon, LayoutGridIcon, type LucideIcon, SearchIcon, ShieldCheckIcon, StarIcon, XIcon, } from "lucide-react"; import { AnimatePresence, LayoutGroup, motion, useReducedMotion } from "motion/react"; import { useEffect, useId, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; export type AppTone = "blue" | "green" | "amber" | "red" | "neutral"; export interface MarketplaceCategory { id: string; label: string; icon: LucideIcon; } export interface MarketplaceApp { id: string; name: string; category: string; description: string; icon: LucideIcon; tone: AppTone; rating: number; installs: string; pricing: "free" | "paid"; isNew?: boolean; connected?: boolean; permissions?: string[]; } export interface ConnectorMarketplaceProps { apps: MarketplaceApp[]; categories: MarketplaceCategory[]; workspace?: { name: string; initial?: string }; onConnectionChange?: (appId: string, connected: boolean) => void; className?: string; } type Status = "idle" | "connecting" | "connected"; type Phase = "review" | "authorizing" | "syncing" | "done"; const toneClass: Record = { blue: "bg-blue-500 text-white", green: "bg-emerald-500 text-white", amber: "bg-amber-500 text-white", red: "bg-rose-500 text-white", neutral: "bg-foreground text-background", }; const press = "transition-[box-shadow,background-color,transform] duration-150 ease-out active:scale-[0.97]"; function AppTile({ app, size = "md" }: { app: MarketplaceApp; size?: "md" | "lg" }) { return ( ); } function ConnectBridge({ phase, app, workspace }: { phase: Phase; app: MarketplaceApp; workspace: string }) { const reduce = useReducedMotion(); const flowing = phase === "authorizing" || phase === "syncing"; const done = phase === "done"; return (
{flowing && !reduce && [0, 1, 2].map((i) => ( ))}
{done && ( )}
{workspace.slice(0, 1)}
); } function ConnectDialog({ app, workspace, onClose, onConnected, }: { app: MarketplaceApp; workspace: string; onClose: () => void; onConnected: () => void; }) { const reduce = useReducedMotion(); const [phase, setPhase] = useState("review"); const primaryRef = useRef(null); const timers = useRef[]>([]); const permissions = app.permissions ?? ["Read workspace members", "Create and update records", "Send notifications"]; useEffect(() => { primaryRef.current?.focus(); const list = timers.current; return () => list.forEach(clearTimeout); }, []); const authorize = () => { const pace = reduce ? 0.3 : 1; setPhase("authorizing"); timers.current.push(setTimeout(() => setPhase("syncing"), 1100 * pace)); timers.current.push( setTimeout(() => { setPhase("done"); onConnected(); }, 2300 * pace), ); timers.current.push(setTimeout(onClose, 3500 * pace)); }; const steps: { key: Phase; label: string }[] = [ { key: "authorizing", label: "Authorizing access" }, { key: "syncing", label: "Syncing your data" }, { key: "done", label: "Connected" }, ]; const order: Phase[] = ["review", "authorizing", "syncing", "done"]; const at = order.indexOf(phase); return ( { if (e.key === "Escape" && phase === "review") onClose(); }} role="dialog" transition={reduce ? { duration: 0 } : { type: "spring", stiffness: 480, damping: 40 }} > {phase === "review" && ( )}

{phase === "done" ? `${app.name} is connected` : `Connect ${app.name} to ${workspace}`}

{phase === "review" ? (

{app.name} will be able to:

    {permissions.map((p) => (
  • {p}
  • ))}
) : ( {steps.map((s, i) => { const state = at > i + 1 || phase === "done" ? "done" : at === i + 1 ? "active" : "todo"; return (
  • {state === "done" ? ( ) : state === "active" ? ( ) : null} {s.label}
  • ); })}
    )}
    ); } function ConnectButton({ status, onConnect, onDisconnect }: { status: Status; onConnect: () => void; onDisconnect: () => void }) { if (status === "connecting") { return ( ); } if (status === "connected") { return ( ); } return ( ); } export function ConnectorMarketplace({ apps, categories, workspace = { name: "Acme Labs" }, onConnectionChange, className, }: ConnectorMarketplaceProps) { const uid = useId().replace(/:/g, ""); const reduce = useReducedMotion(); const [category, setCategory] = useState("all"); const [query, setQuery] = useState(""); const [filters, setFilters] = useState({ connected: false, free: false, isNew: false }); const [sort, setSort] = useState<"popular" | "az">("popular"); const [status, setStatus] = useState>(() => Object.fromEntries(apps.map((a) => [a.id, a.connected ? "connected" : "idle"])), ); const [dialogApp, setDialogApp] = useState(null); const returnFocus = useRef(null); const setAppStatus = (id: string, s: Status) => { setStatus((prev) => ({ ...prev, [id]: s })); if (s !== "connecting") onConnectionChange?.(id, s === "connected"); }; const q = query.trim().toLowerCase(); const visible = apps .filter((a) => category === "all" || a.category === category) .filter((a) => !q || a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q)) .filter((a) => !filters.connected || status[a.id] === "connected") .filter((a) => !filters.free || a.pricing === "free") .filter((a) => !filters.isNew || a.isNew) .sort((a, b) => (sort === "az" ? a.name.localeCompare(b.name) : b.rating - a.rating)); const connectedCount = Object.values(status).filter((s) => s === "connected").length; const allCategories: MarketplaceCategory[] = [{ id: "all", label: "All apps", icon: LayoutGridIcon }, ...categories]; const countFor = (id: string) => (id === "all" ? apps.length : apps.filter((a) => a.category === id).length); const spring = reduce ? { duration: 0 } : ({ type: "spring", stiffness: 480, damping: 42 } as const); const closeDialog = () => { if (dialogApp) { const id = dialogApp.id; setStatus((prev) => (prev[id] === "connecting" ? { ...prev, [id]: "idle" } : prev)); } setDialogApp(null); requestAnimationFrame(() => returnFocus.current?.focus()); }; return (
    setQuery(e.target.value)} placeholder="Search apps" type="search" value={query} />
    {( [ { value: "popular", label: "Top rated" }, { value: "az", label: "A–Z" }, ] as const ).map((o) => ( ))}
    {allCategories.map((c) => ( ))}
    {( [ { key: "connected", label: "Connected" }, { key: "free", label: "Free" }, { key: "isNew", label: "New" }, ] as const ).map((f) => ( ))}
    {visible.map((app) => (

    {app.name}

    {app.isNew && ( New )}

    {categories.find((c) => c.id === app.category)?.label ?? app.category}

    {app.pricing === "free" ? "Free" : "Paid"}

    {app.description}

    {app.rating.toFixed(1)} {app.installs} installs { returnFocus.current = document.activeElement as HTMLElement | null; setAppStatus(app.id, "connecting"); setDialogApp(app); }} onDisconnect={() => setAppStatus(app.id, "idle")} status={status[app.id] ?? "idle"} />
    ))}
    {visible.length === 0 && (

    No apps match

    Try another category or clear the filters.

    )}
    {dialogApp && (
    setAppStatus(dialogApp.id, "connected")} workspace={workspace.name} /> )}
    ); }