Files
vibn-agent-runner/vibn-frontend/app/components/auth/AuthFlow.tsx

246 lines
7.7 KiB
TypeScript

"use client";
import { useSession } from "next-auth/react";
import { useRouter, useSearchParams } from "next/navigation";
import React, { useEffect, Suspense } from "react";
import AuthScreen from "./AuthScreen";
function AuthFlowInner({ mode }: { mode: "signin" | "signup" }) {
const { data: session, status } = useSession();
const router = useRouter();
const searchParams = useSearchParams();
const [ssoProcessing, setSsoProcessing] = React.useState(false);
const [ssoToken, setSsoToken] = React.useState<string | null>(null);
const [routing, setRouting] = React.useState(false);
useEffect(() => {
if (status === "authenticated" && session?.user?.email) {
setRouting(true);
const isVibnCodeSSO = searchParams?.get("vibncode") === "true";
if (isVibnCodeSSO) {
setSsoProcessing(true);
// Call our secure token endpoint
fetch("/api/auth/token")
.then((r) => r.json())
.then((data) => {
if (data.token) {
setSsoToken(data.token);
// Deep-link redirect back to the VibnCode desktop app
window.location.href = `vibncode://auth/callback?token=${data.token}`;
} else {
console.error("SSO Token missing from response", data);
setSsoProcessing(false);
}
})
.catch((err) => {
console.error("Desktop SSO failed:", err);
setSsoProcessing(false);
});
return;
}
// Resolve the user's ACTUAL workspace slug (it can differ from their
// email — e.g. after they renamed it in onboarding) and decide where to
// land. Onboarding shows only once: gate on the onboardingComplete flag,
// with project count as a fallback for users who onboarded before the
// flag existed.
Promise.all([
fetch("/api/workspaces")
.then((r) => r.json())
.catch(() => ({})),
fetch("/api/projects")
.then((r) => r.json())
.catch(() => ({})),
]).then(([ws, proj]) => {
const slug: string | undefined = ws?.workspaces?.[0]?.slug;
const onboarded = ws?.onboarded === true;
const hasProjects =
Array.isArray(proj?.projects) && proj.projects.length > 0;
if (slug && (onboarded || hasProjects)) {
router.push(`/${slug}/projects`);
} else {
router.push("/onboarding");
}
});
}
}, [status, session, router, searchParams]);
if (status === "loading" || ssoProcessing || routing) {
return (
<div
className="new-site-wrapper"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
background:
"radial-gradient(circle at 20% 20%, #1c1c1f, #0b0b0f 60%)",
}}
>
{ssoToken ? (
<div
style={{
border: "1px solid rgba(255, 255, 255, 0.08)",
background: "rgba(12, 12, 16, 0.85)",
borderRadius: "20px",
padding: "32px",
boxShadow: "0 18px 50px rgba(0, 0, 0, 0.35)",
backdropFilter: "blur(16px)",
textAlign: "center",
width: "min(480px, 90vw)",
color: "#f5f5f5",
fontFamily: "-apple-system, sans-serif",
}}
>
<div
style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: "56px",
height: "56px",
borderRadius: "50%",
border: "1px solid rgba(255, 255, 255, 0.12)",
background:
"linear-gradient(135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02))",
fontSize: "28px",
marginBottom: "20px",
}}
>
</div>
<h1
style={{
margin: "0 0 12px",
fontSize: "24px",
fontWeight: "600",
color: "#f8f8f8",
}}
>
Authentication Successful
</h1>
<p
style={{ margin: "0 0 24px", color: "#cfcfd4", fontSize: "14px" }}
>
Signed in. Redirecting to VibnCode...
</p>
<div
style={{
margin: "0 auto 20px",
width: "44px",
height: "44px",
borderRadius: "50%",
border: "4px solid rgba(255, 255, 255, 0.15)",
borderTopColor: "#ffffff",
animation: "spin 1s linear infinite",
}}
/>
<style>{`
@keyframes spin { to { transform: rotate(360deg); } }
`}</style>
<p
style={{
margin: "0 0 16px",
color: "#b6b6bd",
lineHeight: "1.6",
fontSize: "13px",
}}
>
If the app doesn&apos;t open automatically, copy your Workspace
API Key below and paste it into the connection card.
</p>
<div
style={{
display: "flex",
gap: "10px",
justifyContent: "center",
marginBottom: "16px",
}}
>
<button
type="button"
onClick={() => {
navigator.clipboard.writeText(ssoToken);
alert("Workspace API Key copied!");
}}
style={{
padding: "10px 16px",
borderRadius: "999px",
border: "1px solid rgba(255, 255, 255, 0.18)",
background: "rgba(255, 255, 255, 0.12)",
color: "#ffffff",
cursor: "pointer",
fontSize: "13px",
fontWeight: "500",
}}
>
Copy Workspace Key
</button>
</div>
<div
style={{
padding: "12px",
borderRadius: "12px",
background: "rgba(255, 255, 255, 0.06)",
fontFamily: "monospace",
fontSize: "12px",
wordBreak: "break-all",
color: "#d8d8df",
}}
>
{ssoToken}
</div>
</div>
) : (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "16px",
}}
>
<div
style={{
width: "24px",
height: "24px",
borderRadius: "50%",
border: "2px solid oklch(0.20 0.009 60)",
borderTopColor: "var(--accent)",
animation: "spin .9s linear infinite",
}}
/>
<style>{`
@keyframes spin { to { transform: rotate(360deg); } }
`}</style>
<div
style={{
color: "var(--fg-mute)",
fontFamily: "var(--font-mono)",
fontSize: "11px",
letterSpacing: "0.1em",
textTransform: "uppercase",
}}
>
Checking session
</div>
</div>
)}
</div>
);
}
return <AuthScreen mode={mode} />;
}
export default function AuthFlow({ mode }: { mode: "signin" | "signup" }) {
return (
<Suspense>
<AuthFlowInner mode={mode} />
</Suspense>
);
}