Add bio field and enhance smart assignment with bio matching
- Add bio field to User model for judge/mentor profile descriptions - Add bio step to onboarding wizard (optional step with 500 char limit) - Enhance smart assignment to match judge bio against project description - Uses keyword extraction and Jaccard-like similarity scoring - Only applies if judge has a bio (no penalty for empty bio) - Max 15 points for bio match on top of existing scoring - Fix geographic distribution query to use round relation for programId - Update score breakdown: tags (40), bio (15), workload (25), country (15) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
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,
|
||||
@@ -36,9 +37,10 @@ import {
|
||||
ArrowLeft,
|
||||
Camera,
|
||||
Globe,
|
||||
FileText,
|
||||
} from 'lucide-react'
|
||||
|
||||
type Step = 'name' | 'photo' | 'country' | 'phone' | 'tags' | 'preferences' | 'complete'
|
||||
type Step = 'name' | 'photo' | 'country' | 'bio' | 'phone' | 'tags' | 'preferences' | 'complete'
|
||||
|
||||
export default function OnboardingPage() {
|
||||
const router = useRouter()
|
||||
@@ -48,6 +50,7 @@ export default function OnboardingPage() {
|
||||
// Form state
|
||||
const [name, setName] = useState('')
|
||||
const [country, setCountry] = useState('')
|
||||
const [bio, setBio] = useState('')
|
||||
const [phoneNumber, setPhoneNumber] = useState('')
|
||||
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
|
||||
const [lockedTags, setLockedTags] = useState<string[]>([])
|
||||
@@ -70,6 +73,10 @@ export default function OnboardingPage() {
|
||||
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)
|
||||
@@ -96,10 +103,10 @@ export default function OnboardingPage() {
|
||||
// Dynamic steps based on WhatsApp availability
|
||||
const steps: Step[] = useMemo(() => {
|
||||
if (whatsappEnabled) {
|
||||
return ['name', 'photo', 'country', 'phone', 'tags', 'preferences', 'complete']
|
||||
return ['name', 'photo', 'country', 'bio', 'phone', 'tags', 'preferences', 'complete']
|
||||
}
|
||||
// Skip phone step if WhatsApp is disabled
|
||||
return ['name', 'photo', 'country', 'tags', 'preferences', 'complete']
|
||||
return ['name', 'photo', 'country', 'bio', 'tags', 'preferences', 'complete']
|
||||
}, [whatsappEnabled])
|
||||
|
||||
const currentIndex = steps.indexOf(step)
|
||||
@@ -128,6 +135,7 @@ export default function OnboardingPage() {
|
||||
await completeOnboarding.mutateAsync({
|
||||
name,
|
||||
country: country || undefined,
|
||||
bio: bio || undefined,
|
||||
phoneNumber: phoneNumber || undefined,
|
||||
expertiseTags,
|
||||
notificationPreference,
|
||||
@@ -302,7 +310,49 @@ export default function OnboardingPage() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Step 4: Phone (only if WhatsApp enabled) */}
|
||||
{/* Step 4: Bio */}
|
||||
{step === 'bio' && (
|
||||
<>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5 text-primary" />
|
||||
About You
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Tell us a bit about yourself and your expertise. This helps us match you with relevant projects. (Optional)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="bio">Bio</Label>
|
||||
<Textarea
|
||||
id="bio"
|
||||
value={bio}
|
||||
onChange={(e) => setBio(e.target.value)}
|
||||
placeholder="e.g., Marine biologist with 10 years experience in coral reef conservation and ocean acidification research..."
|
||||
rows={4}
|
||||
maxLength={500}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground text-right">
|
||||
{bio.length}/500 characters
|
||||
</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">
|
||||
{bio ? 'Continue' : 'Skip for now'}
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Step 5: Phone (only if WhatsApp enabled) */}
|
||||
{step === 'phone' && whatsappEnabled && (
|
||||
<>
|
||||
<CardHeader>
|
||||
@@ -343,7 +393,7 @@ export default function OnboardingPage() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Step 5: Tags */}
|
||||
{/* Step 6: Tags */}
|
||||
{step === 'tags' && (
|
||||
<>
|
||||
<CardHeader>
|
||||
@@ -377,7 +427,7 @@ export default function OnboardingPage() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Step 6: Preferences */}
|
||||
{/* Step 7: Preferences */}
|
||||
{step === 'preferences' && (
|
||||
<>
|
||||
<CardHeader>
|
||||
@@ -434,6 +484,12 @@ export default function OnboardingPage() {
|
||||
<span className="text-muted-foreground">Country:</span> {country}
|
||||
</p>
|
||||
)}
|
||||
{bio && (
|
||||
<p>
|
||||
<span className="text-muted-foreground">Bio:</span>{' '}
|
||||
{bio.length > 50 ? `${bio.substring(0, 50)}...` : bio}
|
||||
</p>
|
||||
)}
|
||||
{whatsappEnabled && phoneNumber && (
|
||||
<p>
|
||||
<span className="text-muted-foreground">Phone:</span>{' '}
|
||||
|
||||
Reference in New Issue
Block a user