M21: add optional organisation identity
This commit is contained in:
@@ -11,6 +11,8 @@ export interface SystemStatus {
|
||||
environment: string;
|
||||
demo_mode: boolean;
|
||||
knowledge_provider: string;
|
||||
oidc_enabled: boolean;
|
||||
oidc_provider_name: string | null;
|
||||
}
|
||||
|
||||
export interface Vehicle {
|
||||
|
||||
@@ -6,6 +6,8 @@ interface AuthState {
|
||||
user: CurrentUser | null;
|
||||
loading: boolean;
|
||||
demoMode: boolean;
|
||||
oidcEnabled: boolean;
|
||||
oidcProviderName: string | null;
|
||||
loginAs: (role: Role) => Promise<void>;
|
||||
loginWithPassword: (email: string, password: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
@@ -35,11 +37,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<CurrentUser | null>(() => readCachedUser());
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [demoMode, setDemoMode] = useState(true);
|
||||
const [oidcEnabled, setOidcEnabled] = useState(false);
|
||||
const [oidcProviderName, setOidcProviderName] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
api.get<SystemStatus>("/api/v1/system/status")
|
||||
.then((system) => setDemoMode(system.demo_mode))
|
||||
.then((system) => {
|
||||
setDemoMode(system.demo_mode);
|
||||
setOidcEnabled(system.oidc_enabled);
|
||||
setOidcProviderName(system.oidc_provider_name);
|
||||
})
|
||||
.catch(() => setDemoMode(true))
|
||||
.finally(() => api
|
||||
.get<CurrentUser>("/api/v1/auth/session")
|
||||
@@ -119,7 +127,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, demoMode, loginAs, loginWithPassword, logout }}>{children}</AuthContext.Provider>
|
||||
<AuthContext.Provider value={{ user, loading, demoMode, oidcEnabled, oidcProviderName, loginAs, loginWithPassword, logout }}>{children}</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,5 +23,7 @@
|
||||
"emailLabel": "Email address",
|
||||
"passwordLabel": "Password",
|
||||
"signIn": "Sign in",
|
||||
"passwordLoginFailed": "Sign-in failed. Check your credentials."
|
||||
"passwordLoginFailed": "Sign-in failed. Check your credentials.",
|
||||
"or": "or",
|
||||
"signInWithOrganization": "Sign in with {{provider}}"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,7 @@
|
||||
"emailLabel": "Adresse e-mail",
|
||||
"passwordLabel": "Mot de passe",
|
||||
"signIn": "Se connecter",
|
||||
"passwordLoginFailed": "Connexion impossible. Vérifiez vos identifiants."
|
||||
"passwordLoginFailed": "Connexion impossible. Vérifiez vos identifiants.",
|
||||
"or": "ou",
|
||||
"signInWithOrganization": "Se connecter avec {{provider}}"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,7 @@
|
||||
"emailLabel": "E-mailadres",
|
||||
"passwordLabel": "Wachtwoord",
|
||||
"signIn": "Aanmelden",
|
||||
"passwordLoginFailed": "Aanmelden mislukt. Controleer je gegevens."
|
||||
"passwordLoginFailed": "Aanmelden mislukt. Controleer je gegevens.",
|
||||
"or": "of",
|
||||
"signInWithOrganization": "Aanmelden met {{provider}}"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PRODUCT_NAME } from "../product";
|
||||
|
||||
export function Login() {
|
||||
const { t } = useTranslation(["auth", "common"]);
|
||||
const { loginAs, loginWithPassword, loading, demoMode } = useAuth();
|
||||
const { loginAs, loginWithPassword, loading, demoMode, oidcEnabled, oidcProviderName } = useAuth();
|
||||
const { manifest } = useDemoManifest();
|
||||
const navigate = useNavigate();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -100,6 +100,13 @@ export function Login() {
|
||||
<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>}
|
||||
{oidcEnabled && <>
|
||||
<div className="login-divider"><span>{t("or")}</span></div>
|
||||
<a className="button login-oidc" href="/api/v1/auth/oidc/login">
|
||||
<Icon name="shield" />
|
||||
{t("signInWithOrganization", { provider: oidcProviderName })}
|
||||
</a>
|
||||
</>}
|
||||
<div className="access-note"><Icon name="shield" /><p><strong>{t("safeByDesignTitle")}</strong><span>{t("safeByDesignDetail")}</span></p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -113,6 +113,10 @@ a:hover { color: var(--teal); }
|
||||
.sidebar-language { display: none; padding: 0 20px 14px; }
|
||||
.login-panel-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.login-panel-top .page-eyebrow { margin: 0; }
|
||||
.login-divider { display: flex; align-items: center; gap: 12px; margin: 18px 0; color: var(--muted); font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; }
|
||||
.login-divider::before, .login-divider::after { content: ""; height: 1px; flex: 1; background: var(--line); }
|
||||
.login-oidc { width: 100%; min-height: 44px; justify-content: center; gap: 8px; background: white; border: 1px solid var(--line-strong); }
|
||||
.login-oidc svg { width: 17px; }
|
||||
.demo-badge { position: relative; }
|
||||
.demo-badge-trigger { display: flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px; color: #48566a; background: #eaf0f5; border: 1px solid #d7e0e8; border-radius: 999px; font-size: .68rem; font-weight: 700; letter-spacing: .02em; cursor: pointer; }
|
||||
.demo-badge-trigger:hover { background: #dfe8ef; }
|
||||
|
||||
Reference in New Issue
Block a user