Skip to content
Open
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
43 changes: 36 additions & 7 deletions app/console/deployments/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { Rocket, ExternalLink, Trash2, RefreshCw, Server, ScrollText, RotateCcw, AlertTriangle } from "lucide-react";
import { Rocket, ExternalLink, Trash2, RefreshCw, Server, ScrollText, RotateCcw, AlertTriangle, Copy, Check } from "lucide-react";

interface DeploymentSummary {
sandboxId: string;
Expand Down Expand Up @@ -33,6 +33,33 @@ const STATUS_DOT: Record<DeploymentSummary["status"], string> = {
failed: "bg-red-500",
};

// Isolated Copy Component to maintain separate success timer states inside lists (Fixes #49)
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);

const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent row click events
try {
await navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Fallback if system permission is denied
}
};

return (
<button
type="button"
onClick={handleCopy}
className="p-1 rounded bg-gray-100 hover:bg-gray-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-gray-500 dark:text-zinc-400 transition-colors shrink-0"
title="Copy URL to clipboard"
>
{copied ? <Check className="w-3 h-3 text-green-600 dark:text-green-400" /> : <Copy className="w-3 h-3" />}
Comment thread
prakshithamalla-art marked this conversation as resolved.
</button>
);
}

export default function Page() {
const router = useRouter();
const [deployments, setDeployments] = useState<DeploymentSummary[]>([]);
Expand Down Expand Up @@ -91,7 +118,6 @@ export default function Page() {
const res = await fetch(`/api/deploy/${sandboxId}`, { method: "POST" });
const data = await res.json();
if (data.ok && data.deployment?.sandboxId) {
// Navigate to the new deployment's log page
router.push(`/console/deployments/${data.deployment.sandboxId}`);
} else {
alert(data.error ?? "Redeploy failed");
Expand Down Expand Up @@ -185,17 +211,20 @@ export default function Page() {
<span>{new Date(d.startedAt).toLocaleString()}</span>
<span>{d.logCount} log lines</span>
</div>
{d.publicUrl && d.status !== "live" && (
<div className="flex items-center gap-1.5 mt-1.5">
<span className="text-xs text-gray-400 dark:text-zinc-500">Preview URL:</span>

{/* Public Preview Block with Copy to Clipboard Integration */}
{d.publicUrl && (
<div className="flex items-center gap-2 mt-2 bg-gray-50 dark:bg-zinc-800/40 border border-gray-100 dark:border-zinc-800/60 rounded-lg px-2.5 py-1.5 w-fit max-w-full">
<span className="text-xs text-gray-400 dark:text-zinc-500 font-medium whitespace-nowrap">Preview URL:</span>
<a
href={d.publicUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-mono text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 truncate max-w-xs"
className="text-xs font-mono text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 truncate max-w-xs md:max-w-md underline"
>
{d.publicUrl}
</a>
<CopyButton text={d.publicUrl} />
</div>
)}
</div>
Expand Down Expand Up @@ -246,4 +275,4 @@ export default function Page() {
)}
</div>
);
}
}
Loading