diff --git a/frontend/src/app/agents/[agentId]/edit/page.tsx b/frontend/src/app/agents/[agentId]/edit/page.tsx index 377a775c..36bbc357 100644 --- a/frontend/src/app/agents/[agentId]/edit/page.tsx +++ b/frontend/src/app/agents/[agentId]/edit/page.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { diff --git a/frontend/src/app/agents/[agentId]/page.tsx b/frontend/src/app/agents/[agentId]/page.tsx index 29706e31..cfe07319 100644 --- a/frontend/src/app/agents/[agentId]/page.tsx +++ b/frontend/src/app/agents/[agentId]/page.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { diff --git a/frontend/src/app/agents/new/page.tsx b/frontend/src/app/agents/new/page.tsx index 92fec06f..69c0378d 100644 --- a/frontend/src/app/agents/new/page.tsx +++ b/frontend/src/app/agents/new/page.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx index ea5cb450..e718e27e 100644 --- a/frontend/src/app/agents/page.tsx +++ b/frontend/src/app/agents/page.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { type ColumnDef, type SortingState, diff --git a/frontend/src/app/boards/[boardId]/approvals/page.tsx b/frontend/src/app/boards/[boardId]/approvals/page.tsx index a362a64d..1bf60bde 100644 --- a/frontend/src/app/boards/[boardId]/approvals/page.tsx +++ b/frontend/src/app/boards/[boardId]/approvals/page.tsx @@ -2,7 +2,7 @@ import { useParams } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut } from "@/auth/clerk"; import { BoardApprovalsPanel } from "@/components/BoardApprovalsPanel"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; diff --git a/frontend/src/app/boards/[boardId]/edit/page.tsx b/frontend/src/app/boards/[boardId]/edit/page.tsx index a3f98133..7f147ac6 100644 --- a/frontend/src/app/boards/[boardId]/edit/page.tsx +++ b/frontend/src/app/boards/[boardId]/edit/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { X } from "lucide-react"; import { ApiError } from "@/api/mutator"; diff --git a/frontend/src/app/boards/[boardId]/page.tsx b/frontend/src/app/boards/[boardId]/page.tsx index 11a24cc3..c5bfcd8a 100644 --- a/frontend/src/app/boards/[boardId]/page.tsx +++ b/frontend/src/app/boards/[boardId]/page.tsx @@ -3,7 +3,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { Activity, ArrowUpRight, diff --git a/frontend/src/app/boards/new/page.tsx b/frontend/src/app/boards/new/page.tsx index c9fb9ae6..2d851d39 100644 --- a/frontend/src/app/boards/new/page.tsx +++ b/frontend/src/app/boards/new/page.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { useCreateBoardApiV1BoardsPost } from "@/api/generated/boards/boards"; diff --git a/frontend/src/app/boards/page.tsx b/frontend/src/app/boards/page.tsx index 2234ef87..b4c96a67 100644 --- a/frontend/src/app/boards/page.tsx +++ b/frontend/src/app/boards/page.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { type ColumnDef, flexRender, diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index be3ef59d..f32dfce9 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -2,7 +2,7 @@ import { useMemo } from "react"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { Area, AreaChart, diff --git a/frontend/src/app/gateways/[gatewayId]/edit/page.tsx b/frontend/src/app/gateways/[gatewayId]/edit/page.tsx index 9cd78a80..33046791 100644 --- a/frontend/src/app/gateways/[gatewayId]/edit/page.tsx +++ b/frontend/src/app/gateways/[gatewayId]/edit/page.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { CheckCircle2, RefreshCcw, XCircle } from "lucide-react"; import { ApiError } from "@/api/mutator"; diff --git a/frontend/src/app/gateways/[gatewayId]/page.tsx b/frontend/src/app/gateways/[gatewayId]/page.tsx index a8f4133c..3aad93bd 100644 --- a/frontend/src/app/gateways/[gatewayId]/page.tsx +++ b/frontend/src/app/gateways/[gatewayId]/page.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { useParams, useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { ApiError } from "@/api/mutator"; import { diff --git a/frontend/src/app/gateways/new/page.tsx b/frontend/src/app/gateways/new/page.tsx index f310fd73..dea970cc 100644 --- a/frontend/src/app/gateways/new/page.tsx +++ b/frontend/src/app/gateways/new/page.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { CheckCircle2, RefreshCcw, XCircle } from "lucide-react"; import { ApiError } from "@/api/mutator"; diff --git a/frontend/src/app/gateways/page.tsx b/frontend/src/app/gateways/page.tsx index ca5ac186..b60c0adb 100644 --- a/frontend/src/app/gateways/page.tsx +++ b/frontend/src/app/gateways/page.tsx @@ -3,7 +3,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; -import { SignInButton, SignedIn, SignedOut, useAuth } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth } from "@/auth/clerk"; import { type ColumnDef, type SortingState, diff --git a/frontend/src/app/onboarding/page.tsx b/frontend/src/app/onboarding/page.tsx index 21bd9361..24a934ce 100644 --- a/frontend/src/app/onboarding/page.tsx +++ b/frontend/src/app/onboarding/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; -import { SignInButton, SignedIn, SignedOut, useAuth, useUser } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut, useAuth, useUser } from "@/auth/clerk"; import { Globe, Info, RotateCcw, Save, User } from "lucide-react"; import { ApiError } from "@/api/mutator"; diff --git a/frontend/src/auth/clerk.tsx b/frontend/src/auth/clerk.tsx new file mode 100644 index 00000000..d5ee1dca --- /dev/null +++ b/frontend/src/auth/clerk.tsx @@ -0,0 +1,74 @@ +"use client"; + +import type { ReactNode } from "react"; + +// NOTE: We intentionally keep this file very small and dependency-free. +// It provides CI/secretless-build safe fallbacks for Clerk hooks/components. + +import { + ClerkProvider, + SignedIn as ClerkSignedIn, + SignedOut as ClerkSignedOut, + SignInButton as ClerkSignInButton, + SignOutButton as ClerkSignOutButton, + useAuth as clerkUseAuth, + useUser as clerkUseUser, +} from "@clerk/nextjs"; + +export function isClerkEnabled(): boolean { + const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; + if (!key) return false; + + // Clerk validates publishable key contents at runtime; use a conservative heuristic. + const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key); + if (!m) return false; + const body = m[2]; + if (body.length < 16) return false; + if (/^0+$/.test(body)) return false; + return true; +} + +export function SignedIn(props: { children: ReactNode }) { + if (!isClerkEnabled()) return null; + return {props.children}; +} + +export function SignedOut(props: { children: ReactNode }) { + if (!isClerkEnabled()) return <>{props.children}; + return {props.children}; +} + +// Accept arbitrary Clerk component props so existing call sites don't need edits. +export function SignInButton(props: any) { + if (!isClerkEnabled()) return null; + return ; +} + +export function SignOutButton(props: any) { + if (!isClerkEnabled()) return null; + return ; +} + +export function useUser() { + if (!isClerkEnabled()) { + return { isLoaded: true, isSignedIn: false, user: null } as const; + } + return clerkUseUser(); +} + +export function useAuth() { + if (!isClerkEnabled()) { + return { + isLoaded: true, + isSignedIn: false, + userId: null, + sessionId: null, + getToken: async () => null, + } as const; + } + return clerkUseAuth(); +} + +// Re-export ClerkProvider for places that want to mount it, but strongly prefer +// gating via isClerkEnabled() at call sites. +export { ClerkProvider }; diff --git a/frontend/src/components/BoardApprovalsPanel.tsx b/frontend/src/components/BoardApprovalsPanel.tsx index 0ed470d4..264d73c5 100644 --- a/frontend/src/components/BoardApprovalsPanel.tsx +++ b/frontend/src/components/BoardApprovalsPanel.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from "react"; -import { useAuth } from "@clerk/nextjs"; +import { useAuth } from "@/auth/clerk"; import { useQueryClient } from "@tanstack/react-query"; import { Clock } from "lucide-react"; diff --git a/frontend/src/components/organisms/LandingHero.tsx b/frontend/src/components/organisms/LandingHero.tsx index 38db84f0..349bd48a 100644 --- a/frontend/src/components/organisms/LandingHero.tsx +++ b/frontend/src/components/organisms/LandingHero.tsx @@ -1,6 +1,6 @@ "use client"; -import { SignInButton, SignedIn, SignedOut } from "@clerk/nextjs"; +import { SignInButton, SignedIn, SignedOut } from "@/auth/clerk"; import { HeroCopy } from "@/components/molecules/HeroCopy"; import { Button } from "@/components/ui/button"; diff --git a/frontend/src/components/organisms/UserMenu.tsx b/frontend/src/components/organisms/UserMenu.tsx index 09189244..661fbeab 100644 --- a/frontend/src/components/organisms/UserMenu.tsx +++ b/frontend/src/components/organisms/UserMenu.tsx @@ -1,7 +1,7 @@ "use client"; import Image from "next/image"; -import { SignOutButton, useUser } from "@clerk/nextjs"; +import { SignOutButton, useUser } from "@/auth/clerk"; import { LogOut } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; diff --git a/frontend/src/components/templates/DashboardShell.tsx b/frontend/src/components/templates/DashboardShell.tsx index ace048aa..04b516aa 100644 --- a/frontend/src/components/templates/DashboardShell.tsx +++ b/frontend/src/components/templates/DashboardShell.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; -import { SignedIn, useUser } from "@clerk/nextjs"; +import { SignedIn, useUser } from "@/auth/clerk"; import { BrandMark } from "@/components/atoms/BrandMark"; import { UserMenu } from "@/components/organisms/UserMenu"; diff --git a/frontend/src/components/templates/LandingShell.tsx b/frontend/src/components/templates/LandingShell.tsx index 59760fc4..32d6af7a 100644 --- a/frontend/src/components/templates/LandingShell.tsx +++ b/frontend/src/components/templates/LandingShell.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react"; -import { SignedIn } from "@clerk/nextjs"; +import { SignedIn } from "@/auth/clerk"; import { BrandMark } from "@/components/atoms/BrandMark"; import { UserMenu } from "@/components/organisms/UserMenu"; diff --git a/frontend/src/proxy.ts b/frontend/src/proxy.ts index 7543980c..41a0eb80 100644 --- a/frontend/src/proxy.ts +++ b/frontend/src/proxy.ts @@ -1,6 +1,18 @@ +import { NextResponse } from "next/server"; import { clerkMiddleware } from "@clerk/nextjs/server"; -export default clerkMiddleware(); +const isClerkEnabled = () => { + const key = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY; + if (!key) return false; + const m = /^pk_(test|live)_([A-Za-z0-9]+)$/.exec(key); + if (!m) return false; + const body = m[2]; + if (body.length < 16) return false; + if (/^0+$/.test(body)) return false; + return true; +}; + +export default isClerkEnabled() ? clerkMiddleware() : () => NextResponse.next(); export const config = { matcher: [