M8: add operational authentication mode

This commit is contained in:
NuklearRabbit
2026-08-10 02:06:50 +02:00
parent 4cdf667dc1
commit 0bfcf71ff7
20 changed files with 345 additions and 24 deletions
+3 -3
View File
@@ -46,7 +46,7 @@ export function Dashboard() {
const { t } = useTranslation(["dashboard", "common", "integrations", "quality"]);
const { formatTime, formatShortDate, formatNumber } = useLocaleFormat();
const greetingPeriod = useGreetingPeriod();
const { user } = useAuth();
const { user, demoMode } = useAuth();
const { manifest } = useDemoManifest();
const { openGuide, restart, currentIndex, completed, totalSteps } = useDemoGuide();
const [searchParams, setSearchParams] = useSearchParams();
@@ -114,7 +114,7 @@ export function Dashboard() {
<div className="page dashboard-page">
<PageHeader eyebrow={t("eyebrow")} title={greetingTitle} description={t("description")} actions={<Link className="button button-secondary" to="/vehicles"><Icon name="fleet" /> {t("viewFleet")}</Link>} />
<section className="demo-start-panel" aria-label={t("demoStart.title")}>
{demoMode && <section className="demo-start-panel" aria-label={t("demoStart.title")}>
<div>
<Icon name="spark" />
<div>
@@ -132,7 +132,7 @@ export function Dashboard() {
)}
<Link className="button button-primary" to="/scenarios">{t("demoStart.viewScenarios")} <Icon name="chevron" /></Link>
</div>
</section>
</section>}
<section className="readiness-band" aria-labelledby="readiness-heading">
<div className="readiness-label">
+21 -4
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, type FormEvent } from "react";
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useAuth } from "../context/AuthContext";
@@ -10,10 +10,12 @@ import { PRODUCT_NAME } from "../product";
export function Login() {
const { t } = useTranslation(["auth", "common"]);
const { loginAs, loading } = useAuth();
const { loginAs, loginWithPassword, loading, demoMode } = useAuth();
const { manifest } = useDemoManifest();
const navigate = useNavigate();
const [error, setError] = useState<string | null>(null);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
async function handleLogin(role: Role, guided = false) {
setError(null);
@@ -25,6 +27,17 @@ export function Login() {
}
}
async function handlePasswordLogin(event: FormEvent) {
event.preventDefault();
setError(null);
try {
await loginWithPassword(email, password);
navigate("/dashboard");
} catch {
setError(t("passwordLoginFailed"));
}
}
const orgName = manifest?.organization_name ?? t("common:orgName");
const description = t("defaultDescription", { productName: PRODUCT_NAME });
@@ -61,7 +74,7 @@ export function Login() {
<p className="login-intro">{t("accessIntro")}</p>
{error && <p className="error" role="alert">{error}</p>}
<button
{demoMode ? <><button
type="button"
className="button button-primary login-guide-cta"
disabled={loading}
@@ -82,7 +95,11 @@ export function Login() {
<span><strong>{t("exploreAsRentalEmployee")}</strong><small>{t("exploreAsRentalEmployeeDetail")}</small></span>
<Icon name="chevron" />
</button>
</div>
</div></> : <form className="login-options" onSubmit={handlePasswordLogin}>
<label>{t("emailLabel")}<input type="email" autoComplete="username" value={email} onChange={(event) => setEmail(event.target.value)} required /></label>
<label>{t("passwordLabel")}<input type="password" autoComplete="current-password" value={password} onChange={(event) => setPassword(event.target.value)} minLength={8} required /></label>
<button type="submit" className="button button-primary" disabled={loading}>{t("signIn")}</button>
</form>}
<div className="access-note"><Icon name="shield" /><p><strong>{t("safeByDesignTitle")}</strong><span>{t("safeByDesignDetail")}</span></p></div>
</div>
</section>