MOPC-App/src/app/(auth)/onboarding/page.tsx

345 lines
12 KiB
TypeScript
Raw Normal View History

'use client'
import { useState, useMemo } from 'react'
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'
import { PhoneInput } from '@/components/ui/phone-input'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { toast } from 'sonner'
import { ExpertiseSelect } from '@/components/shared/expertise-select'
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')
// Fetch feature flags
const { data: featureFlags } = trpc.settings.getFeatureFlags.useQuery()
const whatsappEnabled = featureFlags?.whatsappEnabled ?? false
const completeOnboarding = trpc.user.completeOnboarding.useMutation()
// 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])
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 {
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 (
<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">
{/* 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">
Step {currentIndex + 1} of {totalVisibleSteps}
</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&apos;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>
</>
)}
{/* Step 2: Phone (only if WhatsApp enabled) */}
{step === 'phone' && whatsappEnabled && (
<>
<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>
<PhoneInput
id="phone"
value={phoneNumber}
onChange={(value) => setPhoneNumber(value || '')}
defaultCountry="MC"
placeholder="Enter phone number"
/>
<p className="text-xs text-muted-foreground">
Select your country and enter your number for WhatsApp notifications
</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">
<ExpertiseSelect
value={expertiseTags}
onChange={setExpertiseTags}
maxTags={5}
/>
<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>
{whatsappEnabled && (
<>
<SelectItem value="WHATSAPP" disabled={!phoneNumber}>
WhatsApp only
</SelectItem>
<SelectItem value="BOTH" disabled={!phoneNumber}>
Both Email and WhatsApp
</SelectItem>
</>
)}
<SelectItem value="NONE">No notifications</SelectItem>
</SelectContent>
</Select>
{whatsappEnabled && !phoneNumber && (
<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>
{whatsappEnabled && phoneNumber && (
<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&apos;ll be redirected to your dashboard
shortly.
</p>
<Loader2 className="h-6 w-6 animate-spin text-primary" />
</CardContent>
)}
</Card>
</div>
)
}