feat(web): wire the SPA to the live bus via the gateway (drop mock)
Replace the mock data source with a real data layer that talks to the webgw gateway over REST + SSE. The UI components keep their look and props; only where the data comes from changed. - src/api.ts: the single repository layer. fetch wrappers (same-origin cookie) for login/logout/me and rooms list/create/join/send, plus streamRoom() which opens an EventSource and yields each decrypted message. Wire->UI mappers (roomFromWire, messageFromWire). - src/types.ts: add the gateway wire shapes (MeInfo, RoomWire, MsgWire) next to the existing UI types. - App.tsx: probe /api/me on mount to resume an existing session; otherwise show Login. Logout calls the gateway. - Login.tsx: the password field now unlocks the gateway session (operator passphrase); shows a basic error and a loading state. Wallet-per-browser is phase 2. - ChatShell.tsx: load rooms from /api/rooms with loading / empty / error states; same Flex layout. - ChatPanel.tsx: stream messages over SSE for the active room (dedup by id), composer sends through the gateway; no optimistic insert (the peer's own echo returns over SSE with the real frame id). - vite.config.ts: dev proxy /api (REST + SSE) -> the gateway on :8481. mock.ts is left untouched (no longer imported) to avoid churn with the parallel styling work on master. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+35
-2
@@ -1,11 +1,44 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Center, Loader } from "@mantine/core";
|
||||
import { Login } from "./Login";
|
||||
import { ChatShell } from "./ChatShell";
|
||||
import { api } from "./api";
|
||||
import type { User } from "./types";
|
||||
|
||||
// shortEndpoint hace legible el endpoint id del operador para mostrarlo como
|
||||
// handle por defecto cuando no se escribió uno en el login.
|
||||
function shortEndpoint(ep: string) {
|
||||
return ep.slice(0, 8);
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [checking, setChecking] = useState(true);
|
||||
|
||||
// Al montar, comprueba si ya hay una sesión viva en el gateway (cookie). Si la
|
||||
// hay, entra directo; si no (401), muestra el login.
|
||||
useEffect(() => {
|
||||
api
|
||||
.me()
|
||||
.then((me) =>
|
||||
setUser({ id: me.endpoint, handle: shortEndpoint(me.endpoint) }),
|
||||
)
|
||||
.catch(() => {})
|
||||
.finally(() => setChecking(false));
|
||||
}, []);
|
||||
|
||||
const logout = () => {
|
||||
void api.logout().catch(() => {});
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
if (checking) {
|
||||
return (
|
||||
<Center h="100vh" bg="dark.9">
|
||||
<Loader color="brand" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
if (!user) return <Login onLogin={setUser} />;
|
||||
return <ChatShell user={user} onLogout={() => setUser(null)} />;
|
||||
return <ChatShell user={user} onLogout={logout} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user