-
-
setName(e.target.value)} className="mt-1 w-full rounded border px-3 py-2" />
+
+
+ ← Zurück
+
+ Kontakte
+ Mindestens E-Mail oder Telegram pro Kontakt.
+
-
- {contacts.map((c) => (
- -
- {c.name}
- {c.email && · {c.email}}
- {c.telegramChatId && · Telegram {c.telegramChatId}}
-
- ))}
-
-
+
+ {contacts.length === 0 ? (
+ Noch keine Kontakte angelegt.
+ ) : (
+
+ {contacts.map((c) => (
+ -
+ {c.name}
+ {c.email && · {c.email}}
+ {c.telegramChatId && · Telegram {c.telegramChatId}}
+
+ ))}
+
+ )}
+
+
);
}
diff --git a/app/globals.css b/app/globals.css
new file mode 100644
index 0000000..63b96ab
--- /dev/null
+++ b/app/globals.css
@@ -0,0 +1,41 @@
+@import "tailwindcss";
+
+@theme {
+ --font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
+
+ --color-brand-50: #eef5f0;
+ --color-brand-100: #dcebe1;
+ --color-brand-600: #397b58;
+ --color-brand-700: #306e4e;
+ --color-brand-800: #285a41;
+
+ --color-ink: #202823;
+ --color-muted: #5f6d63;
+ --color-line: #dde5de;
+ --color-wash: #f2f5f2;
+ --color-danger: #9c3838;
+
+ --radius-card: 16px;
+ --radius-field: 10px;
+}
+
+body {
+ background-color: var(--color-wash);
+ color: var(--color-ink);
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+}
+
+:where(a, button, input, select, textarea):focus-visible {
+ outline: 3px solid rgb(57 123 88 / 0.35);
+ outline-offset: 2px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ transition: none !important;
+ animation: none !important;
+ }
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 907f255..0183c17 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,10 +1,17 @@
import type { ReactNode } from "react";
+import { Geist } from "next/font/google";
+import "./globals.css";
-export const metadata = { title: "Gutscheintracker" };
+const geist = Geist({ subsets: ["latin"], display: "swap" });
+
+export const metadata = {
+ title: "Gutscheintracker",
+ description: "Gutscheine verwalten und rechtzeitig an das Ablaufdatum erinnern.",
+};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
-
+
{children}
);
diff --git a/app/login/page.tsx b/app/login/page.tsx
index 995f2bd..3a17c66 100644
--- a/app/login/page.tsx
+++ b/app/login/page.tsx
@@ -33,27 +33,73 @@ export default function LoginPage() {
}
return (
-
- Anmelden
-
+
+
+
+
+ G
+
+ Gutscheintracker
+
+
+
+ Willkommen zurück
+
+
+ Melde dich mit eurem gemeinsamen Haushaltszugang an.
+
+
+
+ Privater Zugang für euren Haushalt
+
+
+
);
}
diff --git a/app/page.tsx b/app/page.tsx
index e37eb6d..7720bb6 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,7 +1,8 @@
import Link from "next/link";
import { Suspense } from "react";
+import Shell from "./components/shell";
import { getDb } from "@/lib/db";
-import { getVoucherStatus } from "@/lib/voucher-status";
+import { getVoucherStatus, type VoucherStatus } from "@/lib/voucher-status";
import { formatEuro, isExpiringSoon } from "@/lib/vouchers";
const FILTERS = [
@@ -12,13 +13,25 @@ const FILTERS = [
{ key: "eingeloest", label: "Eingelöst" },
] as const;
+const STATUS_STYLE: Record = {
+ aktiv: "bg-brand-50 text-brand-800",
+ abgelaufen: "bg-danger/10 text-danger",
+ eingeloest: "bg-wash text-muted",
+};
+
+function statusLabel(s: VoucherStatus) {
+ if (s === "aktiv") return "Aktiv";
+ if (s === "abgelaufen") return "Abgelaufen";
+ return "Eingelöst";
+}
+
function Skeleton() {
return (
-