import { useEffect, useState } from "react"; import { Center, Loader } from "@mantine/core"; import { ChatShell } from "./ChatShell"; import { Join } from "./Join"; import { Recover } from "./Recover"; import { WalletLogin } from "./WalletLogin"; import { Welcome } from "./Welcome"; import { api } from "./api"; import { localIdentity } from "./wallet/account"; import type { User } from "./types"; type Route = "loading" | "join" | "welcome" | "login" | "recover" | "chat"; // readJoinToken returns the invite token if the current URL is /join?token=XXX. function readJoinToken(): string | null { if (window.location.pathname !== "/join") return null; return new URLSearchParams(window.location.search).get("token"); } // clearUrl drops any /join?token from the address bar once consumed, so a refresh // or a shared screenshot does not replay the (single-use) token. function clearUrl() { if (window.location.pathname !== "/") { window.history.replaceState(null, "", "/"); } } export function App() { const [route, setRoute] = useState("loading"); const [user, setUser] = useState(null); const [token, setToken] = useState(""); const [storedHandle, setStoredHandle] = useState(""); // Decide the entry screen on mount: an invite link goes straight to join; a live // gateway session resumes the chat; a device with a stored identity shows the // password unlock; an empty device shows the welcome chooser. useEffect(() => { const t = readJoinToken(); if (t) { setToken(t); setRoute("join"); return; } let cancelled = false; (async () => { try { const me = await api.me(); if (cancelled) return; setUser({ id: me.endpoint, handle: me.handle || me.endpoint.slice(0, 8) }); setRoute("chat"); return; } catch { // no live session — fall through } const stored = await localIdentity(); if (cancelled) return; if (stored) { setStoredHandle(stored.handle); setRoute("login"); } else { setRoute("welcome"); } })(); return () => { cancelled = true; }; }, []); const enterChat = (u: User) => { setUser(u); setRoute("chat"); clearUrl(); }; const logout = () => { void api.logout().catch(() => {}); setUser(null); // Keep the encrypted identity on the device: logging out returns to the // password unlock, not a full reset. void localIdentity().then((stored) => { if (stored) { setStoredHandle(stored.handle); setRoute("login"); } else { setRoute("welcome"); } }); }; switch (route) { case "loading": return (
); case "join": return ( setRoute("recover")} /> ); case "welcome": return ( { setToken(t); setRoute("join"); }} onRecover={() => setRoute("recover")} /> ); case "login": return ( setRoute("recover")} /> ); case "recover": return ( setRoute(storedHandle ? "login" : "welcome")} /> ); case "chat": return user ? ( ) : (
); } }