From d4f42205200813126c37bd1b46938a05ba2bf028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Sat, 28 Feb 2026 10:31:57 +0300 Subject: [PATCH 1/2] fix(onboarding): handle completion API failure instead of silently redirecting Check res.ok and catch network errors in completeOnboarding; surface a toast and bail early rather than pushing to /dashboard on failure, preventing a redirect loop back to /onboarding. Co-Authored-By: Claude Sonnet 4.6 --- app/onboarding/onboarding-wizard.tsx | 624 +++++++++++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 app/onboarding/onboarding-wizard.tsx diff --git a/app/onboarding/onboarding-wizard.tsx b/app/onboarding/onboarding-wizard.tsx new file mode 100644 index 0000000..2ff8488 --- /dev/null +++ b/app/onboarding/onboarding-wizard.tsx @@ -0,0 +1,624 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { + Video, + Building2, + FolderPlus, + PlayCircle, + Bell, + ChevronRight, + Loader2, + Lock, + UserPlus, + Globe, + Youtube, + Upload, + Mail, + AlertCircle, + Info, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { cn } from '@/lib/utils'; + +type Visibility = 'PRIVATE' | 'INVITE' | 'PUBLIC'; + +const TOTAL_STEPS = 5; + +const visibilityOptions: { value: Visibility; label: string; description: string; icon: React.ReactNode }[] = [ + { + value: 'PRIVATE', + label: 'Private', + description: 'Only workspace members and project members can access', + icon: , + }, + { + value: 'INVITE', + label: 'Invite Only', + description: 'Share with specific people via email', + icon: , + }, + { + value: 'PUBLIC', + label: 'Public', + description: 'Anyone with the link can view', + icon: , + }, +]; + +function ToggleButton({ + enabled, + onToggle, + label, + description, +}: { + enabled: boolean; + onToggle: () => void; + label: string; + description?: string; +}) { + return ( + + ); +} + +// ─── Step 1: Welcome ─────────────────────────────────────────────────────────── + +function StepWelcome({ userName, onNext }: { userName: string; onNext: () => void }) { + return ( +
+
+
+
+

Welcome to OpenFrame, {userName.split(' ')[0]}!

+

+ OpenFrame is your collaborative video review platform. Collect timestamped feedback, manage versions, and streamline approvals — all in one place. +

+
+ +
+ ); +} + +// ─── Step 2: Create Workspace ────────────────────────────────────────────────── + +function StepWorkspace({ + onNext, + onWorkspaceCreated, +}: { + onNext: () => void; + onWorkspaceCreated: (id: string) => void; +}) { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + const [formData, setFormData] = useState({ name: '', description: '' }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + setError(''); + try { + const res = await fetch('/api/workspaces', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(formData), + }); + const data = await res.json(); + if (!res.ok) { + setError(data.error || 'Failed to create workspace'); + return; + } + onWorkspaceCreated(data.data.id); + onNext(); + } catch { + setError('Something went wrong. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+
+ +
+

Create your workspace

+

+ Workspaces organize your projects and team members. +

+
+ +
+
+ + setFormData({ ...formData, name: e.target.value })} + required + disabled={isLoading} + className="h-11" + /> +
+
+ +