diff --git a/app/(app)/error.tsx b/app/(app)/error.tsx
new file mode 100644
index 0000000..8de43ca
--- /dev/null
+++ b/app/(app)/error.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { AlertTriangle, RotateCcw } from "lucide-react";
+import Link from "next/link";
+import { useEffect } from "react";
+import { Button, LINK_BUTTON_CLASS } from "@/components/ui/Button";
+
+// Without this file a failed render drops the user on Next.js's own error
+// screen — no navigation, no way back, and in production just "a client-side
+// exception occurred". `reset()` re-renders the segment, which is enough for
+// the common case of a transient Supabase timeout.
+export default function AppError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
+ useEffect(() => {
+ console.error("Route error:", error);
+ }, [error]);
+
+ return (
+
+
+
+
+
+
Diese Ansicht konnte nicht geladen werden
+
+ Die Daten wurden nicht verändert. Meist hilft ein erneuter Versuch; bleibt der Fehler, wenden Sie sich bitte an die
+ IT-Betreuung.
+
+
+ {/* The digest is the only handle on the server-side stack trace, which
+ is deliberately not sent to the browser in production. */}
+ {error.digest && (
+
+ Fehlerkennung: {error.digest}
+
+ )}
+
+
+
+ Zur Übersicht
+
+
+
+ );
+}
diff --git a/app/(app)/loading.tsx b/app/(app)/loading.tsx
new file mode 100644
index 0000000..e71724a
--- /dev/null
+++ b/app/(app)/loading.tsx
@@ -0,0 +1,30 @@
+// Every page in this group is server-rendered per request (they all read
+// from Supabase), so without this the browser sits on the previous page with
+// no feedback until the server answers — on the employee list, long enough
+// to look broken.
+export default function Loading() {
+ return (
+
+
+
+ {[240, 160, 160].map((w, i) => (
+
+ ))}
+
+
+ {Array.from({ length: 8 }, (_, i) => (
+
+
+
+
+
+
+
+
+
+ ))}
+
+ Daten werden geladen…
+
+ );
+}
diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
index e97ea68..58e84b7 100644
--- a/app/(auth)/login/page.tsx
+++ b/app/(auth)/login/page.tsx
@@ -1,4 +1,6 @@
import { login, logout } from "@/actions/auth";
+import { Button } from "@/components/ui/Button";
+import { CONTROL_CLASS } from "@/components/ui/Field";
// The query string is attacker-controlled, so the login page renders a message
// looked up by code rather than whatever text ?error= carries. Reflecting the
@@ -50,7 +52,7 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
type="email"
required
autoComplete="username"
- className="w-full rounded border border-border px-3 py-2 text-sm text-ink outline-none focus:border-brand-500"
+ className={CONTROL_CLASS}
/>
}
>
+ {/* A progress trail, not navigation: only completed steps are
+ reachable, so the rest are genuinely disabled rather than inert
+ buttons that silently swallow a click. */}