Files
pi-pm3/app/frontend/app/components/LoginPrompt.tsx
michael a9acdb85ce Build optimization: pre-built PM3 binaries, ARM64 CI, base image caching
Replace PM3 compile-from-source in pi-gen with pre-built tarball extraction
(saves 43-58 min). Merge stagePM3 into stageDangerousPi as 02-pm3-install
substage, renumber all subsequent substages. Switch CI PM3 build to native
ARM64 runner (ubuntu-24.04-arm64) eliminating QEMU overhead. Add weekly
base-image workflow for pre-baking stages 0-2. Support PM3_TARBALL,
BASE_IMAGE, and APT_PROXY env vars in build-image.sh.

Also includes prior Phase 5 work: theme system, design system integration,
component update system, OS updates, CI build pipeline, and test results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 12:01:01 -08:00

95 lines
2.8 KiB
TypeScript

import { useState, type FormEvent } from "react";
import { getWebSocketManager } from "../hooks/useWebSocket";
export function LoginPrompt() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await getWebSocketManager().fetchToken(username, password);
} catch (err) {
setError(err instanceof Error ? err.message : "Authentication failed");
} finally {
setLoading(false);
}
};
return (
<div
style={{
position: "fixed",
inset: 0,
zIndex: 9999,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "rgba(var(--color-bg-rgb), 0.75)",
backdropFilter: "blur(4px)",
}}
>
<div className="card" style={{ maxWidth: "360px", width: "100%", margin: "1rem" }}>
<div className="card-title" style={{ textAlign: "center", marginBottom: "1.5rem" }}>
Dangerous Pi
</div>
<form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
<div>
<label htmlFor="login-user" style={{ display: "block", marginBottom: "0.25rem", fontSize: "0.875rem" }}>
Username
</label>
<input
id="login-user"
className="input"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
autoComplete="username"
autoFocus
required
style={{ width: "100%" }}
/>
</div>
<div>
<label htmlFor="login-pass" style={{ display: "block", marginBottom: "0.25rem", fontSize: "0.875rem" }}>
Password
</label>
<input
id="login-pass"
className="input"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
required
style={{ width: "100%" }}
/>
</div>
{error && (
<div style={{ color: "var(--color-error, #ff4444)", fontSize: "0.875rem" }}>
{error}
</div>
)}
<button
type="submit"
className="btn-primary"
disabled={loading}
style={{ width: "100%", marginTop: "0.5rem" }}
>
{loading ? "Authenticating..." : "Log In"}
</button>
</form>
</div>
</div>
);
}