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>
95 lines
2.8 KiB
TypeScript
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>
|
|
);
|
|
}
|