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

@@ -7,6 +7,7 @@ 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 { CountrySelect } from '@/components/ui/country-select'
import {
Card,
CardContent,
@@ -23,6 +24,7 @@ import {
} from '@/components/ui/select'
import { toast } from 'sonner'
import { ExpertiseSelect } from '@/components/shared/expertise-select'
import { AvatarUpload } from '@/components/shared/avatar-upload'
import {
User,
Phone,
@@ -32,9 +34,11 @@ import {
Loader2,
ArrowRight,
ArrowLeft,
Camera,
Globe,
} from 'lucide-react'
type Step = 'name' | 'phone' | 'tags' | 'preferences' | 'complete'
type Step = 'name' | 'photo' | 'country' | 'phone' | 'tags' | 'preferences' | 'complete'
export default function OnboardingPage() {
const router = useRouter()
@@ -43,6 +47,7 @@ export default function OnboardingPage() {
// Form state
const [name, setName] = useState('')
const [country, setCountry] = useState('')
const [phoneNumber, setPhoneNumber] = useState('')
const [expertiseTags, setExpertiseTags] = useState<string[]>([])
const [lockedTags, setLockedTags] = useState<string[]>([])
@@ -51,7 +56,8 @@ export default function OnboardingPage() {
>('EMAIL')
// Fetch current user data to get admin-preset tags
const { data: userData, isLoading: userLoading } = trpc.user.me.useQuery()
const { data: userData, isLoading: userLoading, refetch: refetchUser } = trpc.user.me.useQuery()
const { data: avatarUrl } = trpc.avatar.getUrl.useQuery()
// Initialize form with user data
useEffect(() => {
@@ -60,6 +66,10 @@ export default function OnboardingPage() {
if (userData.name) {
setName(userData.name)
}
// Pre-fill country if available
if (userData.country) {
setCountry(userData.country)
}
// Pre-fill phone if available
if (userData.phoneNumber) {
setPhoneNumber(userData.phoneNumber)
@@ -86,10 +96,10 @@ export default function OnboardingPage() {
// Dynamic steps based on WhatsApp availability
const steps: Step[] = useMemo(() => {
if (whatsappEnabled) {
return ['name', 'phone', 'tags', 'preferences', 'complete']
return ['name', 'photo', 'country', 'phone', 'tags', 'preferences', 'complete']
}
// Skip phone step if WhatsApp is disabled
return ['name', 'tags', 'preferences', 'complete']
return ['name', 'photo', 'country', 'tags', 'preferences', 'complete']
}, [whatsappEnabled])
const currentIndex = steps.indexOf(step)
@@ -117,6 +127,7 @@ export default function OnboardingPage() {
try {
await completeOnboarding.mutateAsync({
name,
country: country || undefined,
phoneNumber: phoneNumber || undefined,
expertiseTags,
notificationPreference,
@@ -127,7 +138,9 @@ export default function OnboardingPage() {
// Redirect after a short delay based on user role
setTimeout(() => {
const role = userData?.role
if (role === 'MENTOR') {
if (role === 'SUPER_ADMIN' || role === 'PROGRAM_ADMIN') {
router.push('/admin')
} else if (role === 'MENTOR') {
router.push('/mentor')
} else if (role === 'OBSERVER') {
router.push('/observer')
@@ -211,7 +224,85 @@ export default function OnboardingPage() {
</>
)}
{/* Step 2: Phone (only if WhatsApp enabled) */}
{/* Step 2: Profile Photo */}
{step === 'photo' && (
<>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Camera className="h-5 w-5 text-primary" />
Profile Photo
</CardTitle>
<CardDescription>
Add a profile photo so others can recognize you. This step is optional.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex justify-center">
<AvatarUpload
user={{
name: userData?.name,
email: userData?.email,
profileImageKey: userData?.profileImageKey,
}}
currentAvatarUrl={avatarUrl}
onUploadComplete={() => refetchUser()}
/>
</div>
<p className="text-sm text-muted-foreground text-center">
Click the avatar to upload a new photo. You can crop and adjust before saving.
</p>
<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">
{avatarUrl ? 'Continue' : 'Skip for now'}
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</CardContent>
</>
)}
{/* Step 3: Home Country */}
{step === 'country' && (
<>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Globe className="h-5 w-5 text-primary" />
Home Country
</CardTitle>
<CardDescription>
Select your home country. This helps us match you with relevant projects.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="country">Country</Label>
<CountrySelect
value={country}
onChange={setCountry}
placeholder="Select your country"
/>
</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 4: Phone (only if WhatsApp enabled) */}
{step === 'phone' && whatsappEnabled && (
<>
<CardHeader>
@@ -252,7 +343,7 @@ export default function OnboardingPage() {
</>
)}
{/* Step 3: Tags */}
{/* Step 5: Tags */}
{step === 'tags' && (
<>
<CardHeader>
@@ -286,7 +377,7 @@ export default function OnboardingPage() {
</>
)}
{/* Step 4: Preferences */}
{/* Step 6: Preferences */}
{step === 'preferences' && (
<>
<CardHeader>
@@ -338,6 +429,11 @@ export default function OnboardingPage() {
<p>
<span className="text-muted-foreground">Name:</span> {name}
</p>
{country && (
<p>
<span className="text-muted-foreground">Country:</span> {country}
</p>
)}
{whatsappEnabled && phoneNumber && (
<p>
<span className="text-muted-foreground">Phone:</span>{' '}
@@ -375,7 +471,7 @@ export default function OnboardingPage() {
</>
)}
{/* Step 5: Complete */}
{/* Step 7: 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">