feat(web): dedicated CLI auth page with inline login/register

No more redirect to generic /auth/login. The /cli-auth?code=XXXX page
now shows auth forms inline (Google, GitHub, email) with device code
context — like Anthropic's "Build with Claude" page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-13 08:51:18 +01:00
parent ca441dae45
commit a365fef170
2 changed files with 85 additions and 16 deletions

View File

@@ -0,0 +1,60 @@
"use client";
import { SocialProvider } from "@turbostarter/auth";
import { authConfig } from "~/config/auth";
import { SocialProviders } from "~/modules/auth/form/social-providers";
import { RegisterForm } from "~/modules/auth/form/register-form";
import { AuthDivider } from "~/modules/auth/layout/divider";
interface Props {
code: string;
}
export function CliAuthLogin({ code }: Props) {
const redirectTo = `/cli-auth?code=${encodeURIComponent(code)}`;
return (
<div className="w-full max-w-md space-y-8 p-8">
{/* Header */}
<div className="text-center space-y-3">
<div
className="mx-auto w-14 h-14 rounded-2xl flex items-center justify-center text-2xl font-bold"
style={{ background: "var(--cm-accent, #f97316)", color: "#000" }}
>
cm
</div>
<h1 className="text-2xl font-bold tracking-tight">
Connect to claudemesh CLI
</h1>
<p className="text-sm" style={{ color: "var(--cm-fg-muted, #888)" }}>
Sign in or create an account to connect your terminal session.
</p>
</div>
{/* Social providers */}
<SocialProviders
providers={authConfig.providers.oAuth as SocialProvider[]}
redirectTo={redirectTo}
/>
<AuthDivider />
{/* Email + password form */}
<RegisterForm redirectTo={redirectTo} />
{/* Device code footer */}
<div className="pt-2 text-center">
<div
className="inline-block rounded-lg px-4 py-2 font-mono text-sm tracking-widest"
style={{ background: "var(--cm-bg-muted, #1a1a1a)", color: "var(--cm-fg-muted, #888)" }}
>
{code}
</div>
<p className="mt-2 text-xs" style={{ color: "var(--cm-fg-muted, #666)" }}>
Confirm this code matches your terminal
</p>
</div>
</div>
);
}

View File

@@ -1,14 +1,13 @@
import { redirect } from "next/navigation";
import { getSession } from "~/lib/auth/server";
import { getMetadata } from "~/lib/metadata";
import { CliAuthFlow } from "./cli-auth-flow";
import { DeviceCodeApproval } from "./device-code-approval";
import { CliAuthLogin } from "./cli-auth-login";
export const generateMetadata = getMetadata({
title: "Sync with CLI",
description: "Link your claudemesh CLI to your account.",
title: "Connect CLI",
description: "Sign in to connect your claudemesh CLI.",
});
export default async function CliAuthPage({
@@ -17,24 +16,24 @@ export default async function CliAuthPage({
searchParams: Promise<{ code?: string; port?: string }>;
}) {
const { user } = await getSession();
if (!user) {
const sp = await searchParams;
const qs = new URLSearchParams();
if (sp.code) qs.set("code", sp.code);
if (sp.port) qs.set("port", sp.port);
const returnTo = `/cli-auth${qs.size ? `?${qs}` : ""}`;
return redirect(`/auth/login?redirectTo=${encodeURIComponent(returnTo)}`);
}
const { code, port } = await searchParams;
// Device-code flow: code contains "-" (e.g. "ABCD-EFGH"), no port
const isDeviceCode = code && code.includes("-") && !port;
if (isDeviceCode) {
if (!user) {
// NOT logged in → show inline auth form with device code context
return (
<main className="min-h-screen bg-[var(--cm-bg,#0a0a0a)] text-[var(--cm-fg,#fafafa)] antialiased flex items-center justify-center">
<CliAuthLogin code={code} />
</main>
);
}
// Logged in → auto-approve
return (
<main className="min-h-screen bg-[var(--cm-bg)] text-[var(--cm-fg)] antialiased flex items-center justify-center">
<main className="min-h-screen bg-[var(--cm-bg,#0a0a0a)] text-[var(--cm-fg,#fafafa)] antialiased flex items-center justify-center">
<DeviceCodeApproval
code={code}
userName={user.name ?? user.email}
@@ -43,9 +42,19 @@ export default async function CliAuthPage({
);
}
// Legacy callback flow (port-based)
if (!user) {
const { redirect } = await import("next/navigation");
const qs = new URLSearchParams();
if (code) qs.set("code", code);
if (port) qs.set("port", port);
const returnTo = `/cli-auth${qs.size ? `?${qs}` : ""}`;
return redirect(`/auth/login?redirectTo=${encodeURIComponent(returnTo)}`);
}
return (
<main
className="min-h-screen bg-[var(--cm-bg)] text-[var(--cm-fg)] antialiased"
className="min-h-screen bg-[var(--cm-bg,#0a0a0a)] text-[var(--cm-fg,#fafafa)] antialiased"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
<CliAuthFlow