296 lines
11 KiB
TypeScript
296 lines
11 KiB
TypeScript
import { FormEvent, useState } from "react";
|
|
import { apiPost } from "../lib/api";
|
|
import { useSession } from "../lib/session";
|
|
import type { UserOption } from "../lib/types";
|
|
|
|
type FeedbackState =
|
|
| { type: "error"; text: string }
|
|
| { type: "success"; text: string }
|
|
| null;
|
|
|
|
export default function LoginPage() {
|
|
const [showRegistration, setShowRegistration] = useState(false);
|
|
const [identifier, setIdentifier] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [showLoginValidation, setShowLoginValidation] = useState(false);
|
|
const [showRegisterValidation, setShowRegisterValidation] = useState(false);
|
|
const [registration, setRegistration] = useState({
|
|
companyName: "",
|
|
street: "",
|
|
houseNumber: "",
|
|
postalCode: "",
|
|
city: "",
|
|
email: "",
|
|
phoneNumber: "",
|
|
password: "",
|
|
passwordConfirmation: "",
|
|
});
|
|
const [feedback, setFeedback] = useState<FeedbackState>(null);
|
|
const { setUser } = useSession();
|
|
|
|
async function handlePasswordLogin(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
setShowLoginValidation(true);
|
|
if (!identifier.trim() || !password.trim()) {
|
|
setFeedback({
|
|
type: "error",
|
|
text: "Bitte E-Mail oder Benutzername und Passwort eingeben.",
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
setFeedback(null);
|
|
const response = await apiPost<UserOption>("/session/password-login", {
|
|
identifier: identifier.trim(),
|
|
password,
|
|
});
|
|
setUser(response);
|
|
} catch (loginError) {
|
|
setFeedback({ type: "error", text: (loginError as Error).message });
|
|
}
|
|
}
|
|
|
|
async function handleRegister(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
setShowRegisterValidation(true);
|
|
if (
|
|
!registration.companyName.trim()
|
|
|| !registration.street.trim()
|
|
|| !registration.houseNumber.trim()
|
|
|| !registration.postalCode.trim()
|
|
|| !registration.city.trim()
|
|
|| !registration.email.trim()
|
|
|| !registration.phoneNumber.trim()
|
|
|| !registration.password.trim()
|
|
) {
|
|
setFeedback({
|
|
type: "error",
|
|
text: "Bitte alle Pflichtfelder inklusive Telefonnummer fuer die Registrierung ausfuellen.",
|
|
});
|
|
return;
|
|
}
|
|
if (registration.password !== registration.passwordConfirmation) {
|
|
setFeedback({ type: "error", text: "Die beiden Passwoerter stimmen nicht ueberein." });
|
|
return;
|
|
}
|
|
try {
|
|
setFeedback(null);
|
|
const { passwordConfirmation, ...registrationPayload } = registration;
|
|
void passwordConfirmation;
|
|
const response = await apiPost<UserOption>("/session/register", registrationPayload);
|
|
setFeedback({
|
|
type: "success",
|
|
text: `Registrierung erfolgreich. Willkommen ${response.companyName ?? response.displayName}.`,
|
|
});
|
|
setUser(response);
|
|
} catch (registrationError) {
|
|
setFeedback({ type: "error", text: (registrationError as Error).message });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="login-page">
|
|
<section className="login-hero">
|
|
<div className="login-hero__copy">
|
|
<p className="eyebrow">MUH-App</p>
|
|
<h1>Moderne Steuerung fuer Milchproben und Therapien.</h1>
|
|
<p className="hero-text">
|
|
Fokus auf klare Arbeitsablaeufe, schnelle Probenbearbeitung und ein Portal
|
|
fuer Verwaltung, Berichtsdruck und Versandstatus.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="login-hero__panel">
|
|
<div className="panel-glow" />
|
|
<p className="eyebrow">Zugang</p>
|
|
<h2>Anmelden oder registrieren</h2>
|
|
<p className="muted-text">
|
|
Anmeldung per E-Mail oder Benutzername mit Passwort sowie direkte
|
|
Kundenregistrierung.
|
|
</p>
|
|
|
|
{feedback ? (
|
|
<div className={`alert ${feedback.type === "success" ? "alert--success" : "alert--error"}`}>
|
|
{feedback.text}
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="auth-grid">
|
|
{!showRegistration ? (
|
|
<form className={`login-panel__section ${showLoginValidation ? "show-validation" : ""}`} onSubmit={handlePasswordLogin}>
|
|
<label className="field field--required">
|
|
<span>E-Mail / Benutzername</span>
|
|
<input
|
|
value={identifier}
|
|
onChange={(event) => setIdentifier(event.target.value)}
|
|
placeholder="z. B. admin oder name@hof.de"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>Passwort</span>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
required
|
|
/>
|
|
</label>
|
|
<div className="page-actions">
|
|
<button type="submit" className="accent-button">
|
|
Mit Passwort anmelden
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="secondary-button"
|
|
onClick={() => {
|
|
setFeedback(null);
|
|
setShowRegisterValidation(false);
|
|
setShowRegistration(true);
|
|
}}
|
|
>
|
|
Registrieren
|
|
</button>
|
|
</div>
|
|
</form>
|
|
) : (
|
|
<form className={`login-panel__section ${showRegisterValidation ? "show-validation" : ""}`} onSubmit={handleRegister}>
|
|
<div className="field-grid">
|
|
<label className="field field--wide field--required">
|
|
<span>Firmenname</span>
|
|
<input
|
|
value={registration.companyName}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, companyName: event.target.value }))
|
|
}
|
|
placeholder="z. B. Muster Agrar GmbH"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>Strasse</span>
|
|
<input
|
|
value={registration.street}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, street: event.target.value }))
|
|
}
|
|
placeholder="z. B. Dorfstrasse"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>Hausnummer</span>
|
|
<input
|
|
value={registration.houseNumber}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, houseNumber: event.target.value }))
|
|
}
|
|
placeholder="z. B. 12a"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>PLZ</span>
|
|
<input
|
|
value={registration.postalCode}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, postalCode: event.target.value }))
|
|
}
|
|
placeholder="z. B. 12345"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>Ort</span>
|
|
<input
|
|
value={registration.city}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, city: event.target.value }))
|
|
}
|
|
placeholder="z. B. Musterstadt"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>E-Mail</span>
|
|
<input
|
|
type="email"
|
|
value={registration.email}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, email: event.target.value }))
|
|
}
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--required">
|
|
<span>Telefonnummer</span>
|
|
<input
|
|
type="tel"
|
|
value={registration.phoneNumber}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, phoneNumber: event.target.value }))
|
|
}
|
|
placeholder="z. B. 04531 181424"
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--wide field--required">
|
|
<span>Passwort</span>
|
|
<input
|
|
type="password"
|
|
value={registration.password}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({ ...current, password: event.target.value }))
|
|
}
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field field--wide field--required">
|
|
<span>Passwort wiederholen</span>
|
|
<input
|
|
type="password"
|
|
value={registration.passwordConfirmation}
|
|
className={
|
|
showRegisterValidation
|
|
&& registration.password !== registration.passwordConfirmation
|
|
? "is-invalid"
|
|
: ""
|
|
}
|
|
aria-invalid={
|
|
showRegisterValidation && registration.password !== registration.passwordConfirmation
|
|
}
|
|
onChange={(event) =>
|
|
setRegistration((current) => ({
|
|
...current,
|
|
passwordConfirmation: event.target.value,
|
|
}))
|
|
}
|
|
required
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="page-actions">
|
|
<button type="submit" className="accent-button">
|
|
Registrieren
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="secondary-button"
|
|
onClick={() => {
|
|
setFeedback(null);
|
|
setShowLoginValidation(false);
|
|
setShowRegistration(false);
|
|
}}
|
|
>
|
|
Zurück
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|