=== NEW === - studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan) - docs/FACTORY-V2.md — Factory v2 architecture doc - docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report === UPDATED SERVERS === - fieldedge: Added jobs-tools, UI build script, main entry update - lightspeed: Updated main + server entry points - squarespace: Added collection-browser + page-manager apps - toast: Added main + server entry points === INFRA === - infra/command-center/state.json — Updated pipeline state - infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
237 lines
7.7 KiB
TypeScript
237 lines
7.7 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import {
|
||
Check,
|
||
Copy,
|
||
ExternalLink,
|
||
ChevronDown,
|
||
ChevronUp,
|
||
LayoutDashboard,
|
||
Globe,
|
||
Rocket,
|
||
} from 'lucide-react';
|
||
import { cn } from '@/lib/utils';
|
||
import type { DeployResult } from '@mcpengine/ai-pipeline/types';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Props
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export interface DeploySuccessProps {
|
||
result: DeployResult;
|
||
serverName?: string;
|
||
onDashboard?: () => void;
|
||
onViewServer?: () => void;
|
||
onDeployAnother?: () => void;
|
||
className?: string;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Component
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export function DeploySuccess({
|
||
result,
|
||
serverName = 'MCP Server',
|
||
onDashboard,
|
||
onViewServer,
|
||
onDeployAnother,
|
||
className,
|
||
}: DeploySuccessProps) {
|
||
const [copied, setCopied] = useState(false);
|
||
const [showConfig, setShowConfig] = useState(false);
|
||
const [confettiFired, setConfettiFired] = useState(false);
|
||
|
||
// Fire confetti on mount
|
||
useEffect(() => {
|
||
if (confettiFired) return;
|
||
setConfettiFired(true);
|
||
|
||
// Dynamic import canvas-confetti (client-side only)
|
||
import('canvas-confetti')
|
||
.then((mod) => {
|
||
const confetti = mod.default;
|
||
// Burst 1 — center
|
||
confetti({
|
||
particleCount: 100,
|
||
spread: 70,
|
||
origin: { y: 0.6 },
|
||
colors: ['#818cf8', '#34d399', '#f59e0b', '#f472b6'],
|
||
});
|
||
// Burst 2 — left
|
||
setTimeout(() => {
|
||
confetti({
|
||
particleCount: 50,
|
||
angle: 60,
|
||
spread: 55,
|
||
origin: { x: 0, y: 0.65 },
|
||
colors: ['#818cf8', '#34d399'],
|
||
});
|
||
}, 200);
|
||
// Burst 3 — right
|
||
setTimeout(() => {
|
||
confetti({
|
||
particleCount: 50,
|
||
angle: 120,
|
||
spread: 55,
|
||
origin: { x: 1, y: 0.65 },
|
||
colors: ['#f59e0b', '#f472b6'],
|
||
});
|
||
}, 400);
|
||
})
|
||
.catch(() => {
|
||
// canvas-confetti not installed — no big deal
|
||
console.log('canvas-confetti not available');
|
||
});
|
||
}, [confettiFired]);
|
||
|
||
const copyUrl = async () => {
|
||
if (result.url) {
|
||
await navigator.clipboard.writeText(result.url);
|
||
setCopied(true);
|
||
setTimeout(() => setCopied(false), 2000);
|
||
}
|
||
};
|
||
|
||
const slug = result.url
|
||
?.replace(/^https?:\/\//, '')
|
||
.replace(/\.mcpengine\.run.*/, '') ?? 'my-server';
|
||
|
||
const claudeConfig = JSON.stringify(
|
||
{
|
||
mcpServers: {
|
||
[slug]: {
|
||
url: result.endpoint ?? result.url,
|
||
},
|
||
},
|
||
},
|
||
null,
|
||
2,
|
||
);
|
||
|
||
return (
|
||
<div className={cn('flex flex-col items-center text-center', className)}>
|
||
{/* Checkmark with glow */}
|
||
<div className="relative mb-6">
|
||
<div className="absolute inset-0 animate-ping rounded-full bg-emerald-500/20" />
|
||
<div className="absolute -inset-4 rounded-full bg-emerald-500/10 blur-xl" />
|
||
<div className="relative flex h-20 w-20 items-center justify-center rounded-full border-2 border-emerald-500 bg-emerald-500/20 shadow-2xl shadow-emerald-500/30">
|
||
<Check className="h-10 w-10 text-emerald-400" strokeWidth={3} />
|
||
</div>
|
||
</div>
|
||
|
||
{/* Heading */}
|
||
<h2 className="mb-2 text-2xl font-bold text-white">
|
||
Deployment Successful! 🚀
|
||
</h2>
|
||
<p className="mb-6 text-gray-400">
|
||
<span className="font-semibold text-white">{serverName}</span> is now
|
||
live and ready to connect
|
||
</p>
|
||
|
||
{/* URL display with copy */}
|
||
{result.url && (
|
||
<div className="mb-8 w-full max-w-lg">
|
||
<div className="group relative flex items-center gap-2 rounded-xl border border-emerald-500/30 bg-emerald-500/5 px-4 py-3 transition-all hover:border-emerald-500/50 hover:bg-emerald-500/10">
|
||
<Globe className="h-4 w-4 shrink-0 text-emerald-400" />
|
||
<span className="truncate font-mono text-sm text-emerald-300 animate-pulse">
|
||
{result.url}
|
||
</span>
|
||
<button
|
||
onClick={copyUrl}
|
||
className={cn(
|
||
'ml-auto flex shrink-0 items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium transition-all',
|
||
copied
|
||
? 'bg-emerald-500 text-white'
|
||
: 'bg-gray-800 text-gray-300 hover:bg-gray-700',
|
||
)}
|
||
>
|
||
{copied ? (
|
||
<>
|
||
<Check className="h-3.5 w-3.5" />
|
||
Copied!
|
||
</>
|
||
) : (
|
||
<>
|
||
<Copy className="h-3.5 w-3.5" />
|
||
Copy
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Claude Desktop config */}
|
||
<div className="mb-8 w-full max-w-lg">
|
||
<button
|
||
onClick={() => setShowConfig(!showConfig)}
|
||
className="flex w-full items-center justify-between rounded-xl border border-gray-700 bg-gray-800/50 px-4 py-3 text-sm font-medium text-gray-300 transition-all hover:border-gray-600 hover:bg-gray-800"
|
||
>
|
||
<span className="flex items-center gap-2">
|
||
<span className="text-lg">🖥️</span>
|
||
Add to Claude Desktop
|
||
</span>
|
||
{showConfig ? (
|
||
<ChevronUp className="h-4 w-4 text-gray-500" />
|
||
) : (
|
||
<ChevronDown className="h-4 w-4 text-gray-500" />
|
||
)}
|
||
</button>
|
||
|
||
{showConfig && (
|
||
<div className="mt-2 overflow-hidden rounded-xl border border-gray-700 bg-gray-900">
|
||
<div className="flex items-center justify-between border-b border-gray-800 px-4 py-2">
|
||
<span className="text-xs text-gray-500">
|
||
claude_desktop_config.json
|
||
</span>
|
||
<button
|
||
onClick={async () => {
|
||
await navigator.clipboard.writeText(claudeConfig);
|
||
}}
|
||
className="flex items-center gap-1 text-xs text-gray-400 hover:text-white"
|
||
>
|
||
<Copy className="h-3 w-3" />
|
||
Copy
|
||
</button>
|
||
</div>
|
||
<pre className="overflow-x-auto p-4 text-left font-mono text-xs leading-relaxed text-indigo-300">
|
||
{claudeConfig}
|
||
</pre>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Action buttons */}
|
||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||
<button
|
||
onClick={onDashboard}
|
||
className="flex items-center gap-2 rounded-xl border border-gray-700 bg-gray-800 px-5 py-2.5 text-sm font-medium text-gray-300 transition-all hover:border-gray-600 hover:bg-gray-700"
|
||
>
|
||
<LayoutDashboard className="h-4 w-4" />
|
||
Open Dashboard
|
||
</button>
|
||
|
||
{result.url && (
|
||
<button
|
||
onClick={onViewServer}
|
||
className="flex items-center gap-2 rounded-xl border border-indigo-500/30 bg-indigo-500/10 px-5 py-2.5 text-sm font-medium text-indigo-300 transition-all hover:border-indigo-500/50 hover:bg-indigo-500/20"
|
||
>
|
||
<ExternalLink className="h-4 w-4" />
|
||
View Server
|
||
</button>
|
||
)}
|
||
|
||
<button
|
||
onClick={onDeployAnother}
|
||
className="flex items-center gap-2 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 px-5 py-2.5 text-sm font-medium text-white shadow-lg shadow-indigo-500/25 transition-all hover:shadow-indigo-500/40"
|
||
>
|
||
<Rocket className="h-4 w-4" />
|
||
Deploy Another
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|