Login
Die kanonische Anmeldeseite für interne ZVV-Apps: 3-Column-Layout mit Login-Card in der Mitte, zwei Maturitäts-Stufen (Magic-Link zuerst, Microsoft später) und ein Bestätigungs-Zustand, der das Formular ersetzt. Hier im Rahmen vorgeführt — das Vorbild selbst steht ohne App-Shell unter /login.
Was das Pattern löst
Jede ZVV-App braucht eine Anmeldeseite, und jede baute sie bisher neu — mit eigener Anordnung, eigenem Wortlaut und eigener Antwort auf die Frage, was passiert, solange Azure AD noch nicht bereitsteht. Das Pattern beantwortet diese Frage einmal: Stufe 1 stellt den Magic-Link nach vorne und zeigt den Microsoft-Knopf als sichtbaren Platzhalter, damit niemand denkt, SSO sei vergessen worden. Stufe 2 dreht das Verhältnis um, sobald das Onboarding durch ist — dieselbe Seite, dieselben Bausteine, nur eine andere Rangfolge.
Die Feature-Cards links und rechts sind kein Schmuck: sie erklären der Nutzerin vor der Anmeldung, wofür die App zuständig ist. Unter lg klappen sie als 2×4-Raster unter die Karte, statt zu verschwinden.
Vorschau
Der Umschalter gibt Stufe und Zustand vor. „Vollbild öffnen“ zeigt dieselbe Komponente ohne App-Shell in einem neuen Tab — der Showcase-Rahmen bleibt hier stehen, statt die Nutzerin aus der Navigation zu reissen.
ZVV Atlas
Foundation · Patterns · Living Docs
Azure AD / Entra ID · SSO — folgt nach Kanton-Onboarding
Was die Plattform leistet
Internes Tool für ZVV-Mitarbeitende
Ruhezustand. Eingabe und Mock-Absenden laufen hier genauso wie auf der Vollbild-Seite — dieselbe Komponente, kein nachgebautes Verhalten.
Verwendung
// Eine Quelle, zwei Auftritte — das Markup existiert genau einmal.
import { LoginView } from '@/components/showcase/login-pattern/login-view'
// app/login/page.tsx — Vollbild, ausserhalb der (showcase)-Gruppe.
// Die Route bringt Huelle, Stufen-Umschalter und <main id="main"> selbst mit.
<main id="main" tabIndex={-1} className="flex flex-1 flex-col">
<LoginView stage={stage} />
</main>
// Vorschau im Showcase-Rahmen: Zustand vorgegeben, Logo ohne priority
// (nicht das LCP-Element), Ueberschrift eine Stufe tiefer — das h1 der
// Seite gehoert dem SectionHeader.
<div className="relative isolate overflow-hidden rounded-xl border">
<LoginView stage={2} demo="gesendet" priorityLogo={false}
headingLevel="h2" className="min-h-[46rem]" />
</div>- –
demofriert den gezeigten Zustand ein; ohne den Prop verhält sich die Komponente exakt wie im Vollbild, samt Mock-Timern. Der Zustand wird während des Renderns abgeleitet und nie in den State gespiegelt — sonst gäbe es zwei Wahrheiten. - –
headingLevelexistiert, weil die Login-Card einh1trägt und diese Seite bereits eines hat. Zweih1wären eine A11y-Regression, ein zweites<main id="main">ebenso — deshalb bleibt dasmainbei der Route statt in der Komponente. - – Die Komponente ist bewusst motion-frei:
LazyMotionumschliesst nur die Showcase-Gruppe, das Vollbild liegt ausserhalb. Einm.*hier würde /login zur Laufzeit brechen.
Was die Vorschau nicht zeigt
Der Umschalter kennt nur die Zustände, die der Pattern-Code wirklich hat. Drei Dinge aus der Spec fehlen der Referenz-Umsetzung — sie stehen hier, statt in der Vorschau nachgestellt zu werden:
- – Fehlerzustand (Spec §8: Inline-Banner unter dem Magic-Link-Feld, bei unbekanntem Konto mit CTA). Die Referenz-Seite mockt den Versand und kann nicht scheitern; ein Fehler-Markup, das nur die Vorschau kennt, wäre genau die Drift, die Atlas abstellen soll.
- – Server-Action mit
useActionState(Spec §4). Hier läuft einsetTimeout, weil der Showcase keinen Auth-Backend hat. Für den echten Ablauf siehe Auth Flow. - – Einlauf-Animationen (Spec §2:
fade-in-upgestaffelt, schwebende Blobs). Die Feature-Cards tragen die Verzögerung alsanimationDelay, die passenden@keyframesgibt es im Showcase-CSS aber nicht — die Angabe läuft ins Leere.