Skip to content

Commit f5484a6

Browse files
Merge pull request #29 from devweekends/fellowship-update
add: add multiple features and fix bugs
2 parents 22eedf6 + 921f4fe commit f5484a6

15 files changed

Lines changed: 511 additions & 180 deletions

File tree

app/admin/dsoc/page.tsx

Lines changed: 213 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,35 @@ interface Application {
4141
project: { _id: string; title: string };
4242
}
4343

44+
interface Mentor {
45+
_id: string;
46+
name: string;
47+
email: string;
48+
company?: string;
49+
jobTitle?: string;
50+
picture?: string;
51+
expertise?: string[];
52+
isActive: boolean;
53+
isVerified: boolean;
54+
createdAt?: string;
55+
}
56+
57+
interface Mentee {
58+
_id: string;
59+
name: string;
60+
email: string;
61+
university?: string;
62+
degree?: string;
63+
picture?: string;
64+
skills?: string[];
65+
mentor?: { _id: string; name: string; company?: string; jobTitle?: string; picture?: string } | string;
66+
projects?: Array<string | { _id: string }>;
67+
applications?: Array<string | { _id: string }>;
68+
isActive: boolean;
69+
isVerified: boolean;
70+
createdAt?: string;
71+
}
72+
4473
interface Stats {
4574
projects: { total: number; open: number; inProgress: number; completed: number };
4675
mentors: number;
@@ -52,6 +81,8 @@ export default function AdminDSOCPage() {
5281
const [activeTab, setActiveTab] = useState('overview');
5382
const [projects, setProjects] = useState<Project[]>([]);
5483
const [applications, setApplications] = useState<Application[]>([]);
84+
const [mentors, setMentors] = useState<Mentor[]>([]);
85+
const [mentees, setMentees] = useState<Mentee[]>([]);
5586
const [stats, setStats] = useState<Stats | null>(null);
5687
const [loading, setLoading] = useState(true);
5788
const [search, setSearch] = useState('');
@@ -62,21 +93,27 @@ export default function AdminDSOCPage() {
6293

6394
const fetchData = async () => {
6495
try {
65-
const [statsRes, projectsRes, appsRes] = await Promise.all([
96+
const [statsRes, projectsRes, appsRes, mentorsRes, menteesRes] = await Promise.all([
6697
fetch('/api/dsoc/stats'),
6798
fetch('/api/dsoc/projects?limit=100'),
68-
fetch('/api/dsoc/applications')
99+
fetch('/api/dsoc/applications'),
100+
fetch('/api/dsoc/mentors'),
101+
fetch('/api/dsoc/mentees')
69102
]);
70103

71-
const [statsData, projectsData, appsData] = await Promise.all([
104+
const [statsData, projectsData, appsData, mentorsData, menteesData] = await Promise.all([
72105
statsRes.json(),
73106
projectsRes.json(),
74-
appsRes.json()
107+
appsRes.json(),
108+
mentorsRes.json(),
109+
menteesRes.json()
75110
]);
76111

77112
if (statsData.success) setStats(statsData.data);
78113
if (projectsData.success) setProjects(projectsData.data);
79114
if (appsData.success) setApplications(appsData.data);
115+
if (mentorsData.success) setMentors(mentorsData.data || []);
116+
if (menteesData.success) setMentees(menteesData.data || []);
80117
} catch (error) {
81118
console.error('Error fetching data:', error);
82119
} finally {
@@ -398,23 +435,183 @@ export default function AdminDSOCPage() {
398435

399436
{/* Mentors Tab */}
400437
{activeTab === 'mentors' && (
401-
<div className="neo-brutal-card p-12 text-center">
402-
<Users className="w-16 h-16 mx-auto text-muted-foreground mb-4" />
403-
<h3 className="text-xl font-bold mb-2">Mentor Management</h3>
404-
<p className="text-muted-foreground">
405-
Coming soon - manage mentor profiles, verification, and project assignments.
406-
</p>
438+
<div className="space-y-6">
439+
<div className="neo-brutal-card p-6 flex items-center justify-between gap-4 flex-wrap">
440+
<div>
441+
<h3 className="text-xl font-bold mb-1">Mentor Management</h3>
442+
<p className="text-muted-foreground">
443+
Review mentor profiles, see verification state, and open the public mentor page.
444+
</p>
445+
</div>
446+
<div className="flex items-center gap-3 text-sm font-bold uppercase tracking-wider">
447+
<span className="neo-brutal-badge bg-[var(--dsoc-secondary)] text-white">
448+
Total {mentors.length}
449+
</span>
450+
<span className="neo-brutal-badge bg-[var(--dsoc-success)] text-white">
451+
Active {mentors.filter((mentor) => mentor.isActive).length}
452+
</span>
453+
</div>
454+
</div>
455+
456+
{mentors.length === 0 ? (
457+
<div className="neo-brutal-card p-12 text-center">
458+
<Users className="w-16 h-16 mx-auto text-muted-foreground mb-4" />
459+
<h3 className="text-xl font-bold mb-2">No mentors found</h3>
460+
<p className="text-muted-foreground">
461+
Create a mentor from the DSOC mentor registration page or seed one in the database.
462+
</p>
463+
</div>
464+
) : (
465+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
466+
{mentors.map((mentor) => (
467+
<div key={mentor._id} className="neo-brutal-card p-5 flex flex-col gap-4">
468+
<div className="flex items-start gap-4">
469+
<div className="w-14 h-14 rounded-full overflow-hidden border-4 border-[var(--dsoc-dark)] bg-[var(--dsoc-dark)] text-white flex items-center justify-center font-black shrink-0">
470+
{mentor.picture ? (
471+
<img src={mentor.picture} alt={mentor.name} className="w-full h-full object-cover" />
472+
) : (
473+
mentor.name
474+
.split(' ')
475+
.map((part) => part[0])
476+
.join('')
477+
.slice(0, 2)
478+
)}
479+
</div>
480+
481+
<div className="min-w-0 flex-1">
482+
<h4 className="font-bold text-lg leading-tight">{mentor.name}</h4>
483+
<p className="text-sm text-muted-foreground truncate">{mentor.email}</p>
484+
<p className="text-sm text-muted-foreground mt-1">
485+
{mentor.jobTitle || 'Mentor'}{mentor.company ? ` · ${mentor.company}` : ''}
486+
</p>
487+
</div>
488+
</div>
489+
490+
<div className="flex flex-wrap gap-2 text-xs font-bold uppercase tracking-wider">
491+
<span className={`neo-brutal-badge ${mentor.isActive ? 'bg-[var(--dsoc-success)] text-white' : 'bg-gray-300 text-black'}`}>
492+
{mentor.isActive ? 'Active' : 'Inactive'}
493+
</span>
494+
<span className={`neo-brutal-badge ${mentor.isVerified ? 'bg-[var(--dsoc-primary)] text-white' : 'bg-[var(--dsoc-pink)] text-white'}`}>
495+
{mentor.isVerified ? 'Verified' : 'Pending Review'}
496+
</span>
497+
</div>
498+
499+
{mentor.expertise && mentor.expertise.length > 0 && (
500+
<div className="flex flex-wrap gap-2">
501+
{mentor.expertise.slice(0, 4).map((skill) => (
502+
<span key={skill} className="px-2 py-1 text-xs font-bold border-2 border-[var(--dsoc-dark)] bg-background">
503+
{skill}
504+
</span>
505+
))}
506+
</div>
507+
)}
508+
509+
<Link
510+
href="/mentor"
511+
className="neo-brutal-btn neo-brutal-btn-secondary w-full justify-center"
512+
>
513+
View Mentor Portal
514+
</Link>
515+
</div>
516+
))}
517+
</div>
518+
)}
407519
</div>
408520
)}
409521

410522
{/* Mentees Tab */}
411523
{activeTab === 'mentees' && (
412-
<div className="neo-brutal-card p-12 text-center">
413-
<Users className="w-16 h-16 mx-auto text-muted-foreground mb-4" />
414-
<h3 className="text-xl font-bold mb-2">Mentee Management</h3>
415-
<p className="text-muted-foreground">
416-
Coming soon - manage mentee profiles and project participation.
417-
</p>
524+
<div className="space-y-6">
525+
<div className="neo-brutal-card p-6 flex items-center justify-between gap-4 flex-wrap">
526+
<div>
527+
<h3 className="text-xl font-bold mb-1">Mentee Management</h3>
528+
<p className="text-muted-foreground">
529+
Review mentee profiles, verify status, and see their mentor/project connections.
530+
</p>
531+
</div>
532+
<div className="flex items-center gap-3 text-sm font-bold uppercase tracking-wider">
533+
<span className="neo-brutal-badge bg-[var(--dsoc-secondary)] text-white">
534+
Total {mentees.length}
535+
</span>
536+
<span className="neo-brutal-badge bg-[var(--dsoc-success)] text-white">
537+
Active {mentees.filter((mentee) => mentee.isActive).length}
538+
</span>
539+
</div>
540+
</div>
541+
542+
{mentees.length === 0 ? (
543+
<div className="neo-brutal-card p-12 text-center">
544+
<Users className="w-16 h-16 mx-auto text-muted-foreground mb-4" />
545+
<h3 className="text-xl font-bold mb-2">No mentees found</h3>
546+
<p className="text-muted-foreground">
547+
Create a mentee from the DSOC mentee registration page or seed one in the database.
548+
</p>
549+
</div>
550+
) : (
551+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
552+
{mentees.map((mentee) => {
553+
const mentorName = typeof mentee.mentor === 'object' ? mentee.mentor.name : null;
554+
555+
return (
556+
<div key={mentee._id} className="neo-brutal-card p-5 flex flex-col gap-4">
557+
<div className="flex items-start gap-4">
558+
<div className="w-14 h-14 rounded-full overflow-hidden border-4 border-[var(--dsoc-dark)] bg-[var(--dsoc-dark)] text-white flex items-center justify-center font-black shrink-0">
559+
{mentee.picture ? (
560+
<img src={mentee.picture} alt={mentee.name} className="w-full h-full object-cover" />
561+
) : (
562+
mentee.name
563+
.split(' ')
564+
.map((part) => part[0])
565+
.join('')
566+
.slice(0, 2)
567+
)}
568+
</div>
569+
570+
<div className="min-w-0 flex-1">
571+
<h4 className="font-bold text-lg leading-tight">{mentee.name}</h4>
572+
<p className="text-sm text-muted-foreground truncate">{mentee.email}</p>
573+
<p className="text-sm text-muted-foreground mt-1">
574+
{mentee.degree || 'Mentee'}{mentee.university ? ` · ${mentee.university}` : ''}
575+
</p>
576+
</div>
577+
</div>
578+
579+
<div className="flex flex-wrap gap-2 text-xs font-bold uppercase tracking-wider">
580+
<span className={`neo-brutal-badge ${mentee.isActive ? 'bg-[var(--dsoc-success)] text-white' : 'bg-gray-300 text-black'}`}>
581+
{mentee.isActive ? 'Active' : 'Inactive'}
582+
</span>
583+
<span className={`neo-brutal-badge ${mentee.isVerified ? 'bg-[var(--dsoc-primary)] text-white' : 'bg-[var(--dsoc-pink)] text-white'}`}>
584+
{mentee.isVerified ? 'Verified' : 'Pending Review'}
585+
</span>
586+
</div>
587+
588+
<div className="text-sm text-muted-foreground space-y-1">
589+
<p><span className="font-bold text-foreground">Mentor:</span> {mentorName || 'Unassigned'}</p>
590+
<p><span className="font-bold text-foreground">Projects:</span> {mentee.projects?.length || 0}</p>
591+
<p><span className="font-bold text-foreground">Applications:</span> {mentee.applications?.length || 0}</p>
592+
</div>
593+
594+
{mentee.skills && mentee.skills.length > 0 && (
595+
<div className="flex flex-wrap gap-2">
596+
{mentee.skills.slice(0, 4).map((skill) => (
597+
<span key={skill} className="px-2 py-1 text-xs font-bold border-2 border-[var(--dsoc-dark)] bg-background">
598+
{skill}
599+
</span>
600+
))}
601+
</div>
602+
)}
603+
604+
<Link
605+
href="/dsoc/register/mentee"
606+
className="neo-brutal-btn neo-brutal-btn-secondary w-full justify-center"
607+
>
608+
View Mentee Portal
609+
</Link>
610+
</div>
611+
);
612+
})}
613+
</div>
614+
)}
418615
</div>
419616
)}
420617
</main>

app/admin/dsoc/projects/[id]/edit/page.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client';
22

33
import Link from "next/link";
4-
import { useState, useEffect, use } from "react";
4+
import { useState, useEffect } from "react";
55
import { useRouter } from "next/navigation";
6+
import { useParams } from "next/navigation";
67
import {
78
ArrowLeft,
89
Save,
@@ -23,8 +24,9 @@ interface MentorOption {
2324
expertise?: string[];
2425
}
2526

26-
export default function EditProjectPage({ params }: { params: Promise<{ id: string }> }) {
27-
const resolvedParams = use(params);
27+
export default function EditProjectPage() {
28+
const routeParams = useParams<{ id: string }>();
29+
const projectId = routeParams?.id;
2830
const router = useRouter();
2931
const [loading, setLoading] = useState(true);
3032
const [mentorLoading, setMentorLoading] = useState(true);
@@ -63,7 +65,7 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri
6365
useEffect(() => {
6466
fetchProject();
6567
fetchMentors();
66-
}, [resolvedParams.id]);
68+
}, [projectId]);
6769

6870
const fetchMentors = async () => {
6971
try {
@@ -84,8 +86,13 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri
8486
};
8587

8688
const fetchProject = async () => {
89+
if (!projectId) {
90+
setLoading(true);
91+
return;
92+
}
93+
8794
try {
88-
const res = await fetch(`/api/dsoc/projects/${resolvedParams.id}`);
95+
const res = await fetch(`/api/dsoc/projects/${projectId}`);
8996
const data = await res.json();
9097

9198
if (data.success) {
@@ -204,7 +211,7 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri
204211
featuredImage = await uploadImageToCloudinary(imageFile);
205212
}
206213

207-
const res = await fetch(`/api/dsoc/projects/${resolvedParams.id}`, {
214+
const res = await fetch(`/api/dsoc/projects/${projectId}`, {
208215
method: 'PUT',
209216
headers: { 'Content-Type': 'application/json' },
210217
body: JSON.stringify({

app/api/dsoc/mentees/route.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { NextResponse } from 'next/server';
2+
import connectDB from '@/lib/db';
3+
import { DSOCMentee } from '@/models/DSOCMentee';
4+
5+
// GET DSOC mentees. Admin screens need the full roster, while callers can opt
6+
// into the active + verified subset with ?verifiedOnly=true.
7+
export async function GET(request: Request) {
8+
try {
9+
await connectDB();
10+
11+
const { searchParams } = new URL(request.url);
12+
const verifiedOnly = searchParams.get('verifiedOnly') === 'true';
13+
14+
const query = verifiedOnly
15+
? { isActive: true, isVerified: true }
16+
: {};
17+
18+
const mentees = await DSOCMentee.find(query)
19+
.populate('mentor', '_id name company jobTitle picture')
20+
.select('_id name email university degree picture skills mentor projects applications isActive isVerified createdAt updatedAt')
21+
.sort({ createdAt: -1 })
22+
.lean();
23+
24+
return NextResponse.json({
25+
success: true,
26+
data: mentees,
27+
});
28+
} catch (error) {
29+
console.error('Error fetching DSOC mentees:', error);
30+
return NextResponse.json(
31+
{ success: false, error: 'Failed to fetch mentees' },
32+
{ status: 500 }
33+
);
34+
}
35+
}

app/api/dsoc/mentors/route.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ import { NextResponse } from 'next/server';
22
import connectDB from '@/lib/db';
33
import { DSOCMentor } from '@/models/DSOCMentor';
44

5-
// GET active, verified mentors for project assignment
6-
export async function GET() {
5+
// GET DSOC mentors. Admin screens need the full roster, while callers can opt
6+
// into the assignment-safe subset with ?verifiedOnly=true.
7+
export async function GET(request: Request) {
78
try {
89
await connectDB();
910

10-
const mentors = await DSOCMentor.find({
11-
isActive: true,
12-
isVerified: true,
13-
})
14-
.select('_id name company jobTitle picture expertise')
15-
.sort({ name: 1 })
11+
const { searchParams } = new URL(request.url);
12+
const verifiedOnly = searchParams.get('verifiedOnly') === 'true';
13+
14+
const query = verifiedOnly
15+
? { isActive: true, isVerified: true }
16+
: {};
17+
18+
const mentors = await DSOCMentor.find(query)
19+
.select('_id name email company jobTitle picture expertise isActive isVerified createdAt updatedAt')
20+
.sort({ createdAt: -1 })
1621
.lean();
1722

1823
return NextResponse.json({

0 commit comments

Comments
 (0)