Competition/Round architecture: full platform rewrite (Phases 1-9)
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
141
src/app/(mentor)/mentor/workspace/[projectId]/page.tsx
Normal file
141
src/app/(mentor)/mentor/workspace/[projectId]/page.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
'use client'
|
||||
|
||||
import { useParams } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { WorkspaceChat } from '@/components/mentor/workspace-chat'
|
||||
import { FilePromotionPanel } from '@/components/mentor/file-promotion-panel'
|
||||
import { ArrowLeft, MessageSquare, FileText, Upload } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function MentorWorkspaceDetailPage() {
|
||||
const params = useParams()
|
||||
const projectId = params.projectId as string
|
||||
|
||||
// Get mentor assignment for this project
|
||||
const { data: assignments } = trpc.mentor.getMyProjects.useQuery()
|
||||
const assignment = assignments?.find(a => a.projectId === projectId)
|
||||
|
||||
const { data: project, isLoading } = trpc.project.get.useQuery(
|
||||
{ id: projectId },
|
||||
{ enabled: !!projectId }
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Skeleton className="h-8 w-64" />
|
||||
<Skeleton className="h-96" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={'/mentor/workspace' as Route}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back
|
||||
</Link>
|
||||
</Button>
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<FileText className="h-12 w-12 text-muted-foreground/50 mb-3" />
|
||||
<p className="font-medium">Project not found</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={'/mentor/workspace' as Route}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<h1 className="text-2xl font-bold tracking-tight">{project.title}</h1>
|
||||
{project.status && (
|
||||
<Badge variant="secondary">{project.status}</Badge>
|
||||
)}
|
||||
</div>
|
||||
{project.teamName && (
|
||||
<p className="text-muted-foreground mt-1">{project.teamName}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="chat" className="w-full">
|
||||
<TabsList className="w-full sm:w-auto grid grid-cols-3 sm:inline-grid">
|
||||
<TabsTrigger value="chat" className="flex items-center gap-2">
|
||||
<MessageSquare className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Chat</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="files" className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Files</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="promotion" className="flex items-center gap-2">
|
||||
<Upload className="h-4 w-4" />
|
||||
<span className="hidden sm:inline">Promotion</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="chat" className="mt-6">
|
||||
{assignment ? (
|
||||
<WorkspaceChat mentorAssignmentId={assignment.id} />
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="text-center py-8">
|
||||
<MessageSquare className="h-12 w-12 text-muted-foreground/50 mx-auto mb-3" />
|
||||
<p className="text-sm text-muted-foreground">Loading workspace...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="files" className="mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Workspace Files</CardTitle>
|
||||
<CardDescription>
|
||||
Files shared in the mentor workspace
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center py-8">
|
||||
<FileText className="h-12 w-12 text-muted-foreground/50 mx-auto mb-3" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
File listing feature coming soon
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="promotion" className="mt-6">
|
||||
{assignment ? (
|
||||
<FilePromotionPanel mentorAssignmentId={assignment.id} />
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="text-center py-8">
|
||||
<Upload className="h-12 w-12 text-muted-foreground/50 mx-auto mb-3" />
|
||||
<p className="text-sm text-muted-foreground">Loading workspace...</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
128
src/app/(mentor)/mentor/workspace/page.tsx
Normal file
128
src/app/(mentor)/mentor/workspace/page.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import type { Route } from 'next'
|
||||
import { trpc } from '@/lib/trpc/client'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { ArrowLeft, ArrowRight, Users, Briefcase } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const statusColors: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
|
||||
SUBMITTED: 'secondary',
|
||||
ELIGIBLE: 'default',
|
||||
SEMIFINALIST: 'default',
|
||||
FINALIST: 'default',
|
||||
WINNER: 'default',
|
||||
REJECTED: 'destructive',
|
||||
}
|
||||
|
||||
export default function MentorWorkspacePage() {
|
||||
const { data: assignments, isLoading } = trpc.mentor.getMyProjects.useQuery()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Skeleton className="h-8 w-64" />
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Skeleton key={i} className="h-40" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const projects = assignments || []
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">Mentor Workspace</h1>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Collaborate with your assigned mentee projects
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={'/mentor' as Route}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Back to Dashboard
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<div className="rounded-2xl bg-brand-teal/10 p-4 mb-4">
|
||||
<Briefcase className="h-8 w-8 text-brand-teal/60" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold mb-2">No Projects Assigned</h2>
|
||||
<p className="text-muted-foreground text-center max-w-md">
|
||||
You don't have any mentee projects assigned yet. Check back later.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((assignment) => {
|
||||
const project = assignment.project
|
||||
const teamSize = project.teamMembers?.length || 0
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={assignment.id}
|
||||
className="flex flex-col transition-all duration-200 hover:-translate-y-0.5 hover:shadow-md"
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-lg truncate" title={project.title}>
|
||||
{project.title}
|
||||
</CardTitle>
|
||||
{project.teamName && (
|
||||
<p className="text-sm text-muted-foreground mt-1 truncate">
|
||||
{project.teamName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{project.status && (
|
||||
<Badge variant={statusColors[project.status] || 'secondary'} className="shrink-0 ml-2">
|
||||
{project.status}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 flex flex-col space-y-4">
|
||||
{project.description && (
|
||||
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||
{project.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="flex items-center justify-between text-sm pt-3 border-t">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<Users className="h-4 w-4" />
|
||||
<span>{teamSize} member{teamSize !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<Link href={`/mentor/workspace/${project.id}` as Route}>
|
||||
Open
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user