2026-01-30 13:41:32 +01:00
|
|
|
'use client'
|
|
|
|
|
|
2026-02-04 00:58:22 +01:00
|
|
|
import { useState, useMemo } from 'react'
|
2026-01-30 13:41:32 +01:00
|
|
|
import { useRouter } from 'next/navigation'
|
|
|
|
|
import { trpc } from '@/lib/trpc/client'
|
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
|
|
import { Input } from '@/components/ui/input'
|
|
|
|
|
import { Label } from '@/components/ui/label'
|
2026-02-04 00:54:57 +01:00
|
|
|
import { PhoneInput } from '@/components/ui/phone-input'
|
2026-01-30 13:41:32 +01:00
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from '@/components/ui/card'
|
|
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select'
|
|
|
|
|
import { toast } from 'sonner'
|
2026-02-04 00:56:03 +01:00
|
|
|
import { ExpertiseSelect } from '@/components/shared/expertise-select'
|
2026-01-30 13:41:32 +01:00
|
|
|
import {
|
|
|
|
|
User,
|
|
|
|
|
Phone,
|
|
|
|
|
Tags,
|
|
|
|
|
Bell,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
Loader2,
|
|
|
|
|
ArrowRight,
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
} from 'lucide-react'
|
|
|
|
|
|
|
|
|
|
type Step = 'name' | 'phone' | 'tags' | 'preferences' | 'complete'
|
|
|
|
|
|
|
|
|
|
export default function OnboardingPage() {
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
const [step, setStep] = useState<Step>('name')
|
|
|
|
|
|
|
|
|
|
// Form state
|
|
|
|
|
const [name, setName] = useState('')
|
|
|
|
|
const [phoneNumber, setPhoneNumber] = useState('')
|
|
|
|
|
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
|
|
|
|
|
const [notificationPreference, setNotificationPreference] = useState<
|
|
|
|
|
'EMAIL' | 'WHATSAPP' | 'BOTH' | 'NONE'
|
|
|
|
|
>('EMAIL')
|
|
|
|
|
|
2026-02-04 00:58:22 +01:00
|
|
|
// Fetch feature flags
|
|
|
|
|
const { data: featureFlags } = trpc.settings.getFeatureFlags.useQuery()
|
|
|
|
|
const whatsappEnabled = featureFlags?.whatsappEnabled ?? false
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
const completeOnboarding = trpc.user.completeOnboarding.useMutation()
|
|
|
|
|
|
2026-02-04 00:58:22 +01:00
|
|
|
// Dynamic steps based on WhatsApp availability
|
|
|
|
|
const steps: Step[] = useMemo(() => {
|
|
|
|
|
if (whatsappEnabled) {
|
|
|
|
|
return ['name', 'phone', 'tags', 'preferences', 'complete']
|
|
|
|
|
}
|
|
|
|
|
// Skip phone step if WhatsApp is disabled
|
|
|
|
|
return ['name', 'tags', 'preferences', 'complete']
|
|
|
|
|
}, [whatsappEnabled])
|
|
|
|
|
|
2026-01-30 13:41:32 +01:00
|
|
|
const currentIndex = steps.indexOf(step)
|
2026-02-04 00:58:22 +01:00
|
|
|
const totalVisibleSteps = steps.length - 1 // Exclude 'complete' from count
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
await completeOnboarding.mutateAsync({
|
|
|
|
|
name,
|
|
|
|
|
phoneNumber: phoneNumber || undefined,
|
|
|
|
|
expertiseTags,
|
|
|
|
|
notificationPreference,
|
|
|
|
|
})
|
|
|
|
|
setStep('complete')
|
|
|
|
|
toast.success('Welcome to MOPC!')
|
|
|
|
|
|
|
|
|
|
// Redirect after a short delay
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
router.push('/jury')
|
|
|
|
|
}, 2000)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toast.error(error instanceof Error ? error.message : 'Failed to complete onboarding')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2026-02-04 00:58:22 +01:00
|
|
|
<div className="absolute inset-0 -m-4 flex items-center justify-center p-4 md:p-8 bg-gradient-to-br from-[#053d57] to-[#557f8c]">
|
|
|
|
|
<Card className="w-full max-w-lg max-h-[85vh] overflow-y-auto shadow-2xl">
|
2026-01-30 13:41:32 +01:00
|
|
|
{/* Progress indicator */}
|
|
|
|
|
<div className="px-6 pt-6">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
{steps.slice(0, -1).map((s, i) => (
|
|
|
|
|
<div key={s} className="flex items-center flex-1">
|
|
|
|
|
<div
|
|
|
|
|
className={`h-2 flex-1 rounded-full transition-colors ${
|
|
|
|
|
i < currentIndex
|
|
|
|
|
? 'bg-primary'
|
|
|
|
|
: i === currentIndex
|
|
|
|
|
? 'bg-primary/60'
|
|
|
|
|
: 'bg-muted'
|
|
|
|
|
}`}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-muted-foreground mt-2">
|
2026-02-04 00:58:22 +01:00
|
|
|
Step {currentIndex + 1} of {totalVisibleSteps}
|
2026-01-30 13:41:32 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Step 1: Name */}
|
|
|
|
|
{step === 'name' && (
|
|
|
|
|
<>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2">
|
|
|
|
|
<User className="h-5 w-5 text-primary" />
|
|
|
|
|
Welcome to MOPC
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Let's get your profile set up. What should we call you?
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="name">Full Name</Label>
|
|
|
|
|
<Input
|
|
|
|
|
id="name"
|
|
|
|
|
value={name}
|
|
|
|
|
onChange={(e) => setName(e.target.value)}
|
|
|
|
|
placeholder="Enter your full name"
|
|
|
|
|
autoFocus
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Button onClick={goNext} className="w-full" disabled={!name.trim()}>
|
|
|
|
|
Continue
|
|
|
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-02-04 00:58:22 +01:00
|
|
|
{/* Step 2: Phone (only if WhatsApp enabled) */}
|
|
|
|
|
{step === 'phone' && whatsappEnabled && (
|
2026-01-30 13:41:32 +01:00
|
|
|
<>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2">
|
|
|
|
|
<Phone className="h-5 w-5 text-primary" />
|
|
|
|
|
Contact Information
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Optionally add your phone number for WhatsApp notifications
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="phone">Phone Number (Optional)</Label>
|
2026-02-04 00:54:57 +01:00
|
|
|
<PhoneInput
|
2026-01-30 13:41:32 +01:00
|
|
|
id="phone"
|
|
|
|
|
value={phoneNumber}
|
2026-02-04 00:54:57 +01:00
|
|
|
onChange={(value) => setPhoneNumber(value || '')}
|
|
|
|
|
defaultCountry="MC"
|
|
|
|
|
placeholder="Enter phone number"
|
2026-01-30 13:41:32 +01:00
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
2026-02-04 00:54:57 +01:00
|
|
|
Select your country and enter your number for WhatsApp notifications
|
2026-01-30 13:41:32 +01:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button variant="outline" onClick={goBack} className="flex-1">
|
|
|
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
|
|
|
Back
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={goNext} className="flex-1">
|
|
|
|
|
Continue
|
|
|
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Step 3: Tags */}
|
|
|
|
|
{step === 'tags' && (
|
|
|
|
|
<>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2">
|
|
|
|
|
<Tags className="h-5 w-5 text-primary" />
|
|
|
|
|
Your Expertise
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Select tags that describe your areas of expertise. This helps us match you with relevant projects.
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
2026-02-04 00:56:03 +01:00
|
|
|
<ExpertiseSelect
|
|
|
|
|
value={expertiseTags}
|
|
|
|
|
onChange={setExpertiseTags}
|
|
|
|
|
maxTags={5}
|
|
|
|
|
/>
|
2026-01-30 13:41:32 +01:00
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button variant="outline" onClick={goBack} className="flex-1">
|
|
|
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
|
|
|
Back
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={goNext} className="flex-1">
|
|
|
|
|
Continue
|
|
|
|
|
<ArrowRight className="ml-2 h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Step 4: Preferences */}
|
|
|
|
|
{step === 'preferences' && (
|
|
|
|
|
<>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center gap-2">
|
|
|
|
|
<Bell className="h-5 w-5 text-primary" />
|
|
|
|
|
Notification Preferences
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
How would you like to receive notifications?
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label htmlFor="notifications">Notification Channel</Label>
|
|
|
|
|
<Select
|
|
|
|
|
value={notificationPreference}
|
|
|
|
|
onValueChange={(v) =>
|
|
|
|
|
setNotificationPreference(v as typeof notificationPreference)
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<SelectTrigger id="notifications">
|
|
|
|
|
<SelectValue />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="EMAIL">Email only</SelectItem>
|
2026-02-04 00:58:22 +01:00
|
|
|
{whatsappEnabled && (
|
|
|
|
|
<>
|
|
|
|
|
<SelectItem value="WHATSAPP" disabled={!phoneNumber}>
|
|
|
|
|
WhatsApp only
|
|
|
|
|
</SelectItem>
|
|
|
|
|
<SelectItem value="BOTH" disabled={!phoneNumber}>
|
|
|
|
|
Both Email and WhatsApp
|
|
|
|
|
</SelectItem>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2026-01-30 13:41:32 +01:00
|
|
|
<SelectItem value="NONE">No notifications</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
2026-02-04 00:58:22 +01:00
|
|
|
{whatsappEnabled && !phoneNumber && (
|
2026-01-30 13:41:32 +01:00
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Add a phone number to enable WhatsApp notifications
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="rounded-lg border p-4 bg-muted/50">
|
|
|
|
|
<h4 className="font-medium mb-2">Summary</h4>
|
|
|
|
|
<div className="space-y-1 text-sm">
|
|
|
|
|
<p>
|
|
|
|
|
<span className="text-muted-foreground">Name:</span> {name}
|
|
|
|
|
</p>
|
2026-02-04 00:58:22 +01:00
|
|
|
{whatsappEnabled && phoneNumber && (
|
2026-01-30 13:41:32 +01:00
|
|
|
<p>
|
|
|
|
|
<span className="text-muted-foreground">Phone:</span>{' '}
|
|
|
|
|
{phoneNumber}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<p>
|
|
|
|
|
<span className="text-muted-foreground">Expertise:</span>{' '}
|
|
|
|
|
{expertiseTags.length > 0
|
|
|
|
|
? expertiseTags.join(', ')
|
|
|
|
|
: 'None selected'}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button variant="outline" onClick={goBack} className="flex-1">
|
|
|
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
|
|
|
Back
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleComplete}
|
|
|
|
|
className="flex-1"
|
|
|
|
|
disabled={completeOnboarding.isPending}
|
|
|
|
|
>
|
|
|
|
|
{completeOnboarding.isPending ? (
|
|
|
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<CheckCircle className="mr-2 h-4 w-4" />
|
|
|
|
|
)}
|
|
|
|
|
Complete Setup
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Step 5: Complete */}
|
|
|
|
|
{step === 'complete' && (
|
|
|
|
|
<CardContent className="flex flex-col items-center justify-center py-12">
|
|
|
|
|
<div className="rounded-full bg-green-100 p-4 mb-4">
|
|
|
|
|
<CheckCircle className="h-12 w-12 text-green-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<h2 className="text-xl font-semibold mb-2">Welcome, {name}!</h2>
|
|
|
|
|
<p className="text-muted-foreground text-center mb-4">
|
|
|
|
|
Your profile is all set up. You'll be redirected to your dashboard
|
|
|
|
|
shortly.
|
|
|
|
|
</p>
|
|
|
|
|
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
|
|
|
|
</CardContent>
|
|
|
|
|
)}
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|