initial commit after split

This commit is contained in:
2026-06-24 01:43:10 +02:00
parent b1d6c0150f
commit 30c11a6dcf
173 changed files with 25380 additions and 0 deletions

View 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>
);
}

View 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>
);
}