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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user