← back to Open Seo
src/client/features/dashboard/McpConnectCard.tsx
127 lines
import { Link } from "@tanstack/react-router";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { CopyButton } from "@/client/features/ai-mcp/SetupControls";
import { captureClientEvent } from "@/client/lib/posthog";
import type { DashboardActivation } from "@/server/features/dashboard/services/DashboardService";
import { dismissDashboardMcpCard } from "@/serverFunctions/dashboard";
function firstPrompts(domain: string | null): string[] {
const site = domain ?? "my site";
return [
`Review ${site}. Ideas for what keywords we could target? Use OpenSEO`,
"Research my competitors top pages and keywords and tell me what's working. Use OpenSEO",
];
}
/**
* The MCP activation card. Pitches the agent workflow and links to the
* AI & MCP page for setup; disappears for good after the org's first
* external tool call (or an explicit "I already connected").
*/
export function McpConnectCard({
projectId,
activation,
}: {
projectId: string;
activation: DashboardActivation;
}) {
const queryClient = useQueryClient();
const dismissMutation = useMutation({
mutationFn: () => dismissDashboardMcpCard({ data: { projectId } }),
onSuccess: () =>
void queryClient.invalidateQueries({
queryKey: ["dashboardActivation", projectId],
}),
});
if (activation.mcp.firstToolCallAt || activation.mcp.cardDismissedAt) {
return null;
}
const connected = activation.mcp.authorizedAt !== null;
return (
<div className="overflow-hidden rounded-xl border border-base-300 bg-base-100 shadow-sm">
<div className="flex items-center justify-between gap-4 px-5 py-4">
<h2 className="text-base font-semibold leading-tight">
Connect your AI agent
</h2>
<div className="flex items-center gap-2">
{connected ? (
<span className="badge badge-success badge-outline badge-sm">
Connected
</span>
) : null}
<button
type="button"
className="btn btn-ghost btn-xs text-base-content/60"
disabled={dismissMutation.isPending}
onClick={() => {
captureClientEvent("dashboard:mcp_already_connected");
dismissMutation.mutate();
}}
>
I already connected
</button>
</div>
</div>
<div className="space-y-3 border-t border-base-300 p-5">
{connected ? (
<>
<p className="text-sm text-base-content/70">
Your agent is connected. Try asking it:
</p>
<ul className="space-y-2">
{firstPrompts(activation.domain).map((prompt) => (
<li
key={prompt}
className="flex items-center justify-between gap-2 rounded-md border border-base-300 bg-base-200/50 px-3 py-2"
>
<span className="min-w-0 truncate text-xs text-base-content/80">
{prompt}
</span>
<CopyButton
value={prompt}
successMessage="Prompt copied"
iconOnly
onCopy={() =>
captureClientEvent("dashboard:mcp_prompt_copy")
}
/>
</li>
))}
</ul>
<p className="text-xs text-base-content/50">
Waiting for your first call — this card disappears once your agent
talks to OpenSEO.
</p>
</>
) : (
<>
<div className="space-y-2 text-sm text-base-content/70">
<p>
OpenSEO is designed to give your AI agent the data it needs to
build a great SEO strategy and help you execute it.
</p>
<p>
This way you aren’t limited on “AI credits”.
</p>
<p>
You can work with your agent to figure out what automations make
sense for you and it can help you write content too.
</p>
</div>
<Link
to="/ai"
className="link link-primary text-sm font-medium"
onClick={() => captureClientEvent("dashboard:mcp_setup_open")}
>
Set up in AI & MCP →
</Link>
</>
)}
</div>
</div>
);
}