| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- "use client";
- import React from "react";
- import { useRouter } from "next/navigation";
- import { login } from "@/lib/frontend/apiClient";
- import { sanitizeNext } from "@/lib/frontend/authRedirect";
- import {
- getLoginErrorMessage,
- getLoginReasonAlert,
- } from "@/lib/frontend/authMessages";
- import { homePath } from "@/lib/frontend/routes";
- import { Button } from "@/components/ui/button";
- import { Input } from "@/components/ui/input";
- import { Label } from "@/components/ui/label";
- import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
- import {
- Card,
- CardHeader,
- CardTitle,
- CardDescription,
- CardContent,
- CardFooter,
- } from "@/components/ui/card";
- /**
- * LoginForm (RHL-020)
- *
- * Username casing policy:
- * - Our backend stores usernames in lowercase and performs lowercase normalization.
- * - To make UX consistent (and avoid "it works with BranchUser" confusion),
- * we normalize the username input to lowercase in the UI as well.
- *
- * NOTE:
- * - Password is NOT normalized. It remains case-sensitive.
- *
- * UX rule:
- * - All user-facing text must be German.
- *
- * @param {{ reason: string|null, nextPath: string|null }} props
- */
- export default function LoginForm({ reason, nextPath }) {
- const router = useRouter();
- // Controlled inputs.
- const [username, setUsername] = React.useState("");
- const [password, setPassword] = React.useState("");
- // UX state.
- const [isSubmitting, setIsSubmitting] = React.useState(false);
- const [errorMessage, setErrorMessage] = React.useState("");
- // Optional informational banner (session expired / logged-out).
- const reasonAlert = getLoginReasonAlert(reason);
- // Defensive: sanitize nextPath again on the client.
- const safeNext = sanitizeNext(nextPath) || homePath();
- async function onSubmit(e) {
- e.preventDefault();
- // Enforce our username policy at submit time as well (defense-in-depth).
- const u = username.trim().toLowerCase();
- const p = password; // do NOT normalize password
- if (!u || !p) {
- setErrorMessage("Bitte Benutzername und Passwort eingeben.");
- return;
- }
- setIsSubmitting(true);
- setErrorMessage("");
- try {
- await login({ username: u, password: p });
- router.replace(safeNext);
- } catch (err) {
- setErrorMessage(getLoginErrorMessage(err));
- setIsSubmitting(false);
- }
- }
- return (
- <Card>
- <CardHeader>
- <CardTitle>Anmeldung</CardTitle>
- <CardDescription>
- Bitte geben Sie Ihre Zugangsdaten ein, um die Lieferscheine zu öffnen.
- </CardDescription>
- </CardHeader>
- <CardContent className="space-y-4">
- {reasonAlert ? (
- <Alert>
- <AlertTitle>{reasonAlert.title}</AlertTitle>
- <AlertDescription>{reasonAlert.description}</AlertDescription>
- </Alert>
- ) : null}
- {errorMessage ? (
- <Alert variant="destructive">
- <AlertTitle>Anmeldung fehlgeschlagen</AlertTitle>
- <AlertDescription>{errorMessage}</AlertDescription>
- </Alert>
- ) : null}
- <form onSubmit={onSubmit} className="space-y-4">
- <div className="grid gap-2">
- <Label htmlFor="username">Benutzername</Label>
- <Input
- id="username"
- name="username"
- autoComplete="username"
- // Prevent mobile keyboards from auto-capitalizing the first character.
- autoCapitalize="none"
- autoCorrect="off"
- spellCheck={false}
- value={username}
- onChange={(e) => {
- // Normalize to lowercase as the user types (consistent UX).
- setUsername(e.target.value.toLowerCase());
- }}
- disabled={isSubmitting}
- placeholder="z. B. branchuser"
- />
- </div>
- <div className="grid gap-2">
- <Label htmlFor="password">Passwort</Label>
- <Input
- id="password"
- name="password"
- type="password"
- autoComplete="current-password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- disabled={isSubmitting}
- placeholder="••••••••"
- />
- </div>
- <CardFooter className="p-0">
- <Button type="submit" className="w-full" disabled={isSubmitting}>
- {isSubmitting ? "Anmeldung…" : "Anmelden"}
- </Button>
- </CardFooter>
- </form>
- </CardContent>
- </Card>
- );
- }
|