diff --git a/frontend/src/app/agents/[agentId]/edit/page.tsx b/frontend/src/app/agents/[agentId]/edit/page.tsx index 377a775..36bbc35 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 29706e3..cfe0731 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 92fec06..69c0378 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 ea5cb45..e718e27 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 a362a64..1bf60bd 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 a3f9813..7f147ac 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 11a24cc..c5bfcd8 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 c9fb9ae..2d851d3 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 2234ef8..b4c96a6 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 be3ef59..f32dfce 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 9cd78a8..3304679 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 a8f4133..3aad93b 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 f310fd7..dea970c 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 ca5ac18..b60c0ad 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 21bd936..24a934c 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 0000000..d5ee1dc --- /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 0ed470d..264d73c 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 38db84f..349bd48 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 0918924..661fbea 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 ace048a..04b516a 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 59760fc..32d6af7 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 7543980..41a0eb8 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: [