Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 59 additions & 29 deletions apps/site/src/app/mcp/_components/agent-card.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,75 @@
"use client";

import Image from "next/image";
import { useState } from "react";

import { Tooltip, TooltipContent, TooltipTrigger } from "@prisma/eclipse";

export function AgentCard({
logo,
alt,
icon,
href,
copyText,
}: {
logo: string | null;
alt: string;
icon: string | null;
href: string;
href?: string;
copyText?: string;
}) {
const [copied, setCopied] = useState(false);

const isCopyAction = !!copyText;
const isExternalLink = !isCopyAction && href?.startsWith("http");

const handleClick = (e: React.MouseEvent) => {
if (!isCopyAction) return;
e.preventDefault();
navigator.clipboard.writeText(copyText);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};

const Tag = isCopyAction ? "button" : "a";
const linkProps = isCopyAction
? { type: "button" as const, onClick: handleClick }
: {
href,
...(isExternalLink ? { target: "_blank", rel: "noopener noreferrer" } : {}),
};

return (
<a
href={href}
title={alt}
aria-label={alt}
className="group relative flex h-30 w-full max-w-[165px] items-center justify-center rounded-[12px] border border-stroke-neutral bg-background-neutral-weaker shadow-box-low no-underline outline-offset-4 transition-[border-color,background-color,box-shadow] hover:border-stroke-ppg/60 hover:bg-background-default hover:shadow-box-high focus-visible:ring-2 focus-visible:ring-stroke-ppg dark:bg-background-neutral-weaker dark:hover:bg-background-neutral"
>
{logo ? (
<Image
src={logo}
alt=""
width={48}
height={48}
className="size-12 object-contain brightness-0 opacity-45 transition-opacity group-hover:opacity-65 dark:opacity-55 dark:invert"
unoptimized
/>
) : (
<span className="font-mono text-lg text-foreground-neutral-weak">
Any AI agent
</span>
)}
{icon ? (
<span
className="absolute right-1.75 top-1.75 text-foreground-neutral-weaker opacity-50 transition-opacity group-hover:opacity-100"
aria-hidden
<Tooltip open={copied || undefined}>
<TooltipTrigger asChild>
<Tag
aria-label={alt}
className="group relative flex h-30 w-full max-w-[165px] cursor-pointer items-center justify-center rounded-[12px] border border-stroke-neutral bg-background-neutral-weaker shadow-box-low no-underline outline-offset-4 transition-[border-color,background-color,box-shadow] hover:border-stroke-ppg/60 hover:bg-background-default hover:shadow-box-high focus-visible:ring-2 focus-visible:ring-stroke-ppg dark:bg-background-neutral-weaker dark:hover:bg-background-neutral"
{...linkProps}
>
<i className={`${icon} text-[16px]`} />
</span>
) : null}
</a>
{logo ? (
<Image
src={logo}
alt=""
width={48}
height={48}
className="size-12 object-contain brightness-0 opacity-45 transition-opacity group-hover:opacity-65 dark:opacity-55 dark:invert dark:group-hover:opacity-80"
unoptimized
/>
) : (
<span className="font-mono text-lg text-foreground-neutral-weak">Any AI agent</span>
)}
{icon ? (
<span
className="absolute right-1.75 top-1.75 text-foreground-neutral-weaker opacity-50 transition-opacity group-hover:opacity-100"
aria-hidden
>
<i className={`${icon} text-[16px]`} />
</span>
) : null}
</Tag>
</TooltipTrigger>
<TooltipContent>{copied ? "Copied!" : alt}</TooltipContent>
</Tooltip>
);
}
73 changes: 52 additions & 21 deletions apps/site/src/app/mcp/_components/mcp-agents-section.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { Button } from "@prisma/eclipse";
"use client";

import Script from "next/script";

import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
TooltipProvider,
} from "@prisma/eclipse";

import { AgentCard } from "./agent-card";

export type McpAgent = {
logo: string | null;
alt: string;
icon: string | null;
href: string;
href?: string;
copyText?: string;
};

export function McpAgentsSection({
docsHref,
agents,
}: {
docsHref: string;
agents: readonly McpAgent[];
}) {
export function McpAgentsSection({ agents }: { docsHref: string; agents: readonly McpAgent[] }) {
return (
<section className="px-4 py-12 md:px-0">
<div className="mx-auto flex max-w-[790px] flex-col items-center gap-12 text-center">
Expand All @@ -29,19 +35,44 @@ export function McpAgentsSection({
</p>
</div>

<div className="grid w-full max-w-[368px] grid-cols-2 justify-items-center gap-4 min-[400px]:gap-8 md:max-w-[790px] md:grid-cols-4 md:gap-8">
{agents.map(({ logo, alt, icon, href }) => (
<AgentCard key={alt} logo={logo} alt={alt} icon={icon} href={href} />
))}
</div>
<TooltipProvider>
<div className="grid w-full max-w-[368px] grid-cols-2 justify-items-center gap-4 min-[400px]:gap-8 md:max-w-[790px] md:grid-cols-4 md:gap-8">
{agents.map((agent) => (
<AgentCard key={agent.alt} {...agent} />
))}
</div>
</TooltipProvider>

<Button
href={docsHref}
variant={"link"}
className="text-sm font-semibold text-foreground-ppg underline"
>
Want to see your tool listed?
</Button>
<Dialog>
<DialogTrigger asChild>
<button
type="button"
className="cursor-pointer text-sm font-semibold text-foreground-ppg underline"
>
Want to see your tool listed?
</button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>Want to see your favorite AI tool listed on prisma.io/mcp?</DialogTitle>
</DialogHeader>
<iframe
data-tally-src="https://tally.so/r/wA1R1N"
title="Tool listing request"
width="100%"
height="600"
className="border-0"
/>
<Script
src="https://tally.so/widgets/embed.js"
onLoad={() => {
if (typeof window !== "undefined" && (window as any).Tally) {
(window as any).Tally.loadEmbeds();
}
}}
/>
</DialogContent>
</Dialog>
</div>
</section>
);
Expand Down
45 changes: 37 additions & 8 deletions apps/site/src/app/mcp/_components/mcp-bubble.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { useId, type ReactNode } from "react";
"use client";

import { useId, useState, type ReactNode } from "react";

import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@prisma/eclipse";

const bubbleShadow = "shadow-box-low dark:shadow-box-high";

Expand Down Expand Up @@ -46,7 +55,7 @@ const promptConfig: Record<McpPromptBubbleVariant, string> = {
};

const promptTextClass =
"inline-block w-full break-words text-pretty font-mono text-[14px] font-normal leading-5 text-background-ppg-reverse-strong dark:text-foreground-ppg-reverse-weak";
"inline-block w-full break-words text-pretty text-left font-mono text-[14px] font-normal leading-5 text-background-ppg-reverse-strong dark:text-foreground-ppg-reverse-weak";

function BubbleTail({ side }: { side: "left" | "right" }) {
const positionClass =
Expand Down Expand Up @@ -92,14 +101,34 @@ export function McpPromptBubble({
variant: McpPromptBubbleVariant;
children: ReactNode;
}) {
const [copied, setCopied] = useState(false);

const handleCopy = () => {
if (typeof children === "string") {
navigator.clipboard.writeText(children);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}
};

return (
<div className="relative w-full">
<div
className={`relative z-10 flex w-full items-center rounded-[12px] border bg-background-ppg text-background-ppg-reverse-strong transition-colors duration-300 ${promptConfig[variant]} [--mcp-bubble-fill:var(--color-background-ppg)] dark:[--mcp-bubble-fill:var(--color-background-ppg)] [--mcp-bubble-stroke:var(--color-stroke-ppg)] bg-(--mcp-bubble-fill) border-(--mcp-bubble-stroke)`}
>
<code className={promptTextClass}>{children}</code>
<BubbleTail side="right" />
</div>
<TooltipProvider>
<Tooltip open={copied || undefined}>
<TooltipTrigger asChild>
<button
type="button"
onClick={handleCopy}
aria-label={copied ? "Copied!" : "Copy prompt"}
className={`relative z-10 flex w-full cursor-pointer items-center rounded-[12px] border bg-background-ppg text-background-ppg-reverse-strong transition-colors duration-300 ${promptConfig[variant]} [--mcp-bubble-fill:var(--color-background-ppg)] dark:[--mcp-bubble-fill:var(--color-background-ppg)] [--mcp-bubble-stroke:var(--color-stroke-ppg)] bg-(--mcp-bubble-fill) border-(--mcp-bubble-stroke) hover:brightness-110`}
>
<code className={promptTextClass}>{children}</code>
<BubbleTail side="right" />
</button>
</TooltipTrigger>
<TooltipContent>{copied ? "Copied!" : "Copy prompt"}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
}
Expand Down
31 changes: 13 additions & 18 deletions apps/site/src/app/mcp/_components/mcp-cta-section.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Button } from "@prisma/eclipse";

export function McpCtaSection({ docsHref }: { docsHref: string }) {
export function McpCtaSection({
docsHref,
readDocsHref,
}: {
docsHref: string;
readDocsHref: string;
}) {
return (
<section className="bg-radial from-background-ppg/50 from-0% to-background-default to-70% px-4 py-12">
<div className="mx-auto max-w-360 rounded-2xl bg-[url('/illustrations/homepage/footer_grid.svg')] bg-cover bg-center px-4 py-12">
Expand All @@ -11,36 +17,25 @@ export function McpCtaSection({ docsHref }: { docsHref: string }) {
Start Building with AI
</h3>
<p className="max-w-[463px] text-base leading-6 text-foreground-neutral-weak">
Join thousands of developers, and agents, already using Prisma MCP
for faster, more intuitive database workflows.
Join thousands of developers, and agents, already using Prisma MCP for faster, more
intuitive database workflows.
</p>
</div>

<div className="flex flex-col items-center gap-4">
<div className="flex w-full flex-col items-center justify-center gap-4 min-[720px]:flex-row min-[720px]:gap-6">
<Button
href={docsHref}
variant={"ppg"}
size={"3xl"}
className="gap-3"
>
<Button href={docsHref} variant={"ppg"} size={"3xl"} className="gap-3">
Add MCP Server
<i
className="fa-regular fa-arrow-right shrink-0 text-[16px]"
aria-hidden
/>
<i className="fa-regular fa-arrow-right shrink-0 text-[16px]" aria-hidden />
</Button>
<Button
href={docsHref}
href={readDocsHref}
variant={"default-stronger"}
size={"3xl"}
className="gap-3"
>
Read Docs
<i
className="fa-regular fa-book-open shrink-0 text-[16px]"
aria-hidden
/>
<i className="fa-regular fa-book-open shrink-0 text-[16px]" aria-hidden />
</Button>
</div>

Expand Down
Loading
Loading