Files
gutscheintracker/app/contacts/page.tsx
T

121 lines
4.6 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import Shell from "../components/shell";
import { validateContact } from "@/lib/contacts";
type Contact = { id: number; name: string; email: string | null; telegramChatId: string | null };
const inputClass =
"mt-1.5 h-11 w-full rounded-field border border-line bg-white px-3.5 text-sm text-ink placeholder:text-muted/60";
export default function ContactsPage() {
const [contacts, setContacts] = useState<Contact[]>([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [telegramChatId, setTelegramChatId] = useState("");
const [errors, setErrors] = useState<string[]>([]);
async function load() {
const res = await fetch("/api/contacts");
if (res.ok) setContacts(await res.json());
}
useEffect(() => { load(); }, []);
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
const input = { name, email: email || null, telegramChatId: telegramChatId || null };
const errs = validateContact(input);
setErrors(errs);
if (errs.length) return;
const res = await fetch("/api/contacts", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setErrors(data.errors ?? ["speichern"]);
return;
}
setName(""); setEmail(""); setTelegramChatId(""); setErrors([]);
load();
}
return (
<Shell>
<Link href="/" className="text-sm font-medium text-muted hover:text-ink">
← Zurück
</Link>
<h1 className="mt-2 text-2xl font-semibold tracking-tight">Kontakte</h1>
<p className="mt-1 text-sm text-muted">Mindestens E-Mail oder Telegram pro Kontakt.</p>
<form onSubmit={onSubmit} className="mt-5 rounded-card border border-line bg-white p-5 sm:p-6" noValidate>
<div className="grid gap-4 sm:grid-cols-2">
<div className="sm:col-span-2">
<label htmlFor="name" className="block text-[13px] font-semibold">Name</label>
<input
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
autoComplete="off"
placeholder="z. B. Mama"
aria-invalid={errors.includes("name")}
className={`${inputClass} ${errors.includes("name") ? "border-danger" : ""}`}
/>
</div>
<div>
<label htmlFor="email" className="block text-[13px] font-semibold">E-Mail</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="email"
placeholder="name@beispiel.de"
aria-invalid={errors.includes("email")}
className={`${inputClass} ${errors.includes("email") ? "border-danger" : ""}`}
/>
</div>
<div>
<label htmlFor="telegramChatId" className="block text-[13px] font-semibold">Telegram-Chat-ID</label>
<input
id="telegramChatId"
value={telegramChatId}
onChange={(e) => setTelegramChatId(e.target.value)}
autoComplete="off"
placeholder="z. B. 123456789"
className={inputClass}
/>
</div>
</div>
{errors.length > 0 && (
<p role="alert" className="mt-4 rounded-field bg-danger/10 px-3.5 py-2.5 text-sm font-medium text-danger">
Bitte prüfe die Eingaben: {errors.join(", ")}
</p>
)}
<button
type="submit"
className="mt-5 h-12 w-full rounded-field bg-brand-600 text-sm font-semibold text-white transition-colors hover:bg-brand-700 active:scale-[0.99] sm:w-auto sm:px-8"
>
Hinzufügen
</button>
</form>
<section aria-label="Gespeicherte Kontakte" className="mt-5 rounded-card border border-line bg-white px-5 py-2">
{contacts.length === 0 ? (
<p className="py-3 text-sm text-muted">Noch keine Kontakte angelegt.</p>
) : (
<ul className="divide-y divide-line">
{contacts.map((c) => (
<li key={c.id} className="py-3 text-sm">
<span className="font-semibold">{c.name}</span>
{c.email && <span className="text-muted"> · {c.email}</span>}
{c.telegramChatId && <span className="text-muted"> · Telegram {c.telegramChatId}</span>}
</li>
))}
</ul>
)}
</section>
</Shell>
);
}