// login.jsx — two-step auth: password then TOTP
const { useState: useStateL, useCallback: useCallbackL } = React;
function LoginScreen({ onLogin, onRegister }) {
const [step, setStep] = useStateL("password"); // password | setup | totp
const [email, setEmail] = useStateL("getpat@gmail.com");
const [password, setPassword] = useStateL("");
const [code, setCode] = useStateL("");
const [token, setToken] = useStateL("");
const [setupInfo, setSetupInfo] = useStateL(null);
const [error, setError] = useStateL("");
const [loading, setLoading] = useStateL(false);
const submitPassword = useCallbackL(async (e) => {
e.preventDefault();
setError(""); setLoading(true);
try {
const r = await fetch("/api/v1/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await r.json();
if (!r.ok) { setError(data.detail || "Login failed"); return; }
setToken(data.token);
if (data.step === "setup") {
setSetupInfo({ secret: data.totp_secret, uri: data.totp_uri, qr: data.totp_qr });
setStep("setup");
} else {
setStep("totp");
}
} catch {
setError("Network error");
} finally { setLoading(false); }
}, [email, password]);
const submitTotp = useCallbackL(async (e) => {
e.preventDefault();
setError(""); setLoading(true);
try {
const r = await fetch("/api/v1/auth/verify-totp", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, code }),
});
const data = await r.json();
if (!r.ok) { setError(data.detail || "Invalid code"); return; }
onLogin();
} catch {
setError("Network error");
} finally { setLoading(false); }
}, [token, code, onLogin]);
return (
▲
Everest
{step === "password" && (
)}
{step === "setup" && setupInfo && (
)}
{step === "totp" && (
)}
{step === "password" && onRegister && (
New here?{" "}
)}
);
}
const LS = {
overlay: { position: "fixed", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "#0f1117", zIndex: 9999 },
card: { background: "#1a1d27", border: "1px solid rgba(255,255,255,0.08)", borderRadius: "12px", padding: "2.5rem 2rem", width: "100%", maxWidth: "380px", boxShadow: "0 24px 64px rgba(0,0,0,0.5)" },
logo: { display: "flex", alignItems: "center", gap: "0.5rem", marginBottom: "1.5rem" },
logoMark: { fontSize: "1.4rem", color: "#C9512B" },
logoText: { fontSize: "1.2rem", fontWeight: 600, color: "#e8e6e3", letterSpacing: "0.02em" },
subtitle: { margin: "0 0 1.25rem", fontSize: "0.85rem", color: "#888", textTransform: "uppercase", letterSpacing: "0.08em" },
hint: { fontSize: "0.82rem", color: "#888", lineHeight: 1.5, margin: "0 0 1rem" },
label: { display: "block", fontSize: "0.78rem", color: "#888", marginBottom: "0.3rem", textTransform: "uppercase", letterSpacing: "0.06em" },
input: { display: "block", width: "100%", boxSizing: "border-box", padding: "0.6rem 0.75rem", marginBottom: "1rem", background: "#0f1117", color: "#e8e6e3", border: "1px solid rgba(255,255,255,0.12)", borderRadius: "6px", fontSize: "0.95rem", outline: "none" },
btn: { display: "block", width: "100%", padding: "0.65rem", background: "#C9512B", color: "#fff", border: "none", borderRadius: "6px", fontSize: "0.9rem", fontWeight: 600, cursor: "pointer", marginTop: "0.5rem" },
linkBtn: { display: "block", width: "100%", padding: "0.5rem", background: "transparent", color: "#888", border: "none", fontSize: "0.82rem", cursor: "pointer", marginTop: "0.5rem" },
error: { color: "#e05c5c", fontSize: "0.82rem", margin: "0 0 0.75rem" },
secretBox: { background: "#0f1117", border: "1px solid rgba(255,255,255,0.1)", borderRadius: "6px", padding: "0.75rem", marginBottom: "0.75rem" },
secretLabel: { display: "block", fontSize: "0.72rem", color: "#888", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "0.3rem" },
secret: { fontSize: "0.85rem", color: "#e8e6e3", wordBreak: "break-all", letterSpacing: "0.1em" },
summary: { fontSize: "0.78rem", color: "#888", cursor: "pointer" },
uriText: { display: "block", fontSize: "0.7rem", color: "#888", wordBreak: "break-all", marginTop: "0.4rem" },
};