61 lines
2.9 KiB
TypeScript
61 lines
2.9 KiB
TypeScript
// Гостевая страница /g/<токен>: имя → сессия гостя (без прав модератора) → звонок.
|
||
import { useCallback, useEffect, useState } from "react";
|
||
import { api } from "./api";
|
||
import { CallWithSession } from "./Call";
|
||
import { Icon } from "./Icon";
|
||
|
||
const NAME_KEY = "calls_guest_name_v1";
|
||
|
||
export function GuestPage({ token }: { token: string }) {
|
||
const sessionKey = "calls_guest_session_v1:" + token.slice(0, 12);
|
||
const [state, setState] = useState<"checking" | "ok" | "bad">("checking");
|
||
const [name, setName] = useState(() => { try { return localStorage.getItem(NAME_KEY) ?? ""; } catch { return ""; } });
|
||
const [session, setSession] = useState<string | null>(() => sessionStorage.getItem(sessionKey));
|
||
const [err, setErr] = useState("");
|
||
const [busy, setBusy] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (session) return;
|
||
api.guestCheck(token).then(() => setState("ok")).catch(() => setState("bad"));
|
||
}, [token, session]);
|
||
|
||
const join = async () => {
|
||
const n = name.trim();
|
||
if (!n) { setErr("Вкажіть ім'я"); return; }
|
||
setBusy(true); setErr("");
|
||
try {
|
||
const r = await api.guestJoin(token, n);
|
||
try { localStorage.setItem(NAME_KEY, n); } catch { /* без сховища */ }
|
||
sessionStorage.setItem(sessionKey, r.session);
|
||
setSession(r.session);
|
||
} catch (e) {
|
||
if ((e as { status?: number }).status === 404) setState("bad");
|
||
else setErr((e as Error).message);
|
||
} finally { setBusy(false); }
|
||
};
|
||
|
||
const lost = useCallback((m: string) => { sessionStorage.removeItem(sessionKey); setSession(null); setErr(m); }, [sessionKey]);
|
||
|
||
if (session) return <CallWithSession session={session} onSessionLost={lost} />;
|
||
return (
|
||
<main id="main"><div className="page">
|
||
<div className="head"><div><h1>Дзвінок</h1><div className="sub">Запрошення від <b>CRM Tvorymo</b></div></div></div>
|
||
{state === "checking" && <div className="empty">Перевірка посилання…</div>}
|
||
{state === "bad" && <div className="pcard"><div className="t">Посилання недійсне або застаріло.<small>Попросіть того, хто вас запросив, надіслати нове посилання.</small></div></div>}
|
||
{state === "ok" && <div className="pcard">
|
||
<div className="f">
|
||
<label htmlFor="gname">Ваше ім'я</label>
|
||
<div className="in big">
|
||
<input id="gname" value={name} maxLength={60} autoComplete="name" autoFocus
|
||
onChange={(e) => setName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") void join(); }} />
|
||
</div>
|
||
{err && <div className="err">{err}</div>}
|
||
</div>
|
||
<div className="row wrap mt-14">
|
||
<button className="btn gold" disabled={busy} onClick={() => void join()}><Icon name="video" />Приєднатися</button>
|
||
</div>
|
||
</div>}
|
||
</div></main>
|
||
);
|
||
}
|