"use client"; import { useState, useEffect, useCallback } from "react"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { createApiClient, type Project, type SnapshotMeta } from "@/lib/api"; function formatDate(iso: string) { return new Date(iso).toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit", }); } function formatRelative(iso: string) { const diff = Date.now() - new Date(iso).getTime(); const minutes = Math.floor(diff / 60_000); if (minutes < 1) return "just now"; if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } const ENV_BADGE: Record = { production: "bg-red-500/10 text-red-400 border-red-500/20", staging: "bg-yellow-500/10 text-yellow-400 border-yellow-500/20", development: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20", }; // ── Snapshot Row ──────────────────────────────────────────────────────────── function SnapshotRow({ snap, isLatest, }: { snap: SnapshotMeta; isLatest: boolean; }) { return (
{/* Version bubble */}
v{snap.version}
{/* connector line — handled by parent spacing */}
{isLatest && ( latest )}

{snap.message ?? No message}

{snap.pushed_by ? ( <> {snap.pushed_by.split("-")[0]}… {" · "} ) : null} {formatRelative(snap.created_at)}

{formatDate(snap.created_at)}
); } // ── CLI Reference Panel ───────────────────────────────────────────────────── function CliPanel({ projectId }: { projectId: string }) { const [copied, setCopied] = useState(null); function copy(text: string, key: string) { navigator.clipboard.writeText(text).then(() => { setCopied(key); setTimeout(() => setCopied(null), 1500); }); } const commands = [ { key: "push", cmd: "envsync push -m \"describe changes\"", desc: "Encrypt & push local .env" }, { key: "pull", cmd: "envsync pull", desc: "Pull latest snapshot" }, { key: "diff", cmd: "envsync diff", desc: "Compare local vs remote" }, { key: "log", cmd: "envsync log", desc: "View snapshot history" }, ]; return (

CLI Quick Reference

Project ID: {projectId}

{commands.map(({ key, cmd, desc }) => (
{cmd}

{desc}

))}
); } // ── Page ─────────────────────────────────────────────────────────────────── export default function ProjectDetailPage() { const { id } = useParams<{ id: string }>(); const { data: session, status } = useSession(); const [project, setProject] = useState(null); const [snapshots, setSnapshots] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchData = useCallback(async () => { if (!session?.accessToken || !id) return; setLoading(true); setError(null); try { const api = createApiClient(session.accessToken); const [proj, snaps] = await Promise.all([ api.getProject(id), api.listSnapshots(id, 50), ]); setProject(proj); setSnapshots(snaps); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load project"); } finally { setLoading(false); } }, [session?.accessToken, id]); useEffect(() => { if (status === "authenticated") fetchData(); }, [status, fetchData]); if (status === "loading" || loading) { return (
); } if (error) { return (
{error}
); } if (!project) return null; const envClass = ENV_BADGE[project.environment.toLowerCase()] ?? "bg-zinc-800 text-zinc-400 border-zinc-700"; return (
{/* Breadcrumb */}
Projects / {project.name}
{/* Project header */}

{project.name}

{project.environment}

{snapshots.length} snapshot{snapshots.length !== 1 ? "s" : ""} · Created {formatDate(project.created_at)}

{/* Snapshot history */}

Snapshot History

{snapshots.length} total
{snapshots.length === 0 ? (

No snapshots yet.

Run{" "} envsync push{" "} to create the first one.

) : (
{snapshots.map((snap, i) => ( ))}
)}
{/* Rollback note */} {snapshots.length > 1 && (

To roll back:{" "} envsync rollback --version <N>

)}
{/* Sidebar */}
{/* Stats */}

Stats

); } function Stat({ label, value }: { label: string; value: string }) { return (
{label} {value}
); } function CopyIcon() { return ( ); } function CheckIcon() { return ( ); } function InfoIcon() { return ( ); }