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
40 changes: 40 additions & 0 deletions app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Link from "next/link";

export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="relative min-h-screen flex flex-col items-center justify-center p-6 bg-[#09090b] overflow-hidden">
{/* Background ambient lighting */}
<div className="pointer-events-none absolute inset-0">
<div className="absolute top-1/4 left-1/2 -translate-x-1/2 h-[500px] w-[500px] rounded-full bg-violet-600/15 blur-[140px]" />
<div className="absolute bottom-1/4 right-1/4 h-[400px] w-[400px] rounded-full bg-cyan-500/10 blur-[140px]" />
<div className="absolute inset-0 grid-pattern opacity-30" />
</div>

{/* Header brand link */}
<div className="relative z-10 mb-8">
<Link href="/" className="flex items-center gap-2 group">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-violet-500 to-cyan-500 shadow-lg shadow-violet-500/20">
<span className="text-base font-bold text-white">V</span>
</div>
<span className="text-2xl font-bold text-white tracking-tight">
Veodit
</span>
</Link>
</div>

{/* Auth Form Container */}
<div className="relative z-10 w-full max-w-md flex justify-center">
{children}
</div>

{/* Footer copyright */}
<div className="relative z-10 mt-8 text-center text-xs text-zinc-600">
&copy; {new Date().getFullYear()} Veodit. Open-source AI Video Editor.
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions app/(auth)/sign-in/[[...sign-in]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { SignIn } from "@clerk/nextjs";

export default function SignInPage() {
return (
<SignIn
appearance={{
elements: {
rootBox: "w-full flex justify-center",
card: "w-full max-w-md bg-zinc-950/80 glass border border-white/10 p-8 rounded-2xl shadow-2xl shadow-violet-500/10",
headerTitle: "text-2xl font-bold text-white text-center",
headerSubtitle: "text-sm text-zinc-400 text-center",
socialButtonsBlockButton:
"bg-white/5 border border-white/10 hover:bg-white/10 text-white rounded-xl h-11 transition-all",
socialButtonsBlockButtonText: "text-sm font-medium text-zinc-200",
dividerLine: "bg-white/10",
dividerText: "text-xs text-zinc-500 uppercase tracking-widest",
formFieldLabel: "text-xs font-medium text-zinc-300",
formFieldInput:
"h-11 rounded-xl bg-white/5 border border-white/10 px-4 text-sm text-white placeholder:text-zinc-600 focus:border-violet-500/50 focus:ring-1 focus:ring-violet-500/20 transition-all outline-none",
formButtonPrimary:
"h-11 rounded-xl bg-gradient-to-r from-violet-600 to-cyan-500 text-sm font-medium text-white hover:opacity-90 transition-all border-0 shadow-lg shadow-violet-500/20",
footerActionLink:
"text-sm font-medium text-violet-400 hover:text-violet-300 transition-colors",
identityPreviewText: "text-sm text-zinc-300 font-medium",
identityPreviewEditButton: "text-xs text-violet-400 hover:text-violet-300",
},
}}
/>
);
}
28 changes: 28 additions & 0 deletions app/(auth)/sign-up/[[...sign-up]]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { SignUp } from "@clerk/nextjs";

export default function SignUpPage() {
return (
<SignUp
appearance={{
elements: {
rootBox: "w-full flex justify-center",
card: "w-full max-w-md bg-zinc-950/80 glass border border-white/10 p-8 rounded-2xl shadow-2xl shadow-violet-500/10",
headerTitle: "text-2xl font-bold text-white text-center",
headerSubtitle: "text-sm text-zinc-400 text-center",
socialButtonsBlockButton:
"bg-white/5 border border-white/10 hover:bg-white/10 text-white rounded-xl h-11 transition-all",
socialButtonsBlockButtonText: "text-sm font-medium text-zinc-200",
dividerLine: "bg-white/10",
dividerText: "text-xs text-zinc-500 uppercase tracking-widest",
formFieldLabel: "text-xs font-medium text-zinc-300",
formFieldInput:
"h-11 rounded-xl bg-white/5 border border-white/10 px-4 text-sm text-white placeholder:text-zinc-600 focus:border-violet-500/50 focus:ring-1 focus:ring-violet-500/20 transition-all outline-none",
formButtonPrimary:
"h-11 rounded-xl bg-gradient-to-r from-violet-600 to-cyan-500 text-sm font-medium text-white hover:opacity-90 transition-all border-0 shadow-lg shadow-violet-500/20",
footerActionLink:
"text-sm font-medium text-violet-400 hover:text-violet-300 transition-colors",
},
}}
/>
);
}
243 changes: 243 additions & 0 deletions app/(protected)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="space-y-8">
{/* Welcome Banner */}
<div className="relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-r from-violet-900/30 via-zinc-900/50 to-cyan-900/20 p-6 md:p-8 backdrop-blur-xl shadow-2xl shadow-violet-500/5">
<div className="pointer-events-none absolute inset-0 grid-pattern opacity-20" />
<div className="relative z-10 flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div className="flex items-center gap-5">
{avatarUrl ? (
<div className="relative h-16 w-16 rounded-2xl overflow-hidden border-2 border-violet-500/40 shadow-lg">
<Image
src={avatarUrl}
alt={displayName}
fill
className="object-cover"
/>
</div>
) : (
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-violet-500 to-cyan-500 text-2xl font-bold text-white shadow-lg">
{firstName.charAt(0).toUpperCase()}
</div>
)}

<div>
<div className="inline-flex items-center gap-2 rounded-full border border-violet-500/30 bg-violet-500/10 px-3 py-1 text-xs font-semibold text-violet-300 mb-2">
<Sparkles className="size-3" />
<span>AI Workspace Active</span>
</div>
<h1 className="text-2xl md:text-3xl font-bold text-white tracking-tight">
Welcome back, <span className="gradient-text">{firstName}</span>!
</h1>
<p className="text-sm text-zinc-400 mt-1">
{primaryEmail || "Ready to transform long videos into viral shorts?"}
</p>
</div>
</div>

<Link
href="#upload"
className="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-violet-600 to-cyan-500 px-6 py-3 text-sm font-semibold text-white hover:opacity-90 transition-all shadow-xl shadow-violet-500/25"
>
<Upload className="size-4" />
<span>Upload New Video</span>
</Link>
</div>
</div>

{/* Stats Overview Grid */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{/* User Identity / Account */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-5 backdrop-blur-md">
<div className="flex items-center justify-between text-zinc-400 mb-3">
<span className="text-xs font-semibold uppercase tracking-wider">Account</span>
<CheckCircle2 className="size-4 text-emerald-400" />
</div>
<p className="text-lg font-bold text-white truncate">{displayName}</p>
<p className="text-xs text-zinc-500 truncate mt-0.5">{primaryEmail}</p>
</div>

{/* AI Credits */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-5 backdrop-blur-md">
<div className="flex items-center justify-between text-zinc-400 mb-3">
<span className="text-xs font-semibold uppercase tracking-wider">AI Credits</span>
<Zap className="size-4 text-amber-400" />
</div>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-bold text-white">850</span>
<span className="text-xs text-zinc-500">/ 1,000 credits remaining</span>
</div>
<div className="mt-3 h-1.5 w-full rounded-full bg-white/10 overflow-hidden">
<div className="h-full bg-gradient-to-r from-violet-500 to-cyan-500 rounded-full" style={{ width: "85%" }} />
</div>
</div>

{/* Total Shorts Generated */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-5 backdrop-blur-md">
<div className="flex items-center justify-between text-zinc-400 mb-3">
<span className="text-xs font-semibold uppercase tracking-wider">Clips Generated</span>
<Film className="size-4 text-violet-400" />
</div>
<p className="text-2xl font-bold text-white">24</p>
<p className="text-xs text-emerald-400 mt-1 flex items-center gap-1">
<TrendingUp className="size-3" />
+12 clips this week
</p>
</div>

{/* Time Saved */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-5 backdrop-blur-md">
<div className="flex items-center justify-between text-zinc-400 mb-3">
<span className="text-xs font-semibold uppercase tracking-wider">Time Saved</span>
<Clock className="size-4 text-cyan-400" />
</div>
<p className="text-2xl font-bold text-white">18.5 hrs</p>
<p className="text-xs text-zinc-500 mt-1">Compared to manual editing</p>
</div>
</div>

{/* Main Content Dashboard Area */}
<div className="grid gap-6 lg:grid-cols-3">
{/* Upload Video CTA Area */}
<div id="upload" className="lg:col-span-2 space-y-6">
{/* Upload Dropzone Placeholder */}
<div className="rounded-3xl border-2 border-dashed border-white/15 bg-white/[0.01] p-8 text-center hover:border-violet-500/40 hover:bg-white/[0.03] transition-all group cursor-pointer">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-500/10 border border-violet-500/20 group-hover:scale-110 transition-transform">
<Upload className="size-8 text-violet-400" />
</div>
<h3 className="mt-4 text-lg font-semibold text-white">
Drop your long video here or click to browse
</h3>
<p className="mt-1 text-sm text-zinc-400 max-w-sm mx-auto">
Supports MP4, MOV, WebM up to 4K resolution (max 2GB). Podcasts, streams & vlogs.
</p>
<div className="mt-6 inline-flex items-center gap-2 rounded-xl bg-white/10 px-4 py-2 text-xs font-medium text-zinc-200">
<Sparkles className="size-3.5 text-violet-400" />
<span>AI Auto-Cut, Silence Removal & Auto-Captions enabled</span>
</div>
</div>

{/* Recent Projects Placeholder */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-6 space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
<Video className="size-4 text-violet-400" />
Recent Projects
</h3>
<Link href="#" className="text-xs font-medium text-violet-400 hover:text-violet-300 flex items-center gap-1">
View All <ArrowRight className="size-3" />
</Link>
</div>

<div className="space-y-3">
{[
{ 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) => (
<div
key={idx}
className="flex items-center justify-between rounded-xl border border-white/5 bg-white/[0.02] p-4 hover:bg-white/[0.05] transition-colors"
>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-zinc-800 border border-white/10 text-zinc-400">
<Film className="size-5" />
</div>
<div>
<h4 className="text-sm font-medium text-white">{project.name}</h4>
<p className="text-xs text-zinc-500">
{project.duration} &bull; {project.clips} clips generated &bull; {project.time}
</p>
</div>
</div>
<span
className={`text-xs px-2.5 py-1 rounded-full font-medium ${
project.status === "Ready"
? "bg-emerald-500/10 text-emerald-400 border border-emerald-500/20"
: "bg-amber-500/10 text-amber-400 border border-amber-500/20 animate-pulse"
}`}
>
{project.status}
</span>
</div>
))}
</div>
</div>
</div>

{/* Right Sidebar: Recent Exports & Quick Presets */}
<div className="space-y-6">
{/* Export Queue / Recent Exports */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-6 space-y-4">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
<Film className="size-4 text-cyan-400" />
Recent Exports
</h3>

<div className="space-y-3">
{[
{ 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) => (
<div key={i} className="rounded-xl border border-white/5 bg-white/[0.02] p-3 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-white truncate max-w-[180px]">{exportItem.title}</span>
<span className="text-[10px] text-zinc-500">{exportItem.quality}</span>
</div>
<div className="flex items-center justify-between text-xs text-zinc-400">
<span>{exportItem.platform}</span>
<span className="text-violet-400 hover:underline cursor-pointer">Download</span>
</div>
</div>
))}
</div>
</div>

{/* Preset Export Settings */}
<div className="rounded-2xl border border-white/10 bg-white/[0.02] p-6 space-y-4">
<h3 className="text-base font-semibold text-white flex items-center gap-2">
<Sliders className="size-4 text-violet-400" />
Auto-Cut Presets
</h3>
<div className="space-y-2">
{[
"High Energy Hooks (15-30s)",
"Detailed Explainers (30-60s)",
"Viral Quotes & Punchlines",
].map((preset, i) => (
<div key={i} className="flex items-center justify-between p-2.5 rounded-lg bg-white/5 text-xs text-zinc-300 hover:bg-white/10 transition-colors cursor-pointer">
<span>{preset}</span>
<CheckCircle2 className="size-3.5 text-violet-400" />
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}
20 changes: 20 additions & 0 deletions app/(protected)/layout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-screen flex flex-col bg-[#09090b]">
<ProtectedHeader />
<main className="flex-1 mx-auto w-full max-w-7xl px-6 py-8 md:py-12">
{children}
</main>
</div>
);
}
Loading