feat(admin): guests CRUD UI with copy-link
This commit is contained in:
97
app/admin/guests/page.tsx
Normal file
97
app/admin/guests/page.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -154,6 +154,12 @@ function AdminPageContent() {
|
|||||||
>
|
>
|
||||||
View Public Site
|
View Public Site
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/admin/guests"
|
||||||
|
className="inline-flex items-center px-6 py-3 border-2 border-indigo-600 dark:border-indigo-500 text-base font-semibold rounded-lg text-indigo-600 dark:text-indigo-400 bg-white dark:bg-gray-800 hover:bg-indigo-50 dark:hover:bg-gray-700 transition-all"
|
||||||
|
>
|
||||||
|
Convidados
|
||||||
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className="inline-flex items-center px-6 py-3 border-2 border-red-600 dark:border-red-500 text-base font-semibold rounded-lg text-red-600 dark:text-red-400 bg-white dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-900/20 transition-all cursor-pointer"
|
className="inline-flex items-center px-6 py-3 border-2 border-red-600 dark:border-red-500 text-base font-semibold rounded-lg text-red-600 dark:text-red-400 bg-white dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-900/20 transition-all cursor-pointer"
|
||||||
|
|||||||
Reference in New Issue
Block a user