feat(demo): login trigger, feature gating + admin UI (HU/EN/DE)
Login page quietly probes /auth/demo (debounced) as a valid email is typed/pasted and reloads into the app on a match — no visible button. Demo sessions default to the whole library, get a one-time shared-account warning, never see the YouTube-connect onboarding/access UI or sync actions, and admins get a demo whitelist + reset panel in Settings.
This commit is contained in:
parent
747c8d20ce
commit
281062fe33
11 changed files with 307 additions and 62 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { api } from "../lib/api";
|
||||
import { setLanguage, type LangCode } from "../i18n";
|
||||
|
|
@ -6,6 +6,8 @@ import LanguageSwitcher from "./LanguageSwitcher";
|
|||
|
||||
type Phase = "idle" | "sending" | "requested" | "approved" | "error";
|
||||
|
||||
const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
||||
|
||||
export default function Login() {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
|
|
@ -18,6 +20,28 @@ export default function Login() {
|
|||
const [phase, setPhase] = useState<Phase>(bounced === "requested" ? "requested" : "idle");
|
||||
const [error, setError] = useState("");
|
||||
|
||||
// Hidden demo entry: a whitelisted email signs straight into the shared demo account, with
|
||||
// no button to give it away. We watch the field and — once it holds a syntactically valid
|
||||
// email and typing has settled — quietly probe /auth/demo. A match sets the session, so we
|
||||
// reload into the app; a non-match does nothing visible (the normal "request access" flow
|
||||
// stays available). The server is rate-limited and answers uniformly, so this can't be
|
||||
// hammered as an oracle. Skipped while the request-access form is mid-submit/done.
|
||||
useEffect(() => {
|
||||
const candidate = email.trim().toLowerCase();
|
||||
const settled = phase === "requested" || phase === "approved";
|
||||
if (phase === "sending" || settled || !EMAIL_RE.test(candidate)) return;
|
||||
const timer = setTimeout(() => {
|
||||
api
|
||||
.demoLogin(candidate)
|
||||
.then((r) => {
|
||||
if (r.authenticated) window.location.reload();
|
||||
})
|
||||
.catch(() => {});
|
||||
}, 700);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [email, phase]);
|
||||
|
||||
async function submit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setError("");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue