Add src/components/HostCard.tsx
This commit is contained in:
parent
7a6fdde3ed
commit
4579878886
1 changed files with 107 additions and 0 deletions
107
src/components/HostCard.tsx
Normal file
107
src/components/HostCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue