initial commit after split
This commit is contained in:
60
webui/src/features/auth/LoginModal.tsx
Normal file
60
webui/src/features/auth/LoginModal.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useState } from "react";
|
||||
import type { ApiSettings, LoginResponse } from "../../types";
|
||||
import { login } from "../../api/auth";
|
||||
import Button from "../../components/Button";
|
||||
import FormField from "../../components/FormField";
|
||||
import DismissibleAlert from "../../components/DismissibleAlert";
|
||||
|
||||
export default function LoginModal({
|
||||
settings,
|
||||
onClose,
|
||||
onLogin
|
||||
}: {
|
||||
settings: ApiSettings;
|
||||
onClose: () => void;
|
||||
onLogin: (response: LoginResponse) => void;
|
||||
}) {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
async function submit(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
setError("");
|
||||
setBusy(true);
|
||||
try {
|
||||
const response = await login(settings, { email, password });
|
||||
onLogin(response);
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overlay-backdrop" role="dialog" aria-modal="true">
|
||||
<form className="modal-panel" onSubmit={submit}>
|
||||
<header className="modal-header">
|
||||
<h2>Sign in</h2>
|
||||
<button className="modal-close" type="button" onClick={onClose}>×</button>
|
||||
</header>
|
||||
<div className="modal-body form-grid">
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error}>{error}</DismissibleAlert>}
|
||||
<FormField label="Email">
|
||||
<input type="email" value={email} autoComplete="username" onChange={(e) => setEmail(e.target.value)} />
|
||||
</FormField>
|
||||
<FormField label="Password">
|
||||
<input type="password" value={password} autoComplete="current-password" onChange={(e) => setPassword(e.target.value)} />
|
||||
</FormField>
|
||||
</div>
|
||||
<footer className="modal-footer">
|
||||
<Button type="button" onClick={onClose}>Cancel</Button>
|
||||
<Button type="submit" variant="primary" disabled={busy}>{busy ? "Signing in…" : "Sign in"}</Button>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
webui/src/features/auth/PublicLandingPage.tsx
Normal file
45
webui/src/features/auth/PublicLandingPage.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import type { ApiSettings, LoginResponse } from "../../types";
|
||||
import Button from "../../components/Button";
|
||||
import LoginModal from "./LoginModal";
|
||||
|
||||
export default function PublicLandingPage({
|
||||
settings,
|
||||
onLogin
|
||||
}: {
|
||||
settings: ApiSettings;
|
||||
onLogin: (response: LoginResponse) => void;
|
||||
}) {
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="public-landing">
|
||||
<section className="public-card">
|
||||
<div className="public-kicker">GovOPlaN</div>
|
||||
<h1>Modular planning tools with controlled access.</h1>
|
||||
<p>
|
||||
Sign in to open the modules available to your tenant and role.
|
||||
</p>
|
||||
|
||||
<div className="public-actions">
|
||||
<Button variant="primary" onClick={() => setLoginOpen(true)}>Sign in</Button>
|
||||
</div>
|
||||
|
||||
<div className="public-footnote">
|
||||
Access is restricted. Sign in to open available modules and administration.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{loginOpen && (
|
||||
<LoginModal
|
||||
settings={settings}
|
||||
onClose={() => setLoginOpen(false)}
|
||||
onLogin={(response) => {
|
||||
onLogin(response);
|
||||
setLoginOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user