diff --git a/app/(dashboard)/dashboard/page.tsx b/app/(dashboard)/dashboard/page.tsx
index 8c1cd9f..df2476b 100644
--- a/app/(dashboard)/dashboard/page.tsx
+++ b/app/(dashboard)/dashboard/page.tsx
@@ -14,6 +14,14 @@ export default async function DashboardPage({
redirect('/login');
}
+ const userOnboarding = await db.user.findUnique({
+ where: { id: session.user.id },
+ select: { onboardingCompletedAt: true },
+ });
+ if (!userOnboarding?.onboardingCompletedAt) {
+ redirect('/onboarding');
+ }
+
const resolvedSearchParams = await searchParams;
const { ws, sort, page: pageParam } = resolvedSearchParams || {};
diff --git a/app/api/onboarding/complete/route.ts b/app/api/onboarding/complete/route.ts
new file mode 100644
index 0000000..46eba41
--- /dev/null
+++ b/app/api/onboarding/complete/route.ts
@@ -0,0 +1,17 @@
+import { auth } from '@/lib/auth';
+import { db } from '@/lib/db';
+import { apiErrors, successResponse } from '@/lib/api-response';
+
+export async function POST() {
+ const session = await auth();
+ if (!session?.user?.id) {
+ return apiErrors.unauthorized();
+ }
+
+ await db.user.update({
+ where: { id: session.user.id },
+ data: { onboardingCompletedAt: new Date() },
+ });
+
+ return successResponse({ completed: true });
+}
diff --git a/app/onboarding/layout.tsx b/app/onboarding/layout.tsx
new file mode 100644
index 0000000..19313a4
--- /dev/null
+++ b/app/onboarding/layout.tsx
@@ -0,0 +1,7 @@
+export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/app/onboarding/page.tsx b/app/onboarding/page.tsx
new file mode 100644
index 0000000..6082449
--- /dev/null
+++ b/app/onboarding/page.tsx
@@ -0,0 +1,24 @@
+import { auth } from '@/lib/auth';
+import { db } from '@/lib/db';
+import { redirect } from 'next/navigation';
+import { OnboardingWizard } from './onboarding-wizard';
+
+export default async function OnboardingPage() {
+ const session = await auth();
+ if (!session?.user?.id) {
+ redirect('/login');
+ }
+
+ const user = await db.user.findUnique({
+ where: { id: session.user.id },
+ select: { onboardingCompletedAt: true, name: true, email: true },
+ });
+
+ if (user?.onboardingCompletedAt) {
+ redirect('/dashboard');
+ }
+
+ const userName = user?.name || user?.email?.split('@')[0] || 'there';
+
+ return ;
+}
diff --git a/prisma/migrations/20260227120000_add_onboarding/migration.sql b/prisma/migrations/20260227120000_add_onboarding/migration.sql
new file mode 100644
index 0000000..dd59e7a
--- /dev/null
+++ b/prisma/migrations/20260227120000_add_onboarding/migration.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN IF NOT EXISTS "onboardingCompletedAt" TIMESTAMP;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 253ccb6..31debd5 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -21,6 +21,7 @@ model User {
password String? // Hashed password for email/password auth
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
+ onboardingCompletedAt DateTime?
// Relations
accounts Account[]
@@ -70,12 +71,14 @@ enum DownloadEgressSource {
enum VideoAssetKind {
IMAGE
VIDEO
+ AUDIO
}
enum VideoAssetProvider {
R2_IMAGE
YOUTUBE
BUNNY
+ R2_AUDIO
}
model DownloadEgressEvent {