Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions ghostkey-web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,13 @@ export default function App() {
<a href="#main" className="skip-link">
Skip to content
</a>
{/* Wait for the first /health probe before showing the network
banner. `network` defaults to "testnet", so rendering early
flashed an "Alpha: testnet, don't use real money" warning on
every reload of a mainnet vault before flipping to the live
banner. A wrong-network safety warning, even for a moment,
is worse than a brief absence. */}
{health !== "unknown" && <AlphaBanner network={network} />}
{/* Only show the network banner once /health has SUCCEEDED.
`network` defaults to "testnet", so rendering before the
probe (or after a failed one) told a mainnet owner
"Alpha: testnet, don't use real-money keys" — false and
alarming at the exact moment the server was already down.
When the probe fails, ServerOfflineBanner below covers it. */}
{health === "ok" && <AlphaBanner network={network} />}
{demoMode && <DemoBanner />}
{health === "offline" && <ServerOfflineBanner />}
{/*
Expand Down
36 changes: 25 additions & 11 deletions ghostkey-web/src/SignInPortal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ type Phase =
export function SignInPortal({ onNavigate }: Props) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// Mistyped passwords are the #1 sign-in failure, and the field hides
// every character. A show toggle costs nothing and rescues most of
// them (Bitcoin UX principle: password UX is money UX).
const [showPassword, setShowPassword] = useState(false);
const [phase, setPhase] = useState<Phase>({ kind: "idle" });
const [error, setError] = useState<string | null>(null);

Expand Down Expand Up @@ -351,17 +355,27 @@ export function SignInPortal({ onNavigate }: Props) {
</Field>

<Field label="Password">
<input
type="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
reset();
}}
autoComplete="current-password"
className="input"
disabled={phase.kind === "looking" || phase.kind === "unsealing"}
/>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => {
setPassword(e.target.value);
reset();
}}
autoComplete="current-password"
className="input pr-16"
disabled={phase.kind === "looking" || phase.kind === "unsealing"}
/>
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute inset-y-0 right-3 text-xs font-medium text-muted hover:text-[var(--text)]"
aria-pressed={showPassword}
>
{showPassword ? "Hide" : "Show"}
</button>
</div>
</Field>

{phase.kind === "unsealing" ? (
Expand Down
Loading