'use client'; import { useState, useTransition } from 'react'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { login } from '@/lib/auth-actions'; import { sanitizeNextPath } from '@/lib/safe-next'; import { TesseraLogo } from '@/components/brand/tessera-logo'; import { DesktopDownloadLinks } from '@/components/desktop/desktop-download-links'; /** * Split-screen login page (D-01), Design „Mosaik“. * Left (ab lg): dunkles Markenpanel mit Bildmarke, Claim und Farbmosaik. * Right: Anmeldekarte auf der Arbeitsflaeche; unter lg nur die Karte. * No sidebar or header (D-04, handled by (auth) layout). */ export default function LoginPage() { const t = useTranslations('auth'); const [isPending, startTransition] = useTransition(); const [error, setError] = useState(null); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setError(null); const formData = new FormData(e.currentTarget); startTransition(async () => { const result = await login(formData); if (result.success) { // `next` erst beim Absenden aus window.location.search lesen statt // per useSearchParams(): der Hook verlangt in Next 15 eine // Suspense-Grenze, sonst bricht `next build` fuer die statisch // vorgerenderte Anmeldeseite ab (quick-260917-gyd). const next = new URLSearchParams(window.location.search).get('next'); window.location.href = sanitizeNextPath(next); } else { setError(result.error ?? 'invalidCredentials'); } }); } return (
{/* Links (ab lg): dunkles Markenpanel im Ton der App-Leiste mit grosser Bildmarke, Produktname, einem ruhigen Satz und einem stillen Mosaik in Grau und Gelb — kein Verlauf, keine Animation. */}

{t('branding.claim')}

{t('branding.tagline')}

{/* Rechts: Anmeldekarte zentriert auf der Arbeitsflaeche */}
{/* Unter lg: Bildmarke ueber der Karte */}
{/* Login heading */}

{t('login')}

{t('loginSubtitle')}

{/* Error message */} {error && (
{t(`error.${error}`)}
)} {/* Login form */}
{/* Username */}
{/* Password */}
{/* Angemeldet bleiben (D-02) und Passwort vergessen (D-03) in einer Zeile */}
{t('forgotPassword')}
{/* Submit button */}
{/* Desktop-App-Link (D-12), blendet sich aus ohne API-Antwort */}
); } /** * Stilles Mosaik (Design „Mosaik“): acht Kacheln in einem lockeren * 4 x 3-Raster in Grautoenen, eine davon in der Akzentfarbe und — wie die * Signalkachel der Bildmarke — leicht gedreht. Rein dekorativ. */ function LoginMosaic() { // Runde 2: nur Gelb und Graphit-/Grautoene des dunklen Panels. const tiles: { col: number; row: number; color: string; rotate?: boolean }[] = [ { col: 1, row: 1, color: 'oklch(1 0 0 / 0.14)' }, { col: 2, row: 1, color: 'oklch(1 0 0 / 0.08)' }, { col: 4, row: 1, color: 'oklch(1 0 0 / 0.1)' }, { col: 2, row: 2, color: 'oklch(1 0 0 / 0.18)' }, { col: 3, row: 2, color: 'var(--primary)', rotate: true }, { col: 4, row: 2, color: 'oklch(1 0 0 / 0.06)' }, { col: 1, row: 3, color: 'oklch(1 0 0 / 0.1)' }, { col: 3, row: 3, color: 'oklch(1 0 0 / 0.05)' }, ]; return ( ); }