import { useEffect, useState } from "react"; import { useParams, Link } from "react-router-dom"; import Layout from "@/components/Layout"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Github, ExternalLink, LayoutDashboard, Calendar, Cpu, Activity, } from "lucide-react"; const API_BASE = import.meta.env.VITE_API_BASE || ""; interface Project { id: string; title: string; description?: string | null; status?: string | null; technologies?: string[] | null; github_url?: string | null; live_url?: string | null; image_url?: string | null; engine?: string | null; priority?: string | null; progress?: number | null; created_at?: string | null; updated_at?: string | null; } interface Owner { id: string; username?: string | null; full_name?: string | null; avatar_url?: string | null; } const STATUS_COLORS: Record = { planning: "bg-slate-500/20 text-slate-300 border-slate-600", in_progress: "bg-blue-500/20 text-blue-300 border-blue-600", completed: "bg-green-500/20 text-green-300 border-green-600", on_hold: "bg-yellow-500/20 text-yellow-300 border-yellow-600", }; const STATUS_LABELS: Record = { planning: "Planning", in_progress: "In Progress", completed: "Completed", on_hold: "On Hold", }; const formatDate = (v?: string | null) => { if (!v) return null; const d = new Date(v); if (isNaN(d.getTime())) return null; return d.toLocaleDateString(undefined, { dateStyle: "medium" }); }; export default function ProjectDetail() { const { projectId } = useParams<{ projectId: string }>(); const [project, setProject] = useState(null); const [owner, setOwner] = useState(null); const [loading, setLoading] = useState(true); const [notFound, setNotFound] = useState(false); useEffect(() => { if (!projectId) return; setLoading(true); fetch(`${API_BASE}/api/projects/${projectId}`) .then((r) => { if (r.status === 404) { setNotFound(true); return null; } return r.json(); }) .then((body) => { if (!body) return; setProject(body.project); setOwner(body.owner); }) .catch(() => setNotFound(true)) .finally(() => setLoading(false)); }, [projectId]); if (loading) { return (
Loading project…
); } if (notFound || !project) { return (

Project not found.

); } const statusKey = project.status ?? "planning"; const statusClass = STATUS_COLORS[statusKey] ?? STATUS_COLORS.planning; const statusLabel = STATUS_LABELS[statusKey] ?? statusKey; const ownerSlug = owner?.username ?? owner?.id; const ownerName = owner?.full_name || owner?.username || "Unknown"; const ownerInitials = ownerName .split(" ") .map((w) => w[0]) .join("") .slice(0, 2) .toUpperCase(); return (
{/* Header */}
{statusLabel} {project.priority && ( {project.priority} priority )}

{project.title}

{project.description && (

{project.description}

)}
{/* Action buttons */}
{project.github_url && ( )} {project.live_url && ( )}
{/* Meta card */} Details {owner && (
{ownerInitials}

Owner

{ownerSlug ? ( {ownerName} ) : ( {ownerName} )}
)} {project.engine && (

Engine

{project.engine}

)} {typeof project.progress === "number" && (
Progress — {project.progress}%
)} {(project.created_at || project.updated_at) && (
{project.created_at && (

Created {formatDate(project.created_at)}

)} {project.updated_at && (

Updated {formatDate(project.updated_at)}

)}
)} {/* Technologies */} {project.technologies && project.technologies.length > 0 && ( Technologies
{project.technologies.map((tech) => ( {tech} ))}
)}
); }