From 31fda8a28c2d1d75026f41c56fd02951419a02f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Sat, 28 Feb 2026 10:35:01 +0300 Subject: [PATCH] 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 --- app/(dashboard)/dashboard/page.tsx | 8 +++++++ app/api/onboarding/complete/route.ts | 17 +++++++++++++ app/onboarding/layout.tsx | 7 ++++++ app/onboarding/page.tsx | 24 +++++++++++++++++++ .../migration.sql | 1 + prisma/schema.prisma | 3 +++ 6 files changed, 60 insertions(+) create mode 100644 app/api/onboarding/complete/route.ts create mode 100644 app/onboarding/layout.tsx create mode 100644 app/onboarding/page.tsx create mode 100644 prisma/migrations/20260227120000_add_onboarding/migration.sql 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 {