Add curator authentication with audit logging and fix empty 3D gallery sessions.

Introduce session-based curator login, gate debug/checkup routes, log mutations to curator_audit_log, and keep guest hall preload public. Fix gallery view mounting so WebGL halls render reliably after navigation.
This commit is contained in:
Danila Khodjaef
2026-07-06 00:17:01 +03:00
parent aa31a2aa6e
commit 9da065acbe
27 changed files with 1252 additions and 112 deletions
@@ -0,0 +1,90 @@
import { useEffect, useState, type FormEvent } from 'react';
import './CuratorLoginModal.css';
interface Props {
open: boolean;
onClose: () => void;
onLogin: (username: string, password: string) => Promise<void>;
}
export default function CuratorLoginModal({ open, onClose, onLogin }: Props) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (!open) {
setUsername('');
setPassword('');
setError(null);
setSubmitting(false);
}
}, [open]);
if (!open) return null;
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setError(null);
setSubmitting(true);
try {
await onLogin(username.trim(), password);
onClose();
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
} finally {
setSubmitting(false);
}
};
return (
<div className="curator-login-backdrop" onMouseDown={onClose}>
<div
className="curator-login-modal"
role="dialog"
aria-labelledby="curator-login-title"
aria-modal="true"
onMouseDown={(e) => e.stopPropagation()}
>
<h2 id="curator-login-title">Curator login</h2>
<p className="curator-login-hint">
Debug tools and catalog edits require a curator account.
</p>
<form onSubmit={handleSubmit}>
<label className="curator-login-field">
<span>Username</span>
<input
type="text"
autoComplete="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={submitting}
required
/>
</label>
<label className="curator-login-field">
<span>Password</span>
<input
type="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={submitting}
required
/>
</label>
{error && <p className="curator-login-error">{error}</p>}
<div className="curator-login-actions">
<button type="button" className="curator-login-cancel" onClick={onClose} disabled={submitting}>
Cancel
</button>
<button type="submit" className="curator-login-submit" disabled={submitting}>
{submitting ? 'Signing in…' : 'Sign in'}
</button>
</div>
</form>
</div>
</div>
);
}