Files
Flight-Ops/components/SignInScreen.tsx
T
2026-09-26 14:40:56 +00:00

79 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState } from 'react';
import { storeSession } from '@/lib/authClient';
// v1.1.0 (ISS-107) — full-screen sign-in / create-account screen shown when no valid session is
// stored. Both forms POST the same shape ({username, password}); login mints a session for an
// existing account, register creates one first and then mints it. On success the sessionId goes
// to localStorage (via storeSession) and onAuthed() flips the shell back in.
interface SignInScreenProps {
onAuthed: () => void;
}
export default function SignInScreen({ onAuthed }: SignInScreenProps) {
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
async function submit(e: React.FormEvent<HTMLFormElement>, path: '/api/login' | '/api/register') {
e.preventDefault();
if (busy) return;
setBusy(true);
setError(null);
try {
const form = new FormData(e.currentTarget);
const res = await fetch(path, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: String(form.get('username') ?? ''),
password: String(form.get('password') ?? ''),
}),
cache: 'no-store',
});
const data = (await res.json()) as { sessionId?: string; error?: string };
if (!res.ok || !data.sessionId) throw new Error(data.error ?? `HTTP ${res.status}`);
storeSession(data.sessionId);
onAuthed();
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setBusy(false);
}
}
return (
<div className="sign-in">
<h1 data-testid="app-title">Flight Ops</h1>
{error !== null && (
<p data-testid="auth-error" role="alert" className="sign-in-error">
{error}
</p>
)}
<form data-testid="sign-in-form" onSubmit={(e) => void submit(e, '/api/login')}>
<label htmlFor="sign-in-username">Username</label>
<input id="sign-in-username" name="username" autoComplete="username" required />
<label htmlFor="sign-in-password">Password</label>
<input id="sign-in-password" name="password" type="password" autoComplete="current-password" required />
<button type="submit" data-testid="sign-in-submit" className="pane-btn" disabled={busy}>
Sign in
</button>
</form>
<p className="sign-in-divider">or</p>
<form data-testid="create-account-form" onSubmit={(e) => void submit(e, '/api/register')}>
<label htmlFor="register-username">Username (3–20 chars: a-z 0-9 _ -)</label>
<input id="register-username" name="username" autoComplete="off" required />
<label htmlFor="register-password">Password (at least 8 characters)</label>
<input id="register-password" name="password" type="password" autoComplete="new-password" required minLength={8} />
<button type="submit" data-testid="register-submit" className="pane-btn" disabled={busy}>
Create account
</button>
</form>
</div>
);
}