feat(admin): guests CRUD UI with copy-link

This commit is contained in:
belisards
2026-05-03 16:32:19 -03:00
parent 8d9e7c0709
commit 0832c0f9e5
2 changed files with 103 additions and 0 deletions

97
app/admin/guests/page.tsx Normal file
View File

@@ -0,0 +1,97 @@
'use client';
import { useEffect, useState } from 'react';
import AdminGuard from '@/components/admin-guard';
import { guestsApi, type Guest } from '@/lib/api';
export default function AdminGuestsPage() {
return (
<AdminGuard>
<GuestsManager />
</AdminGuard>
);
}
function GuestsManager() {
const [guests, setGuests] = useState<Guest[]>([]);
const [name, setName] = useState('');
const [busy, setBusy] = useState(false);
const [err, setErr] = useState('');
const load = async () => setGuests(await guestsApi.list());
useEffect(() => { load(); }, []);
const linkFor = (g: Guest) => {
const base = typeof window === 'undefined' ? '' : window.location.origin;
return `${base}/?usr=${g.id}`;
};
const onCreate = async (e: React.FormEvent) => {
e.preventDefault();
setErr('');
if (!name.trim()) return;
setBusy(true);
try {
await guestsApi.create(name.trim());
setName('');
await load();
} catch (e: any) {
setErr(e.message || 'Erro ao criar');
} finally {
setBusy(false);
}
};
const onRename = async (g: Guest) => {
const next = prompt('Novo nome', g.name);
if (!next || next.trim() === g.name) return;
await guestsApi.rename(g.id, next.trim());
await load();
};
const onDelete = async (g: Guest) => {
if (!confirm(`Excluir o convidado ${g.name}? As reservas dele(a) ficarão sem dono.`)) return;
await guestsApi.delete(g.id);
await load();
};
const onCopy = async (g: Guest) => {
await navigator.clipboard.writeText(linkFor(g));
};
return (
<div className="max-w-3xl mx-auto p-6 space-y-6">
<h1 className="text-2xl font-bold">Convidados</h1>
<form onSubmit={onCreate} className="flex gap-2">
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Nome do convidado"
className="flex-1 border rounded px-3 py-2"
/>
<button disabled={busy} className="bg-indigo-600 text-white rounded px-4 py-2 disabled:opacity-50">
{busy ? 'Criando…' : 'Criar link'}
</button>
</form>
{err && <div className="text-red-600 text-sm">{err}</div>}
<ul className="divide-y border rounded">
{guests.map((g) => (
<li key={g.id} className="p-3 flex items-center justify-between gap-3">
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{g.name}</div>
<div className="text-xs text-gray-500 truncate">{linkFor(g)}</div>
</div>
<div className="flex gap-2 shrink-0">
<button onClick={() => onCopy(g)} className="text-sm border rounded px-2 py-1">Copiar link</button>
<button onClick={() => onRename(g)} className="text-sm border rounded px-2 py-1">Renomear</button>
<button onClick={() => onDelete(g)} className="text-sm border rounded px-2 py-1 text-red-600">Excluir</button>
</div>
</li>
))}
{guests.length === 0 && <li className="p-3 text-gray-500 text-sm">Nenhum convidado ainda</li>}
</ul>
</div>
);
}