Remove dynamic form builder and complete RoundProject→roundId migration

Major cleanup and schema migration:
- Remove unused dynamic form builder system (ApplicationForm, ApplicationFormField, etc.)
- Complete migration from RoundProject junction table to direct Project.roundId
- Add sortOrder and entryNotificationType fields to Round model
- Add country field to User model for mentor matching
- Enhance onboarding with profile photo and country selection steps
- Fix all TypeScript errors related to roundProjects references
- Remove unused libraries (@radix-ui/react-toast, embla-carousel-react, vaul)

Files removed:
- admin/forms/* pages and related components
- admin/onboarding/* pages
- applicationForm.ts and onboarding.ts routers
- Dynamic form builder Prisma models and enums

Schema changes:
- Removed ApplicationForm, ApplicationFormField, OnboardingStep, ApplicationFormSubmission, SubmissionFile models
- Removed FormFieldType and SpecialFieldType enums
- Added Round.sortOrder, Round.entryNotificationType
- Added User.country

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 14:15:06 +01:00
parent 7bcd2ce6ca
commit 29827268b2
71 changed files with 2139 additions and 6609 deletions

View File

@@ -84,7 +84,7 @@ function EditRoundContent({ roundId }: { roundId: string }) {
const [formInitialized, setFormInitialized] = useState(false)
const [roundType, setRoundType] = useState<'FILTERING' | 'EVALUATION' | 'LIVE_EVENT'>('EVALUATION')
const [roundSettings, setRoundSettings] = useState<Record<string, unknown>>({})
const [entryNotificationType, setEntryNotificationType] = useState<string>('')
// entryNotificationType removed from schema
// Fetch round data - disable refetch on focus to prevent overwriting user's edits
const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery(
@@ -138,7 +138,6 @@ function EditRoundContent({ roundId }: { roundId: string }) {
// Set round type, settings, and notification type
setRoundType((round.roundType as typeof roundType) || 'EVALUATION')
setRoundSettings((round.settingsJson as Record<string, unknown>) || {})
setEntryNotificationType(round.entryNotificationType || '')
setFormInitialized(true)
}
}, [round, form, formInitialized])
@@ -166,7 +165,6 @@ function EditRoundContent({ roundId }: { roundId: string }) {
settingsJson: roundSettings,
votingStartAt: data.votingStartAt ?? null,
votingEndAt: data.votingEndAt ?? null,
entryNotificationType: entryNotificationType || null,
})
// Update evaluation form if criteria changed and no evaluations exist
@@ -353,38 +351,7 @@ function EditRoundContent({ roundId }: { roundId: string }) {
</CardContent>
</Card>
{/* Team Notification */}
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
<Bell className="h-5 w-5" />
Team Notification
</CardTitle>
<CardDescription>
Notification sent to project teams when they enter this round
</CardDescription>
</CardHeader>
<CardContent>
<Select
value={entryNotificationType || 'none'}
onValueChange={(val) => setEntryNotificationType(val === 'none' ? '' : val)}
>
<SelectTrigger>
<SelectValue placeholder="No automatic notification" />
</SelectTrigger>
<SelectContent>
{TEAM_NOTIFICATION_OPTIONS.map((option) => (
<SelectItem key={option.value || 'none'} value={option.value || 'none'}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground mt-2">
When projects advance to this round, the selected notification will be sent to the project team automatically.
</p>
</CardContent>
</Card>
{/* Team Notification - removed from schema, feature not implemented */}
{/* Evaluation Criteria */}
<Card>

View File

@@ -180,7 +180,7 @@ function LiveVotingContent({ roundId }: { roundId: string }) {
if (storedOrder.length > 0) {
setProjectOrder(storedOrder)
} else {
setProjectOrder(sessionData.round.roundProjects.map((rp) => rp.project.id))
setProjectOrder(sessionData.round.projects.map((p) => p.id))
}
}
}, [sessionData])
@@ -253,7 +253,7 @@ function LiveVotingContent({ roundId }: { roundId: string }) {
)
}
const projects = sessionData.round.roundProjects.map((rp) => rp.project)
const projects = sessionData.round.projects
const sortedProjects = projectOrder
.map((id) => projects.find((p) => p.id === id))
.filter((p): p is Project => !!p)

View File

@@ -367,7 +367,7 @@ function RoundDetailContent({ roundId }: { roundId: string }) {
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{round._count.roundProjects}</div>
<div className="text-2xl font-bold">{round._count.projects}</div>
<Button variant="link" size="sm" className="px-0" asChild>
<Link href={`/admin/projects?round=${round.id}`}>View projects</Link>
</Button>

View File

@@ -75,7 +75,7 @@ type RoundData = {
votingStartAt: string | null
votingEndAt: string | null
_count?: {
roundProjects: number
projects: number
assignments: number
}
}
@@ -238,7 +238,7 @@ function ProgramRounds({ program }: { program: any }) {
{round.name}
</span>
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
{round._count?.roundProjects || 0}
{round._count?.projects || 0}
</Badge>
</div>
{index < rounds.length - 1 && (
@@ -425,7 +425,7 @@ function SortableRoundRow({
{/* Projects */}
<div className="flex items-center gap-1.5">
<FileText className="h-4 w-4 text-muted-foreground" />
<span className="font-medium">{round._count?.roundProjects || 0}</span>
<span className="font-medium">{round._count?.projects || 0}</span>
</div>
{/* Assignments */}
@@ -509,7 +509,7 @@ function SortableRoundRow({
<AlertDialogTitle>Delete Round</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete &quot;{round.name}&quot;? This will
remove {round._count?.roundProjects || 0} project assignments,{' '}
remove {round._count?.projects || 0} project assignments,{' '}
{round._count?.assignments || 0} reviewer assignments, and all evaluations
in this round. The projects themselves will remain in the program. This action cannot be undone.
</AlertDialogDescription>