246 lines
7.7 KiB
TypeScript
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'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>
|
|
);
|
|
}
|