Add src/components/HostCard.tsx

This commit is contained in:
Zac Gaetano 2026-03-31 15:29:59 -04:00
parent 7a6fdde3ed
commit 4579878886

107
src/components/HostCard.tsx Normal file
View file

@ -0,0 +1,107 @@
import { useState } from "react";
import { Host, api } from "../api";
interface Props {
host: Host;
onRefresh: () => void;
}
export function HostCard({ host, onRefresh }: Props) {
const [connecting, setConnecting] = useState(false);
const [pairing, setPairing] = useState(false);
const [flash, setFlash] = useState<string | null>(null);
function showFlash(msg: string) {
setFlash(msg);
setTimeout(() => setFlash(null), 3000);
}
async function handleConnect() {
setConnecting(true);
try {
await api.connect(host.ip);
showFlash("Launching Moonlight…");
} catch (e: unknown) {
showFlash(e instanceof Error ? e.message : "Failed to connect");
} finally {
setConnecting(false);
}
}
async function handlePair() {
setPairing(true);
try {
await api.pair(host.ip);
showFlash("Pairing dialog opened…");
onRefresh();
} catch (e: unknown) {
showFlash(e instanceof Error ? e.message : "Pairing failed");
} finally {
setPairing(false);
}
}
const onlineDot = host.online ? "bg-green-400" : "bg-gray-500";
return (
<div
className={`
relative flex flex-col gap-3 p-5 rounded-xl border
bg-surface-raised
${host.online
? "border-surface-elevated hover:border-accent/60 cursor-pointer group"
: "border-surface-elevated opacity-60"
}
transition-all duration-200
`}
>
{/* Online indicator */}
<span className={`absolute top-4 right-4 w-2.5 h-2.5 rounded-full ${onlineDot}`} />
{/* Host icon placeholder */}
<div className="w-12 h-12 rounded-lg bg-surface-elevated flex items-center justify-center text-2xl">
🖥
</div>
<div>
<h3 className="font-semibold text-white truncate">{host.name}</h3>
<p className="text-xs text-gray-400 mt-0.5">
{host.ip}:{host.port}
</p>
</div>
{/* Flash message */}
{flash && (
<p className="text-xs text-accent animate-pulse">{flash}</p>
)}
{/* Actions */}
<div className="flex gap-2 mt-auto">
<button
onClick={handleConnect}
disabled={!host.online || connecting}
className="
flex-1 py-1.5 px-3 rounded-lg text-sm font-medium
bg-accent hover:bg-accent-hover
disabled:opacity-40 disabled:cursor-not-allowed
transition-colors duration-150
"
>
{connecting ? "Launching…" : "Connect"}
</button>
<button
onClick={handlePair}
disabled={!host.online || pairing}
className="
py-1.5 px-3 rounded-lg text-sm font-medium
bg-surface-elevated hover:bg-surface-elevated/80
disabled:opacity-40 disabled:cursor-not-allowed
transition-colors duration-150
"
>
{pairing ? "…" : "Pair"}
</button>
</div>
</div>
);
}