feat(mentor): rewrite project mentor-assignment page (§C)

Replaces single-section AI-only stub with three sections (Project Context,
Currently Assigned, Pick a Mentor). Pick a Mentor is a tab strip:
  - Manual Picker (default): all MENTOR-role users sorted by expertise
    overlap %, with search + load/capacity columns. Assign sends
    method=MANUAL.
  - AI Suggestions: existing pane, with an amber 'AI matching unavailable'
    banner + 'Tag overlap' pills when OPENAI_API_KEY is unset.

Plan: docs/superpowers/plans/2026-04-28-pr4-mentor-assignment-ux.md
This commit is contained in:
Matt
2026-04-28 14:56:46 +02:00
parent 4874491b18
commit ddae34c8f5

View File

@@ -1,7 +1,8 @@
'use client' 'use client'
import { Suspense, use, useState } from 'react' import { Suspense, use, useMemo, useState } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { trpc } from '@/lib/trpc/client' import { trpc } from '@/lib/trpc/client'
import { toast } from 'sonner' import { toast } from 'sonner'
import { import {
@@ -16,13 +17,25 @@ import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { Avatar, AvatarFallback } from '@/components/ui/avatar'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
import { Input } from '@/components/ui/input'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import {
AlertTriangle,
ArrowLeft, ArrowLeft,
Bot, Bot,
Loader2,
Users,
Check, Check,
RefreshCw, Loader2,
Search,
Sparkles,
Users,
} from 'lucide-react' } from 'lucide-react'
import { getInitials } from '@/lib/utils' import { getInitials } from '@/lib/utils'
@@ -30,89 +43,66 @@ interface PageProps {
params: Promise<{ id: string }> params: Promise<{ id: string }>
} }
// Type for mentor suggestion from the API
interface MentorSuggestion {
mentorId: string
confidenceScore: number
expertiseMatchScore: number
reasoning: string
mentor: {
id: string
name: string | null
email: string
expertiseTags: string[]
assignmentCount: number
} | null
}
function MentorAssignmentContent({ projectId }: { projectId: string }) { function MentorAssignmentContent({ projectId }: { projectId: string }) {
const router = useRouter() const router = useRouter()
const [selectedMentorId, setSelectedMentorId] = useState<string | null>(null)
const utils = trpc.useUtils() const utils = trpc.useUtils()
const [search, setSearch] = useState('')
const [pendingMentorId, setPendingMentorId] = useState<string | null>(null)
// Fetch project const { data: project, isLoading: projectLoading } = trpc.project.get.useQuery({ id: projectId })
const { data: project, isLoading: projectLoading } = trpc.project.get.useQuery({
id: projectId,
})
// Fetch suggestions const { data: candidatesData, isLoading: candidatesLoading } =
const { data: suggestions, isLoading: suggestionsLoading, refetch } = trpc.mentor.getSuggestions.useQuery( trpc.mentor.getCandidates.useQuery(
{ projectId, limit: 5 }, { projectId },
{ enabled: !!project && !project.mentorAssignment } { enabled: !!project && !project.mentorAssignment },
)
const {
data: suggestionsData,
isLoading: suggestionsLoading,
refetch: refetchSuggestions,
} = trpc.mentor.getSuggestions.useQuery(
{ projectId, limit: 5 },
{ enabled: !!project && !project.mentorAssignment },
) )
// Assign mentor mutation
const assignMutation = trpc.mentor.assign.useMutation({ const assignMutation = trpc.mentor.assign.useMutation({
onSuccess: () => { onSuccess: () => {
toast.success('Mentor assigned!') toast.success('Mentor assigned')
utils.project.get.invalidate({ id: projectId }) utils.project.get.invalidate({ id: projectId })
utils.mentor.getCandidates.invalidate({ projectId })
utils.mentor.getSuggestions.invalidate({ projectId }) utils.mentor.getSuggestions.invalidate({ projectId })
setPendingMentorId(null)
}, },
onError: (error) => { onError: (err) => {
toast.error(error.message) toast.error(err.message)
setPendingMentorId(null)
}, },
}) })
// Auto-assign mutation
const autoAssignMutation = trpc.mentor.autoAssign.useMutation({
onSuccess: () => {
toast.success('Mentor auto-assigned!')
utils.project.get.invalidate({ id: projectId })
utils.mentor.getSuggestions.invalidate({ projectId })
},
onError: (error) => {
toast.error(error.message)
},
})
// Unassign mutation
const unassignMutation = trpc.mentor.unassign.useMutation({ const unassignMutation = trpc.mentor.unassign.useMutation({
onSuccess: () => { onSuccess: () => {
toast.success('Mentor removed') toast.success('Mentor removed')
utils.project.get.invalidate({ id: projectId }) utils.project.get.invalidate({ id: projectId })
utils.mentor.getCandidates.invalidate({ projectId })
utils.mentor.getSuggestions.invalidate({ projectId }) utils.mentor.getSuggestions.invalidate({ projectId })
}, },
onError: (error) => { onError: (err) => toast.error(err.message),
toast.error(error.message)
},
}) })
const handleAssign = (mentorId: string, suggestion?: MentorSuggestion) => { const filteredCandidates = useMemo(() => {
assignMutation.mutate({ if (!candidatesData) return []
projectId, const q = search.trim().toLowerCase()
mentorId, if (!q) return candidatesData.candidates
method: suggestion ? 'AI_SUGGESTED' : 'MANUAL', return candidatesData.candidates.filter((c) => {
aiConfidenceScore: suggestion?.confidenceScore, const hay = [c.name ?? '', c.email, ...(c.expertiseTags ?? []), c.country ?? '']
expertiseMatchScore: suggestion?.expertiseMatchScore, .join(' ')
aiReasoning: suggestion?.reasoning, .toLowerCase()
return hay.includes(q)
}) })
} }, [candidatesData, search])
if (projectLoading) {
return <MentorAssignmentSkeleton />
}
if (projectLoading) return <MentorAssignmentSkeleton />
if (!project) { if (!project) {
return ( return (
<Card> <Card>
@@ -124,14 +114,38 @@ function MentorAssignmentContent({ projectId }: { projectId: string }) {
} }
const hasMentor = !!project.mentorAssignment const hasMentor = !!project.mentorAssignment
const teamSize = project.teamMembers?.length ?? 0
const aiSource = suggestionsData?.source ?? 'ai'
const handleAssignManual = (mentorId: string) => {
setPendingMentorId(mentorId)
assignMutation.mutate({ projectId, mentorId, method: 'MANUAL' })
}
const handleAssignFromSuggestion = (
mentorId: string,
suggestion: {
confidenceScore: number
expertiseMatchScore: number
reasoning: string
},
) => {
setPendingMentorId(mentorId)
assignMutation.mutate({
projectId,
mentorId,
method: aiSource === 'ai' ? 'AI_SUGGESTED' : 'ALGORITHM',
aiConfidenceScore: suggestion.confidenceScore,
expertiseMatchScore: suggestion.expertiseMatchScore,
aiReasoning: suggestion.reasoning,
})
}
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" className="-ml-4" onClick={() => router.back()}> <Button variant="ghost" className="-ml-4" onClick={() => router.back()}>
<ArrowLeft className="mr-2 h-4 w-4" /> <ArrowLeft className="mr-2 h-4 w-4" /> Back
Back
</Button> </Button>
</div> </div>
@@ -140,37 +154,99 @@ function MentorAssignmentContent({ projectId }: { projectId: string }) {
<p className="text-muted-foreground">{project.title}</p> <p className="text-muted-foreground">{project.title}</p>
</div> </div>
{/* Current Assignment */} {/* ─── Project Context ─── */}
{hasMentor && (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-lg">Current Mentor</CardTitle> <CardTitle className="text-lg">Project Context</CardTitle>
<CardDescription>What this project needs from a mentor</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="grid grid-cols-2 gap-x-6 gap-y-3 text-sm md:grid-cols-3">
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">Ocean Issue</div>
<div className="font-medium">{project.oceanIssue ?? '—'}</div>
</div>
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">Category</div>
<div className="font-medium">{project.competitionCategory ?? '—'}</div>
</div>
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">Country</div>
<div className="font-medium">{project.country ?? '—'}</div>
</div>
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">Team Size</div>
<div className="font-medium">{teamSize}</div>
</div>
<div>
<div className="text-muted-foreground text-xs uppercase tracking-wide">
Mentoring Requested
</div>
<div className="font-medium">{project.wantsMentorship ? 'Yes' : 'No'}</div>
</div>
</div>
{project.tags && project.tags.length > 0 && (
<div className="mt-4">
<div className="text-muted-foreground mb-1.5 text-xs uppercase tracking-wide">
Project Tags
</div>
<div className="flex flex-wrap gap-1">
{project.tags.map((tag: string) => (
<Badge key={tag} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
</div>
</div>
)}
</CardContent>
</Card>
{/* ─── Currently Assigned ─── */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Currently Assigned</CardTitle>
</CardHeader>
<CardContent>
{hasMentor ? (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Avatar className="h-12 w-12"> <Avatar className="h-12 w-12">
<AvatarFallback> <AvatarFallback>
{getInitials(project.mentorAssignment!.mentor.name || project.mentorAssignment!.mentor.email)} {getInitials(
project.mentorAssignment!.mentor.name ||
project.mentorAssignment!.mentor.email,
)}
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
<div> <div>
<p className="font-medium">{project.mentorAssignment!.mentor.name || 'Unnamed'}</p> <Link
<p className="text-sm text-muted-foreground">{project.mentorAssignment!.mentor.email}</p> href={`/admin/mentors/${project.mentorAssignment!.mentor.id}`}
{project.mentorAssignment!.mentor.expertiseTags && project.mentorAssignment!.mentor.expertiseTags.length > 0 && ( className="font-medium hover:underline"
<div className="flex flex-wrap gap-1 mt-1"> >
{project.mentorAssignment!.mentor.expertiseTags.slice(0, 3).map((tag: string) => ( {project.mentorAssignment!.mentor.name || 'Unnamed'}
<Badge key={tag} variant="secondary" className="text-xs">{tag}</Badge> </Link>
<p className="text-muted-foreground text-sm">
{project.mentorAssignment!.mentor.email}
</p>
{project.mentorAssignment!.mentor.expertiseTags &&
project.mentorAssignment!.mentor.expertiseTags.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{project.mentorAssignment!.mentor.expertiseTags
.slice(0, 5)
.map((tag: string) => (
<Badge key={tag} variant="secondary" className="text-xs">
{tag}
</Badge>
))} ))}
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="text-right"> <div className="flex flex-col items-end gap-2">
<Badge variant="outline" className="mb-2"> <Badge variant="outline" className="text-xs">
{project.mentorAssignment!.method.replace(/_/g, ' ')} {project.mentorAssignment!.method.replace(/_/g, ' ')}
</Badge> </Badge>
<div>
<Button <Button
variant="destructive" variant="destructive"
size="sm" size="sm"
@@ -180,39 +256,153 @@ function MentorAssignmentContent({ projectId }: { projectId: string }) {
{unassignMutation.isPending ? ( {unassignMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
'Remove' 'Unassign'
)} )}
</Button> </Button>
</div> </div>
</div> </div>
</div> ) : (
<p className="text-muted-foreground text-sm">
No mentor assigned yet pick one below.
</p>
)}
</CardContent> </CardContent>
</Card> </Card>
)}
{/* AI Suggestions */} {/* ─── Pick a Mentor ─── */}
{!hasMentor && ( {!hasMentor && (
<>
<Card> <Card>
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <CardTitle className="text-lg">Pick a Mentor</CardTitle>
<div>
<CardTitle className="text-lg flex items-center gap-2">
<Users className="h-5 w-5 text-primary" />
AI-Suggested Mentors
<Badge variant="outline" className="text-xs gap-1 shrink-0 ml-1">
<Bot className="h-3 w-3" />
AI Recommended
</Badge>
</CardTitle>
<CardDescription> <CardDescription>
Mentors matched based on expertise and project needs Browse all eligible mentors or use AI to surface the best fits.
</CardDescription> </CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="manual" className="space-y-4">
<TabsList>
<TabsTrigger value="manual">
<Users className="mr-2 h-4 w-4" /> Manual Picker
</TabsTrigger>
<TabsTrigger value="ai">
<Sparkles className="mr-2 h-4 w-4" /> AI Suggestions
</TabsTrigger>
</TabsList>
<TabsContent value="manual" className="space-y-4">
<div className="relative">
<Search className="text-muted-foreground absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2" />
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search by name, email, country, or expertise tag…"
className="pl-9"
/>
</div> </div>
<div className="flex gap-2"> {candidatesLoading ? (
<div className="space-y-2">
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-14 w-full" />
))}
</div>
) : filteredCandidates.length === 0 ? (
<div className="text-muted-foreground py-8 text-center text-sm">
No matching mentors. Try a different search.
</div>
) : (
<div className="overflow-hidden rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Mentor</TableHead>
<TableHead>Expertise</TableHead>
<TableHead>Country</TableHead>
<TableHead className="text-right">Load</TableHead>
<TableHead className="text-right">Overlap</TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredCandidates.map((c) => (
<TableRow key={c.id}>
<TableCell>
<div className="font-medium">{c.name ?? 'Unnamed'}</div>
<div className="text-muted-foreground text-xs">{c.email}</div>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{c.expertiseTags.slice(0, 4).map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
{c.expertiseTags.length > 4 && (
<Badge variant="outline" className="text-xs">
+{c.expertiseTags.length - 4}
</Badge>
)}
</div>
</TableCell>
<TableCell className="text-sm">{c.country ?? '—'}</TableCell>
<TableCell className="text-right text-sm tabular-nums">
{c.currentAssignments}
{c.maxAssignments != null ? `/${c.maxAssignments}` : ''}
</TableCell>
<TableCell className="text-right">
<Badge
variant={
c.overlapScore >= 0.5
? 'default'
: c.overlapScore > 0
? 'secondary'
: 'outline'
}
className="text-xs tabular-nums"
>
{Math.round(c.overlapScore * 100)}%
</Badge>
</TableCell>
<TableCell>
<Button
size="sm"
onClick={() => handleAssignManual(c.id)}
disabled={assignMutation.isPending}
>
{assignMutation.isPending && pendingMentorId === c.id ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<>
<Check className="mr-1 h-3.5 w-3.5" /> Assign
</>
)}
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</TabsContent>
<TabsContent value="ai" className="space-y-4">
{aiSource === 'fallback' && (
<div className="flex items-start gap-3 rounded-md border border-amber-300 bg-amber-50 p-3 text-sm dark:border-amber-700 dark:bg-amber-950/40">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-600" />
<div>
<p className="font-medium">AI matching unavailable</p>
<p className="text-muted-foreground">
Showing expertise-tag overlap instead. Configure{' '}
<code>OPENAI_API_KEY</code> to enable AI matching.
</p>
</div>
</div>
)}
<div className="flex items-center justify-end">
<Button <Button
variant="outline" variant="outline"
onClick={() => refetch()} size="sm"
onClick={() => refetchSuggestions()}
disabled={suggestionsLoading} disabled={suggestionsLoading}
> >
{suggestionsLoading ? ( {suggestionsLoading ? (
@@ -221,122 +411,106 @@ function MentorAssignmentContent({ projectId }: { projectId: string }) {
'Refresh' 'Refresh'
)} )}
</Button> </Button>
<Button
onClick={() => autoAssignMutation.mutate({ projectId, useAI: true })}
disabled={autoAssignMutation.isPending}
>
{autoAssignMutation.isPending ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
) : (
<RefreshCw className="mr-2 h-4 w-4" />
)}
Auto-Assign Best Match
</Button>
</div> </div>
</div>
</CardHeader>
<CardContent>
{suggestionsLoading ? ( {suggestionsLoading ? (
<div className="space-y-4"> <div className="space-y-3">
{[1, 2, 3].map((i) => ( {[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-24 w-full" /> <Skeleton key={i} className="h-24 w-full" />
))} ))}
</div> </div>
) : suggestions?.suggestions.length === 0 ? ( ) : !suggestionsData || suggestionsData.suggestions.length === 0 ? (
<p className="text-muted-foreground text-center py-8"> <p className="text-muted-foreground py-8 text-center text-sm">
No mentor suggestions available. Try adding more users with expertise tags. No suggestions available.
</p> </p>
) : ( ) : (
<div className="space-y-4"> <div className="space-y-3">
{suggestions?.suggestions.map((suggestion, index) => ( {suggestionsData.suggestions.map((s, i) => (
<div <div
key={suggestion.mentorId} key={s.mentorId}
className={`p-4 rounded-lg border-2 transition-colors ${ className="flex items-start justify-between rounded-md border p-4"
selectedMentorId === suggestion.mentorId
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50'
}`}
> >
<div className="flex items-start justify-between gap-4"> <div className="flex flex-1 gap-3">
<div className="flex items-start gap-4 flex-1">
<div className="relative"> <div className="relative">
<Avatar className="h-12 w-12"> <Avatar className="h-12 w-12">
<AvatarFallback> <AvatarFallback>
{suggestion.mentor ? getInitials(suggestion.mentor.name || suggestion.mentor.email) : '?'} {s.mentor
? getInitials(s.mentor.name || s.mentor.email)
: '?'}
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
{index === 0 && ( {i === 0 && (
<div className="absolute -top-1 -right-1 h-5 w-5 rounded-full bg-primary text-primary-foreground flex items-center justify-center text-xs font-bold"> <div className="bg-primary text-primary-foreground absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full text-xs font-bold">
1 1
</div> </div>
)} )}
</div> </div>
<div className="flex-1 min-w-0"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<p className="font-medium">{suggestion.mentor?.name || 'Unnamed'}</p> <p className="font-medium">{s.mentor?.name || 'Unnamed'}</p>
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="gap-1 text-xs">
{suggestion.mentor?.assignmentCount || 0} projects <Bot className="h-3 w-3" />{' '}
{aiSource === 'ai' ? 'AI' : 'Tag overlap'}
</Badge> </Badge>
</div> </div>
<p className="text-sm text-muted-foreground">{suggestion.mentor?.email}</p> <p className="text-muted-foreground text-sm">{s.mentor?.email}</p>
{s.mentor?.expertiseTags && s.mentor.expertiseTags.length > 0 && (
{/* Expertise tags */} <div className="mt-2 flex flex-wrap gap-1">
{suggestion.mentor?.expertiseTags && suggestion.mentor.expertiseTags.length > 0 && ( {s.mentor.expertiseTags.slice(0, 5).map((tag) => (
<div className="flex flex-wrap gap-1 mt-2">
{suggestion.mentor.expertiseTags.map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs"> <Badge key={tag} variant="secondary" className="text-xs">
{tag} {tag}
</Badge> </Badge>
))} ))}
</div> </div>
)} )}
<div className="mt-3 space-y-1.5">
{/* Match scores */} <div className="flex items-center gap-2 text-xs">
<div className="mt-3 space-y-2">
<div className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground w-28">Confidence:</span> <span className="text-muted-foreground w-28">Confidence:</span>
<Progress value={suggestion.confidenceScore * 100} className="flex-1 h-2" /> <Progress value={s.confidenceScore * 100} className="h-1.5 flex-1" />
<span className="w-12 text-right">{(suggestion.confidenceScore * 100).toFixed(0)}%</span> <span className="w-10 text-right tabular-nums">
{Math.round(s.confidenceScore * 100)}%
</span>
</div> </div>
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center gap-2 text-xs">
<span className="text-muted-foreground w-28">Expertise Match:</span> <span className="text-muted-foreground w-28">
<Progress value={suggestion.expertiseMatchScore * 100} className="flex-1 h-2" /> Expertise Match:
<span className="w-12 text-right">{(suggestion.expertiseMatchScore * 100).toFixed(0)}%</span> </span>
<Progress
value={s.expertiseMatchScore * 100}
className="h-1.5 flex-1"
/>
<span className="w-10 text-right tabular-nums">
{Math.round(s.expertiseMatchScore * 100)}%
</span>
</div> </div>
</div> </div>
{s.reasoning && (
{/* AI Reasoning */} <p className="text-muted-foreground mt-2 text-xs italic">
{suggestion.reasoning && ( &quot;{s.reasoning}&quot;
<p className="mt-2 text-sm text-muted-foreground italic">
&quot;{suggestion.reasoning}&quot;
</p> </p>
)} )}
</div> </div>
</div> </div>
<Button <Button
onClick={() => handleAssign(suggestion.mentorId, suggestion)} size="sm"
onClick={() => handleAssignFromSuggestion(s.mentorId, s)}
disabled={assignMutation.isPending} disabled={assignMutation.isPending}
variant={selectedMentorId === suggestion.mentorId ? 'default' : 'outline'}
> >
{assignMutation.isPending && selectedMentorId === suggestion.mentorId ? ( {assignMutation.isPending && pendingMentorId === s.mentorId ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : ( ) : (
<> <>
<Check className="mr-2 h-4 w-4" /> <Check className="mr-1 h-3.5 w-3.5" /> Assign
Assign
</> </>
)} )}
</Button> </Button>
</div> </div>
</div>
))} ))}
</div> </div>
)} )}
</TabsContent>
</Tabs>
</CardContent> </CardContent>
</Card> </Card>
</>
)} )}
</div> </div>
) )
@@ -353,14 +527,9 @@ function MentorAssignmentSkeleton() {
<Card> <Card>
<CardHeader> <CardHeader>
<Skeleton className="h-6 w-48" /> <Skeleton className="h-6 w-48" />
<Skeleton className="h-4 w-64" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <Skeleton className="h-32 w-full" />
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-24 w-full" />
))}
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
@@ -369,7 +538,6 @@ function MentorAssignmentSkeleton() {
export default function MentorAssignmentPage({ params }: PageProps) { export default function MentorAssignmentPage({ params }: PageProps) {
const { id } = use(params) const { id } = use(params)
return ( return (
<Suspense fallback={<MentorAssignmentSkeleton />}> <Suspense fallback={<MentorAssignmentSkeleton />}>
<MentorAssignmentContent projectId={id} /> <MentorAssignmentContent projectId={id} />