'use client' import { useState, useMemo, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' import { trpc } from '@/lib/trpc/client' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { PhoneInput } from '@/components/ui/phone-input' import { CountrySelect } from '@/components/ui/country-select' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Slider } from '@/components/ui/slider' import { cn } from '@/lib/utils' import { toast } from 'sonner' import { ExpertiseSelect } from '@/components/shared/expertise-select' import { AvatarUpload } from '@/components/shared/avatar-upload' import { Textarea } from '@/components/ui/textarea' import { User, Phone, Tags, Bell, CheckCircle, Loader2, ArrowRight, ArrowLeft, Camera, Globe, FileText, Scale, } from 'lucide-react' import { AnimatedCard } from '@/components/shared/animated-container' type Step = 'name' | 'photo' | 'country' | 'bio' | 'phone' | 'tags' | 'jury' | 'preferences' | 'complete' type JuryPref = { juryGroupMemberId: string juryGroupName: string currentCap: number allowCapAdjustment: boolean allowRatioAdjustment: boolean selfServiceCap: number | null selfServiceRatio: number | null } export default function OnboardingPage() { const router = useRouter() const { data: session, status: sessionStatus } = useSession() const isAuthenticated = sessionStatus === 'authenticated' const [step, setStep] = useState('name') const [initialized, setInitialized] = useState(false) // Form state const [name, setName] = useState('') const [country, setCountry] = useState('') const [bio, setBio] = useState('') const [phoneNumber, setPhoneNumber] = useState('') const [expertiseTags, setExpertiseTags] = useState([]) const [lockedTags, setLockedTags] = useState([]) const [notificationPreference, setNotificationPreference] = useState< 'EMAIL' | 'WHATSAPP' | 'BOTH' | 'NONE' >('EMAIL') const [juryPrefs, setJuryPrefs] = useState>(new Map()) // Fetch current user data only after session is hydrated const { data: userData, isLoading: userLoading, refetch: refetchUser } = trpc.user.me.useQuery( undefined, { enabled: isAuthenticated } ) const { data: avatarUrl } = trpc.avatar.getUrl.useQuery( undefined, { enabled: isAuthenticated } ) // Initialize form with user data useEffect(() => { if (userData && !initialized) { // Pre-fill name if available if (userData.name) { setName(userData.name) } // Pre-fill country if available if (userData.country) { setCountry(userData.country) } // Pre-fill bio if available if (userData.bio) { setBio(userData.bio) } // Pre-fill phone if available if (userData.phoneNumber) { setPhoneNumber(userData.phoneNumber) } // Set admin-preset tags as both locked and selected if (userData.expertiseTags && userData.expertiseTags.length > 0) { setLockedTags(userData.expertiseTags) setExpertiseTags(userData.expertiseTags) } // Pre-fill notification preference if available if (userData.notificationPreference) { setNotificationPreference(userData.notificationPreference) } setInitialized(true) } }, [userData, initialized]) // Fetch jury onboarding context const { data: onboardingCtx } = trpc.user.getOnboardingContext.useQuery( undefined, { enabled: isAuthenticated } ) const juryMemberships: JuryPref[] = onboardingCtx?.memberships ?? [] const hasJuryStep = onboardingCtx?.hasSelfServiceOptions ?? false // Fetch feature flags only after session is hydrated const { data: featureFlags } = trpc.settings.getFeatureFlags.useQuery( undefined, { enabled: isAuthenticated } ) const whatsappEnabled = featureFlags?.whatsappEnabled ?? false const utils = trpc.useUtils() const completeOnboarding = trpc.user.completeOnboarding.useMutation({ onSuccess: () => utils.user.me.invalidate(), }) // Dynamic steps based on WhatsApp availability and jury self-service const steps: Step[] = useMemo(() => { const base: Step[] = ['name', 'photo', 'country', 'bio'] if (whatsappEnabled) base.push('phone') base.push('tags') if (hasJuryStep) base.push('jury') base.push('preferences', 'complete') return base }, [whatsappEnabled, hasJuryStep]) const currentIndex = steps.indexOf(step) const totalVisibleSteps = steps.length - 1 // Exclude 'complete' from count const goNext = () => { if (step === 'name' && !name.trim()) { toast.error('Please enter your name') return } const nextIndex = currentIndex + 1 if (nextIndex < steps.length) { setStep(steps[nextIndex]) } } const goBack = () => { const prevIndex = currentIndex - 1 if (prevIndex >= 0) { setStep(steps[prevIndex]) } } const handleComplete = async () => { try { // Build jury preferences from state const juryPreferences = juryMemberships .map((m) => { const pref = juryPrefs.get(m.juryGroupMemberId) if (!pref) return null return { juryGroupMemberId: m.juryGroupMemberId, selfServiceCap: pref.cap, selfServiceRatio: pref.ratio, } }) .filter(Boolean) as Array<{ juryGroupMemberId: string selfServiceCap?: number selfServiceRatio?: number }> await completeOnboarding.mutateAsync({ name, country: country || undefined, bio: bio || undefined, phoneNumber: phoneNumber || undefined, expertiseTags, notificationPreference, juryPreferences: juryPreferences.length > 0 ? juryPreferences : undefined, }) setStep('complete') toast.success('Welcome to MOPC!') // Redirect after a short delay based on user role setTimeout(() => { const role = userData?.role if (role === 'SUPER_ADMIN' || role === 'PROGRAM_ADMIN') { router.push('/admin') } else if (role === 'MENTOR') { router.push('/mentor') } else if (role === 'OBSERVER') { router.push('/observer') } else { router.push('/jury') } }, 2000) } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to complete onboarding') } } // Show loading while session hydrates or fetching user data if (sessionStatus === 'loading' || userLoading || !initialized) { return (

Loading your profile...

) } return (
{/* Progress indicator */}
{steps.slice(0, -1).map((s, i) => (
))}
{/* Step labels */}
{steps.slice(0, -1).map((s, i) => { const labels: Record = { name: 'Name', photo: 'Photo', country: 'Country', bio: 'About', phone: 'Phone', tags: 'Expertise', jury: 'Jury', preferences: 'Settings', } return (
{labels[s] || s}
) })}

Step {currentIndex + 1} of {totalVisibleSteps}

{/* Step 1: Name */} {step === 'name' && ( <> Welcome to MOPC Let's get your profile set up. What should we call you?
setName(e.target.value)} placeholder="Enter your full name" autoFocus />
)} {/* Step 2: Profile Photo */} {step === 'photo' && ( <> Profile Photo Add a profile photo so others can recognize you. This step is optional.
refetchUser()} />

Click the avatar to upload a new photo. You can crop and adjust before saving.

)} {/* Step 3: Home Country */} {step === 'country' && ( <> Home Country Select your home country. This helps us match you with relevant projects.
)} {/* Step 4: Bio */} {step === 'bio' && ( <> About You Tell us a bit about yourself and your expertise. This helps us match you with relevant projects. (Optional)