evOo-local/apps/web/src/pages/McpOAuthCallback.tsx
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

77 lines
2.8 KiB
TypeScript

import { Trans, useLingui } from "@lingui/react/macro";
import { Button } from "@rakazo/ui-web";
import { useEffect, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { MCP_OAUTH_CHANNEL } from "../lib/mcp-connect";
import { rpc } from "../lib/rpc";
// The window.open name set by the OAuth popup flow. Providers whose login
// pages send COOP sever window.opener mid-flow, but the window name survives,
// so it is the reliable "we are the popup" marker.
const POPUP_NAME = MCP_OAUTH_CHANNEL;
export function McpOAuthCallbackPage() {
const { t } = useLingui();
const navigate = useNavigate();
const [params] = useSearchParams();
const [error, setError] = useState<string | null>(null);
const [done, setDone] = useState(false);
const handledState = useRef<string | null>(null);
useEffect(() => {
const code = params.get("code");
const state = params.get("state");
if (!code || !state) {
setError(params.get("error_description") ?? t`OAuth authorization was cancelled.`);
return;
}
if (handledState.current === state) return;
handledState.current = state;
void rpc.mcp.oauth
.complete({ sessionId: state, code, state })
.then(() => {
const channel = new BroadcastChannel(POPUP_NAME);
channel.postMessage({ type: "mcp-oauth-complete" });
channel.close();
if (window.name === POPUP_NAME) {
setDone(true);
window.close();
return;
}
navigate("/app?mcp_oauth=connected", { replace: true });
})
.catch((err: unknown) =>
setError(err instanceof Error ? err.message : t`Could not complete OAuth`),
);
}, [navigate, params, t]);
const showReturn = Boolean(error) && window.name !== POPUP_NAME;
return (
<div className="grid min-h-screen place-items-center bg-background p-6 text-center">
<div>
<div className="text-lg text-foreground">
{error ? (
<Trans>OAuth connection failed</Trans>
) : done ? (
<Trans>Connected</Trans>
) : (
<Trans>Finishing MCP connection…</Trans>
)}
</div>
{error ? <p className="mt-2 max-w-md text-sm text-muted-foreground">{error}</p> : null}
{showReturn ? (
<Button type="button" className="mt-5" onClick={() => navigate("/app")}>
<Trans>Return to Rakazo</Trans>
</Button>
) : (
<p className="mt-2 text-sm text-muted-foreground">
{error || done ? (
<Trans>You can close this window.</Trans>
) : (
<Trans>You can close this tab if it does not redirect automatically.</Trans>
)}
</p>
)}
</div>
</div>
);
}