diff --git a/app/api/dsoc/mentee/me/route.ts b/app/api/dsoc/mentee/me/route.ts new file mode 100644 index 0000000..315e08c --- /dev/null +++ b/app/api/dsoc/mentee/me/route.ts @@ -0,0 +1,45 @@ +import { NextRequest, NextResponse } from 'next/server'; +import connectDB from '@/lib/db'; +import { DSOCMentee } from '@/models/DSOCMentee'; +import jwt from 'jsonwebtoken'; + +export async function GET(request: NextRequest) { + try { + await connectDB(); + + const token = request.cookies.get('dsoc-mentee-token')?.value; + if (!token) { + return NextResponse.json({ success: false }, { status: 200 }); + } + + const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { + id: string; + role: string; + }; + + if (decoded.role !== 'dsoc-mentee') { + return NextResponse.json({ success: false }, { status: 200 }); + } + + const mentee = await DSOCMentee.findById(decoded.id) + .select('_id name email username isActive') + .lean(); + + if (!mentee || !mentee.isActive) { + return NextResponse.json({ success: false }, { status: 200 }); + } + + return NextResponse.json({ + success: true, + data: { + id: mentee._id, + name: mentee.name, + email: mentee.email, + username: mentee.username, + }, + }); + } catch (error) { + console.error('Error checking DSOC mentee session:', error); + return NextResponse.json({ success: false }, { status: 200 }); + } +} diff --git a/app/api/dsoc/projects/[id]/route.ts b/app/api/dsoc/projects/[id]/route.ts index 6b04aec..255cd63 100644 --- a/app/api/dsoc/projects/[id]/route.ts +++ b/app/api/dsoc/projects/[id]/route.ts @@ -1,5 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import connectDB from '@/lib/db'; +import '@/models/DSOCMentor'; +import '@/models/DSOCMentee'; import { DSOCProject } from '@/models/DSOCProject'; // GET single project by ID diff --git a/app/dsoc/apply/[id]/page.tsx b/app/dsoc/apply/[id]/page.tsx index 5d55463..e8b76e7 100644 --- a/app/dsoc/apply/[id]/page.tsx +++ b/app/dsoc/apply/[id]/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useState, useEffect, use } from "react"; -import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; import { ArrowLeft, ArrowRight, @@ -33,6 +33,24 @@ interface Project { mentors?: { name: string; company?: string }[]; } +type ApplicationFormValues = { + whyThisProject: string; + motivation: string; + relevantExperience: string; + technicalSkills: string; + portfolioLinks: string; + githubProfile: string; + previousContributions: string; + proposal: string; + timeline: string; + expectedLearnings: string; + challenges: string; + availability: string; + timezone: string; + startDate: string; + coverLetter: string; +}; + const STEPS = [ { id: 1, title: 'About You', icon: User, description: 'Your background' }, { id: 2, title: 'Experience', icon: Briefcase, description: 'Skills & projects' }, @@ -42,36 +60,88 @@ const STEPS = [ export default function ApplyPage({ params }: { params: Promise<{ id: string }> }) { const resolvedParams = use(params); - const router = useRouter(); const [project, setProject] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); const [currentStep, setCurrentStep] = useState(1); - - const [formData, setFormData] = useState({ - whyThisProject: '', - motivation: '', - relevantExperience: '', - technicalSkills: '', - portfolioLinks: '', - githubProfile: '', - previousContributions: '', - proposal: '', - timeline: '', - expectedLearnings: '', - challenges: '', - availability: '', - timezone: '', - startDate: '', - coverLetter: '' + const [isMentee, setIsMentee] = useState(null); + const { + register, + handleSubmit, + formState: { errors }, + trigger, + watch, + } = useForm({ + defaultValues: { + whyThisProject: '', + motivation: '', + relevantExperience: '', + technicalSkills: '', + portfolioLinks: '', + githubProfile: '', + previousContributions: '', + proposal: '', + timeline: '', + expectedLearnings: '', + challenges: '', + availability: '', + timezone: '', + startDate: '', + coverLetter: '', + }, + mode: 'onBlur', }); + const availabilityValue = watch('availability'); + + const stepFields: Record = { + 1: ['whyThisProject', 'motivation'], + 2: ['relevantExperience', 'technicalSkills', 'githubProfile', 'portfolioLinks'], + 3: ['proposal', 'timeline', 'expectedLearnings'], + 4: ['availability', 'timezone'], + }; + useEffect(() => { fetchProject(); }, [resolvedParams.id]); + useEffect(() => { + checkMenteeSession(); + }, []); + + const checkMenteeSession = async () => { + try { + const res = await fetch('/api/dsoc/mentee/me', { credentials: 'include' }); + const data = await res.json(); + setIsMentee(Boolean(data?.success)); + } catch (err) { + console.error('Error checking mentee session:', err); + setIsMentee(false); + } + }; + + const validateUrl = (value: string) => { + if (!value) return true; + try { + new URL(value); + return true; + } catch { + return false; + } + }; + + const validateUrlList = (value: string) => { + if (!value) return true; + const entries = value + .split(/[\n,]+/) + .map((entry) => entry.trim()) + .filter(Boolean); + if (entries.length === 0) return true; + return entries.every(validateUrl) || 'Enter a valid URL.'; + }; + const fetchProject = async () => { try { const res = await fetch(`/api/dsoc/projects/${resolvedParams.id}`); @@ -90,12 +160,15 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }> } }; - const handleChange = (e: React.ChangeEvent) => { - setFormData({ ...formData, [e.target.name]: e.target.value }); - }; - - const nextStep = () => { + const nextStep = async () => { if (currentStep < STEPS.length) { + const fields = stepFields[currentStep] || []; + const isValid = await trigger(fields); + if (!isValid) { + setError('Please fix the highlighted fields.'); + return; + } + setError(''); setCurrentStep(currentStep + 1); window.scrollTo({ top: 0, behavior: 'smooth' }); } @@ -108,19 +181,28 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }> } }; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + const onSubmit = async (values: ApplicationFormValues) => { setError(''); setSubmitting(true); + if (!isMentee) { + setError('Please login as a mentee to apply.'); + setSubmitting(false); + return; + } + + const portfolioLinks = values.portfolioLinks + ? values.portfolioLinks.split(/[\n,]+/).map((link) => link.trim()).filter(Boolean) + : []; + try { const res = await fetch('/api/dsoc/applications', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId: resolvedParams.id, - ...formData, - portfolioLinks: formData.portfolioLinks.split('\n').map(s => s.trim()).filter(Boolean) + ...values, + portfolioLinks, }) }); @@ -139,6 +221,10 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }> } }; + const onInvalid = () => { + setError('Please fix the highlighted fields.'); + }; + const formatDeadline = (date: string) => { return new Date(date).toLocaleDateString('en-US', { year: 'numeric', @@ -359,8 +445,21 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }> {/* Form */} -
+
+ {isMentee === false && ( +
+ Please login as a mentee to apply.{' '} + + Login + + {' '}or{' '} + + apply as a mentee + + {' '}first. +
+ )} {error && (
{error} @@ -388,14 +487,16 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }> What specifically attracted you to this project? What problems does it solve that excite you?