From 029e803fb0f1df8c0a9dca530e7b1549f2e1cdcf Mon Sep 17 00:00:00 2001
From: superezz <248160976+superezz@users.noreply.github.com>
Date: Tue, 4 Aug 2026 22:49:48 +0530
Subject: [PATCH] feat: implement Clerk authentication with sign-in, sign-up,
profile, and protected dashboard routing.
---
app/(auth)/layout.tsx | 40 ++++
app/(auth)/sign-in/[[...sign-in]]/page.tsx | 30 +++
app/(auth)/sign-up/[[...sign-up]]/page.tsx | 28 +++
app/(protected)/dashboard/page.tsx | 243 +++++++++++++++++++++
app/(protected)/layout.tsx | 20 ++
app/(protected)/profile/page.tsx | 42 ++++
app/(protected)/settings/page.tsx | 146 +++++++++++++
app/layout.tsx | 48 +++-
components/auth/SignInButton.tsx | 29 +++
components/auth/SignUpButton.tsx | 29 +++
components/auth/UserMenu.tsx | 38 ++++
components/landing/Header.tsx | 109 ++++++---
components/landing/Showcase.tsx | 2 +-
components/layout/ProtectedHeader.tsx | 93 ++++++++
lib/clerk.ts | 29 +++
middleware.ts | 12 +
next.config.ts | 13 +-
package-lock.json | 211 ++++++++++++++++++
package.json | 2 +
19 files changed, 1119 insertions(+), 45 deletions(-)
create mode 100644 app/(auth)/layout.tsx
create mode 100644 app/(auth)/sign-in/[[...sign-in]]/page.tsx
create mode 100644 app/(auth)/sign-up/[[...sign-up]]/page.tsx
create mode 100644 app/(protected)/dashboard/page.tsx
create mode 100644 app/(protected)/layout.tsx
create mode 100644 app/(protected)/profile/page.tsx
create mode 100644 app/(protected)/settings/page.tsx
create mode 100644 components/auth/SignInButton.tsx
create mode 100644 components/auth/SignUpButton.tsx
create mode 100644 components/auth/UserMenu.tsx
create mode 100644 components/layout/ProtectedHeader.tsx
create mode 100644 lib/clerk.ts
create mode 100644 middleware.ts
diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx
new file mode 100644
index 0000000..ab2da5a
--- /dev/null
+++ b/app/(auth)/layout.tsx
@@ -0,0 +1,40 @@
+import Link from "next/link";
+
+export default function AuthLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ {/* Background ambient lighting */}
+
+
+ {/* Header brand link */}
+
+
+
+ V
+
+
+ Veodit
+
+
+
+
+ {/* Auth Form Container */}
+
+ {children}
+
+
+ {/* Footer copyright */}
+
+ © {new Date().getFullYear()} Veodit. Open-source AI Video Editor.
+
+
+ );
+}
diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx
new file mode 100644
index 0000000..26a7f32
--- /dev/null
+++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx
@@ -0,0 +1,30 @@
+import { SignIn } from "@clerk/nextjs";
+
+export default function SignInPage() {
+ return (
+
+ );
+}
diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx
new file mode 100644
index 0000000..d57d042
--- /dev/null
+++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx
@@ -0,0 +1,28 @@
+import { SignUp } from "@clerk/nextjs";
+
+export default function SignUpPage() {
+ return (
+
+ );
+}
diff --git a/app/(protected)/dashboard/page.tsx b/app/(protected)/dashboard/page.tsx
new file mode 100644
index 0000000..6326036
--- /dev/null
+++ b/app/(protected)/dashboard/page.tsx
@@ -0,0 +1,243 @@
+import { currentUser } from "@clerk/nextjs/server";
+import Image from "next/image";
+import Link from "next/link";
+import {
+ Upload,
+ Sparkles,
+ Zap,
+ Film,
+ Video,
+ Clock,
+ ArrowRight,
+ TrendingUp,
+ Sliders,
+ CheckCircle2,
+} from "lucide-react";
+import { getUserDisplayName, getUserPrimaryEmail, getUserAvatarUrl } from "@/lib/clerk";
+
+export default async function DashboardPage() {
+ const user = await currentUser();
+ const displayName = getUserDisplayName(user);
+ const firstName = user?.firstName || displayName;
+ const primaryEmail = getUserPrimaryEmail(user);
+ const avatarUrl = getUserAvatarUrl(user);
+
+ return (
+
+ {/* Welcome Banner */}
+
+
+
+
+ {avatarUrl ? (
+
+
+
+ ) : (
+
+ {firstName.charAt(0).toUpperCase()}
+
+ )}
+
+
+
+
+ AI Workspace Active
+
+
+ Welcome back, {firstName}!
+
+
+ {primaryEmail || "Ready to transform long videos into viral shorts?"}
+
+
+
+
+
+
+
Upload New Video
+
+
+
+
+ {/* Stats Overview Grid */}
+
+ {/* User Identity / Account */}
+
+
+ Account
+
+
+
{displayName}
+
{primaryEmail}
+
+
+ {/* AI Credits */}
+
+
+ AI Credits
+
+
+
+ 850
+ / 1,000 credits remaining
+
+
+
+
+ {/* Total Shorts Generated */}
+
+
+ Clips Generated
+
+
+
24
+
+
+ +12 clips this week
+
+
+
+ {/* Time Saved */}
+
+
+ Time Saved
+
+
+
18.5 hrs
+
Compared to manual editing
+
+
+
+ {/* Main Content Dashboard Area */}
+
+ {/* Upload Video CTA Area */}
+
+ {/* Upload Dropzone Placeholder */}
+
+
+
+
+
+ Drop your long video here or click to browse
+
+
+ Supports MP4, MOV, WebM up to 4K resolution (max 2GB). Podcasts, streams & vlogs.
+
+
+
+ AI Auto-Cut, Silence Removal & Auto-Captions enabled
+
+
+
+ {/* Recent Projects Placeholder */}
+
+
+
+
+ Recent Projects
+
+
+ View All
+
+
+
+
+ {[
+ { name: "Tech Podcast Ep. 42", duration: "48:12", clips: 6, status: "Ready", time: "2 hours ago" },
+ { name: "Product Launch Livestream", duration: "1:15:30", clips: 10, status: "Processing", time: "5 hours ago" },
+ { name: "React 19 Deep Dive", duration: "32:45", clips: 4, status: "Ready", time: "1 day ago" },
+ ].map((project, idx) => (
+
+
+
+
+
+
+
{project.name}
+
+ {project.duration} • {project.clips} clips generated • {project.time}
+
+
+
+
+ {project.status}
+
+
+ ))}
+
+
+
+
+ {/* Right Sidebar: Recent Exports & Quick Presets */}
+
+ {/* Export Queue / Recent Exports */}
+
+
+
+ Recent Exports
+
+
+
+ {[
+ { title: "Short #1 - AI Breakthroughs", platform: "YouTube Shorts", quality: "4K 60fps" },
+ { title: "Reel #2 - Key Takeaways", platform: "Instagram Reels", quality: "1080p" },
+ { title: "Clip #3 - Coding Demo", platform: "TikTok", quality: "1080p" },
+ ].map((exportItem, i) => (
+
+
+ {exportItem.title}
+ {exportItem.quality}
+
+
+ {exportItem.platform}
+ Download
+
+
+ ))}
+
+
+
+ {/* Preset Export Settings */}
+
+
+
+ Auto-Cut Presets
+
+
+ {[
+ "High Energy Hooks (15-30s)",
+ "Detailed Explainers (30-60s)",
+ "Viral Quotes & Punchlines",
+ ].map((preset, i) => (
+
+ {preset}
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/app/(protected)/layout.tsx b/app/(protected)/layout.tsx
new file mode 100644
index 0000000..6a3b3dd
--- /dev/null
+++ b/app/(protected)/layout.tsx
@@ -0,0 +1,20 @@
+import { auth } from "@clerk/nextjs/server";
+import { ProtectedHeader } from "@/components/layout/ProtectedHeader";
+
+export default async function ProtectedLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ // Resource-based server-side protection
+ await auth.protect();
+
+ return (
+
+ );
+}
diff --git a/app/(protected)/profile/page.tsx b/app/(protected)/profile/page.tsx
new file mode 100644
index 0000000..0d46193
--- /dev/null
+++ b/app/(protected)/profile/page.tsx
@@ -0,0 +1,42 @@
+import { UserProfile } from "@clerk/nextjs";
+import { dark } from "@clerk/themes";
+
+export default function ProfilePage() {
+ return (
+
+
+
+ User Profile & Security
+
+
+ Manage your account credentials, security preferences, and active sessions.
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/(protected)/settings/page.tsx b/app/(protected)/settings/page.tsx
new file mode 100644
index 0000000..4f1ec03
--- /dev/null
+++ b/app/(protected)/settings/page.tsx
@@ -0,0 +1,146 @@
+"use client";
+
+import { useState } from "react";
+import { Sliders, Key, Monitor, Sparkles, Check, Copy } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+export default function SettingsPage() {
+ const [copied, setCopied] = useState(false);
+ const apiKey = "veodit_live_89f2a019e481b7a2d3c90e1f2b4c6a8";
+
+ const handleCopyKey = () => {
+ navigator.clipboard.writeText(apiKey);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ };
+
+ return (
+
+
+
+ Application & API Settings
+
+
+ Configure default AI video processing parameters, export presets, and API keys.
+
+
+
+ {/* AI Processing Settings */}
+
+
+
+
+
+
+
AI Auto-Cut Settings
+
Customize how AI evaluates and slices video segments
+
+
+
+
+
+
+
Automatic Silence Removal
+
Remove pauses longer than 0.5 seconds automatically
+
+
+
+
+
+
+
Filler Word Removal
+
Detect and trim "um", "uh", and repeated hesitations
+
+
+
+
+
+
+
Animated Auto Captions
+
Generate word-by-word highlighted subtitles
+
+
+
+
+
+
+ {/* Default Export Settings */}
+
+
+
+
+
+
+
Export Defaults
+
Set default rendering quality and aspect ratio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* API Keys */}
+
+
+
+
+
+
+
API Access & Keys
+
Integrate Veodit AI rendering pipeline into your applications
+
+
+
+
+
+
+
+
+
+
+ Keep your API keys confidential. Do not expose them in public frontend repositories.
+
+
+
+
+ );
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 4ecb241..0fd974a 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,5 +1,7 @@
import type { Metadata } from "next";
import { Inter, Geist_Mono } from "next/font/google";
+import { ClerkProvider } from "@clerk/nextjs";
+import { dark } from "@clerk/themes";
import "./globals.css";
import { cn } from "@/lib/utils";
@@ -47,18 +49,40 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: LayoutProps<"/">) {
return (
-
-
- {children}
-
-
+
+
+ {children}
+
+
+
);
}
diff --git a/components/auth/SignInButton.tsx b/components/auth/SignInButton.tsx
new file mode 100644
index 0000000..f55ac54
--- /dev/null
+++ b/components/auth/SignInButton.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import Link from "next/link";
+import { LogIn } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface SignInButtonProps {
+ className?: string;
+ children?: React.ReactNode;
+}
+
+export function SignInButton({ className, children }: SignInButtonProps) {
+ return (
+
+ {children || (
+ <>
+
+ Sign In
+ >
+ )}
+
+ );
+}
diff --git a/components/auth/SignUpButton.tsx b/components/auth/SignUpButton.tsx
new file mode 100644
index 0000000..d95d702
--- /dev/null
+++ b/components/auth/SignUpButton.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import Link from "next/link";
+import { ChevronRight } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+interface SignUpButtonProps {
+ className?: string;
+ children?: React.ReactNode;
+}
+
+export function SignUpButton({ className, children }: SignUpButtonProps) {
+ return (
+
+ {children || (
+ <>
+ Get Started
+
+ >
+ )}
+
+ );
+}
diff --git a/components/auth/UserMenu.tsx b/components/auth/UserMenu.tsx
new file mode 100644
index 0000000..e427f81
--- /dev/null
+++ b/components/auth/UserMenu.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { UserButton } from "@clerk/nextjs";
+import { LayoutDashboard, User, Settings } from "lucide-react";
+
+export function UserMenu() {
+ return (
+
+
+ }
+ href="/dashboard"
+ />
+ }
+ href="/profile"
+ />
+ }
+ href="/settings"
+ />
+
+
+ );
+}
diff --git a/components/landing/Header.tsx b/components/landing/Header.tsx
index 47e36a9..e707950 100644
--- a/components/landing/Header.tsx
+++ b/components/landing/Header.tsx
@@ -3,15 +3,17 @@
import { useState, useEffect } from "react";
import Link from "next/link";
import { motion, AnimatePresence } from "motion/react";
-import { Menu, X, ChevronRight } from "lucide-react";
+import { Menu, X, ChevronRight, LayoutDashboard } from "lucide-react";
+import { useAuth } from "@clerk/nextjs";
import { GitHubIcon } from "@/components/icons/github";
-import { Button } from "@/components/ui/button";
+import { UserMenu } from "@/components/auth/UserMenu";
import { NAV_LINKS } from "@/constants";
import { cn } from "@/lib/utils";
export function Header() {
const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false);
+ const { isSignedIn, isLoaded } = useAuth();
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 20);
@@ -22,7 +24,9 @@ export function Header() {
// Lock body scroll when mobile nav is open
useEffect(() => {
document.body.style.overflow = mobileOpen ? "hidden" : "";
- return () => { document.body.style.overflow = ""; };
+ return () => {
+ document.body.style.overflow = "";
+ };
}, [mobileOpen]);
return (
@@ -45,7 +49,7 @@ export function Header() {
- {/* Desktop Nav */}
+ {/* Desktop Nav Links */}
{NAV_LINKS.map((link) => (
- {/* Desktop Right */}
+ {/* Desktop Right Auth Navigation */}
GitHub
-
- Log in
-
-
+
+ {isLoaded && !isSignedIn && (
+ <>
+
+ Sign In
+
+
+ Get Started
+
+
+ >
+ )}
+
+ {isLoaded && isSignedIn && (
+ <>
+
+
+ Dashboard
+
+
+ >
+ )}
{/* Mobile Menu Toggle */}
@@ -114,6 +137,7 @@ export function Header() {
{link.label}
))}
+
GitHub
-
setMobileOpen(false)}
- >
- Log in
-
-
+
+ {isLoaded && !isSignedIn && (
+ <>
+
setMobileOpen(false)}
+ >
+ Sign In
+
+
setMobileOpen(false)}
+ >
+ Get Started
+
+
+ >
+ )}
+
+ {isLoaded && isSignedIn && (
+ <>
+
setMobileOpen(false)}
+ >
+
+ Dashboard
+
+
+ Account
+
+
+ >
+ )}
diff --git a/components/landing/Showcase.tsx b/components/landing/Showcase.tsx
index 7d68bda..0466107 100644
--- a/components/landing/Showcase.tsx
+++ b/components/landing/Showcase.tsx
@@ -209,7 +209,7 @@ export function Showcase() {
key={i}
className="flex-1 bg-emerald-500/30 rounded-full"
style={{
- height: `${20 + Math.sin(i * 0.5) * 40 + Math.random() * 30}%`,
+ height: `${25 + Math.abs(Math.sin(i * 0.4) * 45 + Math.cos(i * 0.75) * 25)}%`,
}}
/>
))}
diff --git a/components/layout/ProtectedHeader.tsx b/components/layout/ProtectedHeader.tsx
new file mode 100644
index 0000000..92d9dff
--- /dev/null
+++ b/components/layout/ProtectedHeader.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { LayoutDashboard, User, Settings, Sparkles } from "lucide-react";
+import { UserMenu } from "@/components/auth/UserMenu";
+import { cn } from "@/lib/utils";
+
+const PROTECTED_NAV_LINKS = [
+ { label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
+ { label: "Profile", href: "/profile", icon: User },
+ { label: "Settings", href: "/settings", icon: Settings },
+];
+
+export function ProtectedHeader() {
+ const pathname = usePathname();
+
+ return (
+
+
+ {/* Brand */}
+
+
+
+ V
+
+
+ Veodit
+
+
+
+ {/* Application Navigation */}
+
+
+
+ {/* Right Header Actions */}
+
+
+
+ New Video
+
+
+ {/* Clerk User Menu */}
+
+
+
+
+ {/* Mobile Navigation Bar */}
+
+ {PROTECTED_NAV_LINKS.map((link) => {
+ const Icon = link.icon;
+ const isActive = pathname === link.href;
+ return (
+
+
+ {link.label}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/lib/clerk.ts b/lib/clerk.ts
new file mode 100644
index 0000000..95fec64
--- /dev/null
+++ b/lib/clerk.ts
@@ -0,0 +1,29 @@
+import type { User } from "@clerk/nextjs/server";
+
+export function getUserDisplayName(user: User | null | undefined): string {
+ if (!user) return "Creator";
+ if (user.firstName && user.lastName) {
+ return `${user.firstName} ${user.lastName}`;
+ }
+ if (user.firstName) return user.firstName;
+ if (user.username) return user.username;
+ if (user.emailAddresses && user.emailAddresses.length > 0) {
+ return user.emailAddresses[0].emailAddress.split("@")[0];
+ }
+ return "Creator";
+}
+
+export function getUserPrimaryEmail(user: User | null | undefined): string {
+ if (!user || !user.emailAddresses || user.emailAddresses.length === 0) {
+ return "";
+ }
+ const primary = user.emailAddresses.find(
+ (e) => e.id === user.primaryEmailAddressId
+ );
+ return primary ? primary.emailAddress : user.emailAddresses[0].emailAddress;
+}
+
+export function getUserAvatarUrl(user: User | null | undefined): string {
+ if (!user) return "";
+ return user.imageUrl || "";
+}
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..254f2d7
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,12 @@
+import { clerkMiddleware } from "@clerk/nextjs/server";
+
+export default clerkMiddleware();
+
+export const config = {
+ matcher: [
+ // Skip Next.js internals and all static files, unless found in search params
+ "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|svg|png|jpg|jpeg|webp|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
+ // Always run for API routes
+ "/(api|trpc)(.*)",
+ ],
+};
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..972586b 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,7 +1,18 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
- /* config options here */
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "img.clerk.com",
+ },
+ {
+ protocol: "https",
+ hostname: "images.clerk.dev",
+ },
+ ],
+ },
};
export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
index ec6ef10..49861ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.1.0",
"dependencies": {
"@base-ui/react": "^1.7.0",
+ "@clerk/nextjs": "^7.6.4",
+ "@clerk/themes": "^2.4.57",
"@shadcn/react": "^0.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -526,6 +528,134 @@
}
}
},
+ "node_modules/@clerk/backend": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-3.15.0.tgz",
+ "integrity": "sha512-m8/X0hAjX9HyBD0IdfIFIxP1fbbSvnq5DB6mPLeIc53Ya9XCLn/hlQkaIM8pobKsM/jquRt9DQLT9IvV6z6bFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@clerk/shared": "^4.25.10",
+ "standardwebhooks": "^1.0.0",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ }
+ },
+ "node_modules/@clerk/nextjs": {
+ "version": "7.6.4",
+ "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-7.6.4.tgz",
+ "integrity": "sha512-eDo55Yheel2xoYSvVRsg8SKiZV5sB57NIfX2tTuSRCHh2/pG20nZ2wUHg7CdVQ7xtwtIp9qkmGScSkjm/T4++Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@clerk/backend": "^3.15.0",
+ "@clerk/react": "^6.12.10",
+ "@clerk/shared": "^4.25.10",
+ "server-only": "0.0.1",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "peerDependencies": {
+ "next": "^15.2.8 || ^15.3.8 || ^15.4.10 || ^15.5.9 || ^15.6.0-0 || ^16.0.10 || ^16.1.0-0",
+ "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
+ "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
+ }
+ },
+ "node_modules/@clerk/react": {
+ "version": "6.12.10",
+ "resolved": "https://registry.npmjs.org/@clerk/react/-/react-6.12.10.tgz",
+ "integrity": "sha512-VDYyCyzRdMsNW6qQsMp1+L2VOXxFwpLfWBxOWPvUNOYCruecnAZjUYEJWS+E+i5MAPhSew9HjuweS4bO0FclTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@clerk/shared": "^4.25.10",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
+ "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
+ }
+ },
+ "node_modules/@clerk/shared": {
+ "version": "4.25.10",
+ "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-4.25.10.tgz",
+ "integrity": "sha512-Vjqk74nQGJ8y+cm+eSshoBzRvBhIzuKLOs3Gt13gEHdyQ6yV798Dl2SrBKt0jw2u6I1tOcD3s8/i9P4yJYo0dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "^5.100.6",
+ "dequal": "2.0.3",
+ "glob-to-regexp": "0.4.1",
+ "js-cookie": "3.0.7"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
+ "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@clerk/themes": {
+ "version": "2.4.57",
+ "resolved": "https://registry.npmjs.org/@clerk/themes/-/themes-2.4.57.tgz",
+ "integrity": "sha512-Nb3bO79rMTU/MPVTC/dde6LG27/IgOMKIYi5KSvAmO4ZUHlj0OWufu6CMvz5OYVZ0YdyMnTBU2aPGRUiRzO+2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@clerk/shared": "^3.47.2",
+ "tslib": "2.8.1"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ }
+ },
+ "node_modules/@clerk/themes/node_modules/@clerk/shared": {
+ "version": "3.47.8",
+ "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.47.8.tgz",
+ "integrity": "sha512-cPURU1it/Eal4uXO9SOcHzw9sfE6Opi21W8EJUg+Ri80Q6JKtK8qBmyOpIyqgf09EanexhTzS4xrmYlvn2p7Iw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "3.1.3",
+ "dequal": "2.0.3",
+ "glob-to-regexp": "0.4.1",
+ "js-cookie": "3.0.7",
+ "std-env": "^3.9.0",
+ "swr": "2.3.4"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0",
+ "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@clerk/themes/node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
"node_modules/@date-fns/tz": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.5.0.tgz",
@@ -2304,6 +2434,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
+ "license": "MIT"
+ },
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -2596,6 +2732,16 @@
"tailwindcss": "4.3.3"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.101.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz",
+ "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@ts-morph/common": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz",
@@ -4705,6 +4851,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -5713,6 +5868,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
+ "license": "Unlicense"
+ },
"node_modules/fast-uri": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
@@ -6110,6 +6271,12 @@
"node": ">=10.13.0"
}
},
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -7043,6 +7210,15 @@
"url": "https://github.com/sponsors/panva"
}
},
+ "node_modules/js-cookie": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz",
+ "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -9218,6 +9394,12 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/server-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz",
+ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
+ "license": "MIT"
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -9569,6 +9751,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/standardwebhooks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
+ "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/base64": "^1.0.0",
+ "fast-sha256": "^1.3.0"
+ }
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -9578,6 +9770,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "license": "MIT"
+ },
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
@@ -9892,6 +10090,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/swr": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz",
+ "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/systeminformation": {
"version": "5.33.1",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.33.1.tgz",
diff --git a/package.json b/package.json
index bd2a89b..cd4b07c 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,8 @@
},
"dependencies": {
"@base-ui/react": "^1.7.0",
+ "@clerk/nextjs": "^7.6.4",
+ "@clerk/themes": "^2.4.57",
"@shadcn/react": "^0.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",