The /internal/dev-login-config endpoint is a fm-shell Next.js internal route that doesn't exist on the canvas nginx. Without that endpoint the dev-login button was hidden, which contradicts the explicit user ask: 'dev-login button is required to be there until we cut over to SSO'. Fix: default state to true, treat the config endpoint as 'flip OFF only'. If the endpoint returns enabled:false we hide. Otherwise (404, network error, etc.) we keep the button visible. Confidence: high Scope-risk: narrow Not-tested: real /internal/dev-login-config returning enabled:false (would need that endpoint mounted on canvas — out of scope for now)
146 lines
4.7 KiB
TypeScript
146 lines
4.7 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { useApp } from "../state/store";
|
|
import { api } from "../lib/api";
|
|
import { Bot } from "../components/icons";
|
|
|
|
export default function Login() {
|
|
const setScene = useApp((s) => s.setScene);
|
|
const loginAs = useApp((s) => s.loginAs);
|
|
const [email, setEmail] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [rememberMe, setRememberMe] = useState(false);
|
|
const [devLoginEnabled, setDevLoginEnabled] = useState(true);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
// The dev-login button is intentionally on by default on canvas
|
|
// (the user explicitly asked for it as a documented exception until
|
|
// we cut over fully to SSO). The endpoint config only flips it OFF.
|
|
api.devLoginConfig()
|
|
.then((cfg) => { if (cfg.enabled === false) setDevLoginEnabled(false); })
|
|
.catch(() => { /* endpoint not present — keep default ON */ });
|
|
}, []);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!email) return;
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
await loginAs(email, password || undefined);
|
|
setScene("landing");
|
|
} catch (err) {
|
|
setError((err as Error).message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDevLogin = async () => {
|
|
if (!email) {
|
|
setError("Email required for dev-login");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
await loginAs(email, undefined);
|
|
setScene("landing");
|
|
} catch (err) {
|
|
setError((err as Error).message);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSSO = () => {
|
|
window.location.href = "/api/v1/auth/microsoft/login";
|
|
};
|
|
|
|
return (
|
|
<div className="login-page">
|
|
<div className="login-card">
|
|
<div className="login-head">
|
|
<span className="brand-mark"></span>
|
|
<h1>FLOWMASTER AUTHENTICATION</h1>
|
|
<div className="login-sub">MISSION CONTROL VERIFICATION REQUIRED</div>
|
|
</div>
|
|
|
|
{error && <div className="login-error">{error}</div>}
|
|
|
|
<form className="login-form" onSubmit={handleSubmit}>
|
|
<div className="login-field">
|
|
<label htmlFor="email">OPERATOR_ID (EMAIL)</label>
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
className="login-input"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
disabled={loading}
|
|
autoComplete="email"
|
|
placeholder="e.g. j.doe@flow-master.ai"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className="login-field">
|
|
<label htmlFor="password">PASSPHRASE</label>
|
|
<input
|
|
id="password"
|
|
type="password"
|
|
className="login-input"
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
disabled={loading}
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
|
|
<div className="login-row">
|
|
<label className="login-checkbox">
|
|
<input
|
|
type="checkbox"
|
|
checked={rememberMe}
|
|
onChange={(e) => setRememberMe(e.target.checked)}
|
|
disabled={loading}
|
|
/>
|
|
<span>Remember me</span>
|
|
</label>
|
|
<a href="#" className="login-link">Recover access</a>
|
|
</div>
|
|
|
|
<div className="login-actions">
|
|
<button type="submit" className="btn btn-primary btn-lg" disabled={loading || !email}>
|
|
{loading ? "VERIFYING..." : "SIGN IN"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div className="login-divider">
|
|
<span>OR</span>
|
|
</div>
|
|
|
|
<div className="login-sso-actions">
|
|
<button type="button" className="sso-btn" onClick={handleSSO} disabled={loading}>
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 21 21">
|
|
<rect x="1" y="1" width="9" height="9" fill="#f25022"/>
|
|
<rect x="11" y="1" width="9" height="9" fill="#7fba00"/>
|
|
<rect x="1" y="11" width="9" height="9" fill="#00a4ef"/>
|
|
<rect x="11" y="11" width="9" height="9" fill="#ffb900"/>
|
|
</svg>
|
|
CONTINUE WITH MICROSOFT
|
|
</button>
|
|
|
|
{devLoginEnabled && (
|
|
<button type="button" className="dev-login-btn" onClick={handleDevLogin} disabled={loading || !email}>
|
|
<Bot size={14} /> SIGN IN AS DEVELOPER (DEV-LOGIN)
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|