Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ app/
│ ├── home, calls, usage, keys, settings # auth-gated
│ ├── device, waitlist # device-approval + early-access
│ └── error.tsx
├── (auth)/ # /login, /signup — no sidebar
├── (auth)/ # /login, /signup — branded pages; hand off to Auth0; no sidebar
├── api/
│ ├── pymthouse/ # BFF: account-usage, keys, plans, subscribe, wallet, invoices
│ ├── mcp/ # Streamable HTTP MCP resource server
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ lib/
| `/apps/[id]` | public | App detail + playground |
| `/orgs/[slug]` | public | Organization's published apps |
| `/network` | public | Network stats (sidebar: "Stats") |
| `/login` | public | Sign in |
| `/signup` | public | Sign up |
| `/login` | public | Sign in (hands off to Auth0) |
| `/signup` | public | Sign up (hands off to Auth0) |

See `CLAUDE.md` for console conventions (KPI rows, tables, motion tokens, color rules).
7 changes: 3 additions & 4 deletions app/(app)/install/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
type PointerEvent as ReactPointerEvent,
type ReactNode,
} from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/components/console/AuthContext";
import { AUTH_SIGNIN_HREF } from "@/lib/console/auth-login";
import CopyButton from "@/components/console/CopyButton";
import HarnessLogo from "@/components/console/HarnessLogo";
import SectionHeader from "@/components/console/SectionHeader";
Expand Down Expand Up @@ -545,16 +545,15 @@ function McpServerUrl() {

export default function InstallPage() {
const { isConnected, isLoading } = useAuth();
const router = useRouter();

// Middleware already sends signed-out requests to /login before this page
// is served (see middleware.ts). This client-side fallback only fires if
// the session lapses while the console is open.
useEffect(() => {
if (!isLoading && !isConnected) {
router.replace("/login");
window.location.replace(AUTH_SIGNIN_HREF);
}
}, [isLoading, isConnected, router]);
}, [isLoading, isConnected]);

if (isLoading) return null;

Expand Down
16 changes: 8 additions & 8 deletions app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/components/console/AuthContext";
import { AUTH_SIGNIN_HREF } from "@/lib/console/auth-login";

// Root `/`:
// - signed in → redirect to /home (the console default)
// - signed out → redirect to /login (the console default entry point)
// Explore is still reachable at /explore for anyone who lands there directly;
// it just isn't the default landing any more.
// - signed out → /login
export default function RootPage() {
const { isConnected, isLoading, user } = useAuth();
const router = useRouter();

const signedIn = isConnected && !!user;

useEffect(() => {
if (isLoading) return;
router.replace(signedIn ? "/home" : "/login");
}, [isLoading, signedIn, router]);
if (signedIn) {
window.location.replace("/home");
return;
}
window.location.replace(AUTH_SIGNIN_HREF);
}, [isLoading, signedIn]);

// Nothing renders here — `/` is a pure redirect in both auth states.
return null;
}
3 changes: 2 additions & 1 deletion app/(app)/waitlist/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { auth0 } from "@/lib/auth0";
import ConsolePageHeader from "@/components/console/ConsolePageHeader";
import SectionHeader from "@/components/console/SectionHeader";
import { consoleSignInHref } from "@/lib/console/auth-login";
import { isEmailAllowlisted } from "@/lib/console/email-allowlist";

export const dynamic = "force-dynamic";
Expand All @@ -27,7 +28,7 @@ export default async function WaitlistPage() {
<p className="font-mono text-xs text-fg-muted">{email}</p>
) : (
<p className="text-sm text-fg-muted">
<a className="text-green-bright underline" href="/login">
<a className="text-green-bright underline" href={consoleSignInHref({ returnTo: "/waitlist" })}>
Sign in
</a>{" "}
to join the waitlist with your account email.
Expand Down
2 changes: 1 addition & 1 deletion app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function ConsoleAuthLayout({
}) {
return (
<AuthProvider>
<div className="min-h-screen overflow-x-clip overscroll-none bg-dark font-sans">
<div className="min-h-screen overflow-x-clip overscroll-none bg-background font-sans">
{children}
</div>
</AuthProvider>
Expand Down
24 changes: 17 additions & 7 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { redirect } from "next/navigation";

import { auth0 } from "@/lib/auth0";
import { authLoginHref, safeReturnTo } from "@/lib/console/auth-login";
import LoginPage from "@/components/console/LoginPage";

import type { Metadata } from "next";

export const metadata: Metadata = {
title: "Sign in — Livepeer Early Access",
};

const MCP_CALLBACK_PATH = "/api/mcp/oauth/callback";

export default async function LoginRoute({
searchParams
searchParams,
}: {
searchParams: Promise<{
mcp_oauth?: string;
returnTo?: string;
}>;
}) {
const params = await searchParams;
const mcpOauth = params.mcp_oauth === "1";
const returnTo = safeReturnTo(params.returnTo);

const session = await auth0.getSession();
if (session) {
if (mcpOauth) {
redirect(MCP_CALLBACK_PATH);
}
redirect("/home");
redirect(mcpOauth ? MCP_CALLBACK_PATH : returnTo);
}

// MCP flow must go directly to Auth0 — no interactive UI step.
if (mcpOauth) {
redirect(authLoginHref({ returnTo: MCP_CALLBACK_PATH }));
}

return <LoginPage returnTo={mcpOauth ? MCP_CALLBACK_PATH : "/home"} />;
return <LoginPage mode="signin" returnTo={returnTo} />;
}
21 changes: 15 additions & 6 deletions app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { auth0 } from "@/lib/auth0";
import { safeReturnTo } from "@/lib/console/auth-login";
import LoginPage from "@/components/console/LoginPage";

export default async function SignupRoute() {
const session = await auth0.getSession();
if (session) {
redirect("/home");
}
export const metadata: Metadata = {
title: "Sign up — Livepeer Early Access",
};

return <LoginPage initialMode="signup" />;
export default async function SignupRoute({
searchParams,
}: {
searchParams: Promise<{ returnTo?: string }>;
}) {
const params = await searchParams;
const returnTo = safeReturnTo(params.returnTo);
const session = await auth0.getSession();
if (session) redirect(returnTo);
return <LoginPage mode="signup" returnTo={returnTo} />;
}
4 changes: 2 additions & 2 deletions app/api/mcp/oauth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export async function GET(req: NextRequest) {
const session = await auth0.getSession();
const sub = session?.user?.sub?.trim();
if (!session || !sub) {
const login = new URL("/login", origin);
login.searchParams.set("mcp_oauth", "1");
const login = new URL("/auth/login", origin);
login.searchParams.set("returnTo", "/api/mcp/oauth/callback");
return NextResponse.redirect(login);
}

Expand Down
2 changes: 1 addition & 1 deletion app/authorize/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export async function GET(req: NextRequest) {
return json(req, 503, { error: "temporarily_unavailable" });
}

const login = consoleLoginUrl(req, nonce);
const login = consoleLoginUrl(req);
const response = NextResponse.redirect(login, 302);
response.cookies.set(PKCE_COOKIE, pending, pkceCookieOptions());
for (const [k, v] of Object.entries(corsHeaders(req))) {
Expand Down
8 changes: 8 additions & 0 deletions branding/auth0/branding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"logo_url": "https://livepeer-console.vercel.app/icon.svg",
"favicon_url": "https://livepeer-console.vercel.app/icon.svg",
"colors": {
"primary": "#111111",
"page_background": "#FFFFFF"
}
}
7 changes: 7 additions & 0 deletions branding/auth0/text-login-en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"login": {
"title": "Log in to Livepeer Early Access",
"description": " ",
"buttonText": "Sign in"
}
}
7 changes: 7 additions & 0 deletions branding/auth0/text-signup-en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"signup": {
"title": "Get started with Livepeer",
"description": " ",
"buttonText": "Sign up"
}
}
6 changes: 6 additions & 0 deletions branding/auth0/text-signup-id-en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"signup-id": {
"title": "Get started with Livepeer",
"description": " "
}
}
6 changes: 6 additions & 0 deletions branding/auth0/text-signup-password-en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"signup-password": {
"title": "Get started with Livepeer",
"description": " "
}
}
57 changes: 57 additions & 0 deletions branding/auth0/theme.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"displayName": "Livepeer Console",
"colors": {
"primary_button": "#111111",
"primary_button_label": "#FFFFFF",
"secondary_button_border": "#E4E4E4",
"secondary_button_label": "#111111",
"base_focus_color": "#40BF86",
"base_hover_color": "#F5F5F5",
"links_focused_components": "#40BF86",
"header": "#111111",
"body_text": "#111111",
"widget_background": "#FFFFFF",
"widget_border": "#E4E4E4",
"input_labels_placeholders": "#9CA3AF",
"input_filled_text": "#111111",
"input_border": "#E4E4E4",
"input_background": "#FFFFFF",
"icons": "#9CA3AF",
"error": "#EF4444",
"success": "#40BF86"
},
"fonts": {
"font_url": "https://rsms.me/inter/font-files/InterVariable.woff2",
"links_style": "normal",
"reference_text_size": 16,
"title": { "size": 112.5, "bold": false },
"subtitle": { "size": 87.5, "bold": false },
"body_text": { "size": 87.5, "bold": false },
"buttons_text": { "size": 87.5, "bold": false },
"input_labels": { "size": 87.5, "bold": false },
"links": { "size": 75, "bold": false }
},
"borders": {
"button_border_weight": 0,
"buttons_style": "rounded",
"button_border_radius": 4,
"input_border_weight": 1,
"inputs_style": "rounded",
"input_border_radius": 4,
"widget_corner_radius": 4,
"widget_border_weight": 1,
"show_widget_shadow": true
},
"widget": {
"logo_position": "center",
"logo_url": "https://livepeer-console.vercel.app/icon.svg",
"logo_height": 36,
"header_text_alignment": "center",
"social_buttons_layout": "bottom"
},
"page_background": {
"background_color": "#FFFFFF",
"background_image_url": "",
"page_layout": "center"
}
}
25 changes: 14 additions & 11 deletions components/console/ConsolePageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
"use client";

import type { ReactNode } from "react";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useAuth } from "@/components/console/AuthContext";
import {
AUTH_SIGNIN_HREF,
AUTH_SIGNUP_HREF,
isConsoleAuthPath,
} from "@/lib/console/auth-login";
import ScopeChip, { type PageScope } from "@/components/console/ScopeChip";

interface ConsolePageHeaderProps {
Expand Down Expand Up @@ -34,8 +38,8 @@ interface ConsolePageHeaderProps {
* and right-aligned actions. Information density comes from the content; this
* bar is pure chrome and stays out of the way.
*
* When the user is signed out (and not already on the `/login`
* auth route), a `Sign in` / `Sign up` pair is appended to the right
* When the user is signed out (and not already on /login or /signup),
* a `Sign in` / `Sign up` pair is appended to the right
* actions cluster — mirrors the design prototype's `PageHead` injection
* (`auth.authed === false && !auth.isAuthRoute`). A faint divider sits
* between the page's own actions and the auth CTAs when both exist.
Expand All @@ -49,8 +53,7 @@ export default function ConsolePageHeader({
}: ConsolePageHeaderProps) {
const { isConnected, isLoading } = useAuth();
const pathname = usePathname() ?? "";
const isAuthRoute =
pathname.startsWith("/login") || pathname.startsWith("/signup");
const isAuthRoute = isConsoleAuthPath(pathname);
// Hide auth CTAs while auth state is still resolving (one frame on first
// paint) to avoid flashing them in for connected users.
const showAuthCTAs = !isLoading && !isConnected && !isAuthRoute;
Expand Down Expand Up @@ -84,18 +87,18 @@ export default function ConsolePageHeader({
)}
{showAuthCTAs && (
<>
<Link
href="/login"
<a
href={AUTH_SIGNIN_HREF}
className="inline-flex h-[26px] items-center rounded-[4px] px-2.5 text-[12.5px] text-fg-strong transition-colors hover:bg-hover hover:text-fg"
>
Sign in
</Link>
<Link
href="/signup"
</a>
<a
href={AUTH_SIGNUP_HREF}
className="btn-primary inline-flex h-[26px] items-center rounded-[4px] px-2.5 text-[12.5px] font-medium transition-colors"
>
Sign up
</Link>
</a>
</>
)}
</div>
Expand Down
Loading