Add src/components/AddHostModal.tsx
This commit is contained in:
parent
69c7f281c0
commit
7a6fdde3ed
1 changed files with 81 additions and 0 deletions
81
src/components/AddHostModal.tsx
Normal file
81
src/components/AddHostModal.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { api } from "../api";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClose: () => void;
|
||||||
|
onAdded: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddHostModal({ onClose, onAdded }: Props) {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [ip, setIp] = useState("");
|
||||||
|
const [port, setPort] = useState("47984");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await api.addManualHost(name, ip, parseInt(port, 10) || 47984);
|
||||||
|
onAdded();
|
||||||
|
onClose();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to add host");
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50" onClick={onClose}>
|
||||||
|
<div
|
||||||
|
className="bg-surface-raised border border-surface-elevated rounded-2xl p-6 w-80 shadow-2xl"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h2 className="text-lg font-semibold mb-4">Add Host Manually</h2>
|
||||||
|
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
|
||||||
|
<input
|
||||||
|
className="bg-surface-elevated rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-accent"
|
||||||
|
placeholder="Display name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-surface-elevated rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-accent"
|
||||||
|
placeholder="IP or Tailscale hostname"
|
||||||
|
value={ip}
|
||||||
|
onChange={(e) => setIp(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-surface-elevated rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-accent"
|
||||||
|
placeholder="Port (default 47984)"
|
||||||
|
value={port}
|
||||||
|
onChange={(e) => setPort(e.target.value)}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
{error && <p className="text-xs text-red-400">{error}</p>}
|
||||||
|
<div className="flex gap-2 mt-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="flex-1 py-2 rounded-lg bg-surface-elevated text-sm"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={saving}
|
||||||
|
className="flex-1 py-2 rounded-lg bg-accent text-sm font-medium disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{saving ? "Adding…" : "Add"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue