Zum Inhalt springen
ZVV
Login

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.

Vorführung — keine echte Anmeldung
Stufe
Zustand
Vollbild öffnen(öffnet /login in einem neuen Tab)
ZVV

ZVV Atlas

Foundation · Patterns · Living Docs

Azure AD / Entra ID · SSO — folgt nach Kanton-Onboarding

Was die Plattform leistet

Fahrzeug-Lifecycle
Jahresabschluss
Reports
Custom Fields
8 MVUs
RLS-Isolation
Audit-Trail
RBAC + JWT-Sync

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

components/showcase/login-pattern/login-view.tsxtsx
// 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>
  • demo friert 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.
  • headingLevel existiert, weil die Login-Card ein h1 trägt und diese Seite bereits eines hat. Zwei h1 wären eine A11y-Regression, ein zweites <main id="main"> ebenso — deshalb bleibt das main bei der Route statt in der Komponente.
  • – Die Komponente ist bewusst motion-frei: LazyMotion umschliesst nur die Showcase-Gruppe, das Vollbild liegt ausserhalb. Ein m.* 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 ein setTimeout, weil der Showcase keinen Auth-Backend hat. Für den echten Ablauf siehe Auth Flow.
  • Einlauf-Animationen (Spec §2:fade-in-up gestaffelt, schwebende Blobs). Die Feature-Cards tragen die Verzögerung als animationDelay, die passenden @keyframes gibt es im Showcase-CSS aber nicht — die Angabe läuft ins Leere.