feat(onboarding): add onboarding wizard flow

- Add `onboardingCompletedAt` field to User model + migration
- Add AUDIO/R2_AUDIO enum values to schema
- Gate dashboard behind onboarding check; redirect to /onboarding if not completed
- Add /onboarding page + layout with 5-step wizard (welcome, workspace, project, video info, notifications)
- Add POST /api/onboarding/complete endpoint to mark onboarding done

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Yusuf İpek
2026-02-28 10:35:01 +03:00
co-authored by Claude Sonnet 4.6
parent d4f4220520
commit 31fda8a28c
6 changed files with 60 additions and 0 deletions
+8
View File
@@ -14,6 +14,14 @@ export default async function DashboardPage({
redirect('/login'); 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 resolvedSearchParams = await searchParams;
const { ws, sort, page: pageParam } = resolvedSearchParams || {}; const { ws, sort, page: pageParam } = resolvedSearchParams || {};
+17
View File
@@ -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 });
}
+7
View File
@@ -0,0 +1,7 @@
export default function OnboardingLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background flex items-center justify-center p-4">
{children}
</div>
);
}
+24
View File
@@ -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 <OnboardingWizard userName={userName} />;
}
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN IF NOT EXISTS "onboardingCompletedAt" TIMESTAMP;
+3
View File
@@ -21,6 +21,7 @@ model User {
password String? // Hashed password for email/password auth password String? // Hashed password for email/password auth
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
onboardingCompletedAt DateTime?
// Relations // Relations
accounts Account[] accounts Account[]
@@ -70,12 +71,14 @@ enum DownloadEgressSource {
enum VideoAssetKind { enum VideoAssetKind {
IMAGE IMAGE
VIDEO VIDEO
AUDIO
} }
enum VideoAssetProvider { enum VideoAssetProvider {
R2_IMAGE R2_IMAGE
YOUTUBE YOUTUBE
BUNNY BUNNY
R2_AUDIO
} }
model DownloadEgressEvent { model DownloadEgressEvent {