import { useState } from "react"; import { Link } from "react-router-dom"; import { Play, Square, Hand, Users, ExternalLink, Terminal } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { PhaseBadge } from "@/components/PhaseBadge"; import { api, humanizeError } from "@/lib/api"; import { hostFor, type RuntimeConfig } from "@/lib/config"; import type { ServerInfo } from "@/lib/types"; interface Props { server: ServerInfo; cfg: RuntimeConfig; /** Called after a successful mutation so the parent can refetch. */ onChanged: () => void; } export function ServerCard({ server, cfg, onChanged }: Props) { const { t } = useTranslation("servers"); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); const host = hostFor(server.subdomain, cfg); async function act(kind: "wake" | "stop" | "claim", fn: () => Promise) { setBusy(kind); setError(null); try { await fn(); onChanged(); } catch (e) { setError(humanizeError(e)); } finally { setBusy(null); } } const running = server.phase === "Running"; // Starting and Stopping both have (or had) a pod in flight — neither should // offer "Wake". Grouping them keeps the footer on the Stop side until the // server has fully settled into Stopped/Failed. const transitioning = server.phase === "Starting" || server.phase === "Stopping"; return (
{server.displayName || server.name}
{host}
{running ? ( {server.players ?? 0} {server.maxPlayers ? ` / ${server.maxPlayers}` : ""} {t("online")} ) : ( — )}
{error &&

{error}

}
{server.owned && ( )} {server.claimable && !server.owned && ( )} {server.owned && (running || transitioning ? ( ) : ( ))}
); }