diff --git a/app/api/testimonials/route.ts b/app/api/testimonials/route.ts index b59d213..6fb1974 100644 --- a/app/api/testimonials/route.ts +++ b/app/api/testimonials/route.ts @@ -24,7 +24,7 @@ export async function POST(request: Request) { const data = await request.json(); // Basic validation - if (!data.name || !data.role || !data.content || !data.cta) { + if (!data.name || !data.role || !data.content || !data.linkedin || !data.cta) { return NextResponse.json( { error: 'Missing required fields' }, { status: 400 } diff --git a/app/dsoc/applications/[id]/page.tsx b/app/dsoc/applications/[id]/page.tsx new file mode 100644 index 0000000..06e0e38 --- /dev/null +++ b/app/dsoc/applications/[id]/page.tsx @@ -0,0 +1,444 @@ +'use client'; + +import Link from 'next/link'; +import { useEffect, useMemo, useState } from 'react'; +import { useParams } from 'next/navigation'; +import { + ArrowLeft, + Calendar, + CheckCircle, + Clock, + ExternalLink, + FileText, + Github, + Linkedin, + MessageCircle, + XCircle, + AlertCircle, +} from 'lucide-react'; +import '../../styles.css'; +import DSOCNavbar from '../../components/DSOCNavbar'; + +interface ApplicationMentee { + _id: string; + name: string; + email: string; + university?: string; + github?: string; + linkedin?: string; + picture?: string; +} + +interface ApplicationProject { + _id: string; + title: string; + organization?: string; + status?: string; +} + +interface Application { + _id: string; + status: string; + createdAt: string; + reviewedAt?: string; + score?: number; + mentorNotes?: string; + discordUsername?: string; + proposal: string; + coverLetter?: string; + relevantExperience: string; + whyThisProject: string; + availability: string; + expectedLearnings: string; + portfolioLinks: string[]; + previousContributions?: string; + mentee: ApplicationMentee; + project: ApplicationProject; +} + +export default function MentorApplicationReviewPage() { + const params = useParams<{ id: string }>(); + const applicationId = params?.id; + const [loading, setLoading] = useState(true); + const [application, setApplication] = useState(null); + const [error, setError] = useState(''); + const [saving, setSaving] = useState(false); + const [mentorNotes, setMentorNotes] = useState(''); + const [score, setScore] = useState(''); + + useEffect(() => { + if (applicationId) { + fetchApplication(applicationId); + } + }, [applicationId]); + + const fetchApplication = async (id: string) => { + try { + const res = await fetch(`/api/dsoc/applications/${id}`); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data?.error || 'Failed to fetch application'); + } + const data = await res.json(); + if (data.success) { + setApplication(data.data); + setMentorNotes(data.data.mentorNotes || ''); + setScore(data.data.score !== undefined ? String(data.data.score) : ''); + } else { + setError(data.error || 'Failed to fetch application'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to fetch application'); + } finally { + setLoading(false); + } + }; + + const updateApplication = async (status?: string) => { + if (!applicationId) return; + setSaving(true); + setError(''); + + try { + const payload: { status?: string; mentorNotes?: string; score?: number } = { + mentorNotes, + }; + + if (score.trim() !== '') { + payload.score = Number(score); + } + + if (status) { + payload.status = status; + } + + const res = await fetch(`/api/dsoc/applications/${applicationId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + const data = await res.json(); + if (!res.ok || !data.success) { + throw new Error(data?.error || 'Failed to update application'); + } + + setApplication(data.data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to update application'); + } finally { + setSaving(false); + } + }; + + const formatDate = (value?: string) => { + if (!value) return 'N/A'; + return new Date(value).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + const statusBadge = useMemo(() => { + if (!application) return null; + const status = application.status; + const label = status.replace('-', ' '); + const color = + status === 'accepted' + ? 'bg-[var(--dsoc-success)]' + : status === 'rejected' + ? 'bg-[var(--dsoc-pink)]' + : status === 'under-review' + ? 'bg-[var(--dsoc-secondary)]' + : 'bg-[var(--dsoc-accent)]'; + + return ( + + {label} + + ); + }, [application]); + + if (loading) { + return ( +
+ +
+
+
+

Loading application...

+
+
+
+ ); + } + + if (!application) { + return ( +
+ +
+
+ +

Application Not Found

+

{error || 'This application is unavailable.'}

+ + Back to Dashboard + +
+
+
+ ); + } + + return ( +
+ +
+
+
+
+ + + Back to Dashboard + + / + Application Review +
+ {statusBadge} +
+
+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+
+
+
+
+

{application.project?.title}

+

{application.project?.organization || 'Dev Weekends'}

+
+
+ + Submitted {formatDate(application.createdAt)} +
+
+
+ +
+

+ + Applicant Details +

+
+
+
Name
+
{application.mentee?.name}
+
+
+
Email
+
{application.mentee?.email}
+
+ {application.mentee?.university && ( +
+
University
+
{application.mentee.university}
+
+ )} + {application.discordUsername && ( +
+
Discord
+
{application.discordUsername}
+
+ )} +
+ +
+ {application.mentee?.github && ( + + + GitHub + + )} + {application.mentee?.linkedin && ( + + + LinkedIn + + )} +
+
+ +
+
+

Why This Project

+

{application.whyThisProject}

+
+
+

Relevant Experience

+

{application.relevantExperience}

+
+
+

Proposal

+

{application.proposal}

+
+ {application.expectedLearnings && ( +
+

Expected Learnings

+

{application.expectedLearnings}

+
+ )} + {application.coverLetter && ( +
+

Cover Letter

+

{application.coverLetter}

+
+ )} + {application.previousContributions && ( +
+

Previous Contributions

+

{application.previousContributions}

+
+ )} + {application.portfolioLinks?.length > 0 && ( +
+

Portfolio Links

+
+ {application.portfolioLinks.map((link, index) => ( + + + {link} + + ))} +
+
+ )} +
+
+ +
+
+

Review

+
+
+ Status + {statusBadge} +
+
+ Availability + {application.availability} +
+ {application.reviewedAt && ( +
+ Reviewed + {formatDate(application.reviewedAt)} +
+ )} +
+ +
+ + setScore(e.target.value)} + className="neo-brutal-input" + /> +
+ +
+ +