Skip to content

Commit d55d946

Browse files
Merge pull request #42 from devweekends/dsoc-proposal-portal
feat(dsoc): complete proposal portal end-to-end
2 parents 02ecb92 + 351d3e2 commit d55d946

3 files changed

Lines changed: 117 additions & 34 deletions

File tree

app/api/dsoc/applications/route.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,14 @@ export async function GET(request: NextRequest) {
5353
const mentorOnly = searchParams.get('mentor') === 'true';
5454
const menteeOnly = searchParams.get('my') === 'true';
5555
const menteeId = menteeOnly ? await getMenteeFromToken(request) : null;
56-
56+
57+
if (menteeOnly && !menteeId) {
58+
return NextResponse.json(
59+
{ success: false, error: 'Unauthorized' },
60+
{ status: 401 }
61+
);
62+
}
63+
5764
const query: any = {};
5865

5966
if (mentorOnly) {
@@ -121,6 +128,14 @@ export async function POST(request: NextRequest) {
121128

122129
const body = await request.json();
123130
const { projectId, ...applicationData } = body;
131+
132+
// Drop empty-string optional fields so mongoose doesn't try to cast them
133+
// (empty string into a Date field throws CastError).
134+
Object.keys(applicationData).forEach((key) => {
135+
if (applicationData[key] === '' || applicationData[key] === null) {
136+
delete applicationData[key];
137+
}
138+
});
124139

125140
// Check if project exists and is open
126141
const project = await DSOCProject.findById(projectId);

app/dsoc/mentee/dashboard/page.tsx

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ interface Application {
2020
_id: string;
2121
status: string;
2222
createdAt: string;
23+
mentorNotes?: string;
24+
score?: number;
25+
reviewedAt?: string;
2326
project: {
2427
_id: string;
2528
title: string;
@@ -201,32 +204,63 @@ export default function MenteeDashboard() {
201204
</Link>
202205
</div>
203206
) : (
204-
applications.map((app) => (
205-
<div key={app._id} className="neo-brutal-card p-6">
206-
<div className="flex items-start justify-between gap-4">
207-
<div className="flex-1">
208-
<div className="flex items-center gap-3 mb-2">
209-
{getStatusIcon(app.status)}
210-
<span className={`neo-brutal-badge text-xs text-white ${getStatusColor(app.status)}`}>
211-
{app.status.replace('-', ' ')}
212-
</span>
207+
applications.map((app) => {
208+
const hasFeedback =
209+
(app.status === 'accepted' ||
210+
app.status === 'rejected' ||
211+
app.status === 'waitlisted' ||
212+
app.status === 'under-review') &&
213+
(app.mentorNotes || typeof app.score === 'number');
214+
215+
return (
216+
<div key={app._id} className="neo-brutal-card p-6">
217+
<div className="flex items-start justify-between gap-4">
218+
<div className="flex-1">
219+
<div className="flex items-center gap-3 mb-2">
220+
{getStatusIcon(app.status)}
221+
<span className={`neo-brutal-badge text-xs text-white ${getStatusColor(app.status)}`}>
222+
{app.status.replace('-', ' ')}
223+
</span>
224+
{typeof app.score === 'number' && (
225+
<span className="neo-brutal-badge text-xs bg-[var(--dsoc-purple)] text-white">
226+
Score: {app.score}/100
227+
</span>
228+
)}
229+
</div>
230+
<h3 className="text-xl font-bold">{app.project.title}</h3>
231+
<p className="text-muted-foreground">{app.project.organization}</p>
232+
<p className="text-sm text-muted-foreground mt-2">
233+
Applied on {new Date(app.createdAt).toLocaleDateString()}
234+
{app.reviewedAt && (
235+
<>
236+
{' • Reviewed '}
237+
{new Date(app.reviewedAt).toLocaleDateString()}
238+
</>
239+
)}
240+
</p>
213241
</div>
214-
<h3 className="text-xl font-bold">{app.project.title}</h3>
215-
<p className="text-muted-foreground">{app.project.organization}</p>
216-
<p className="text-sm text-muted-foreground mt-2">
217-
Applied on {new Date(app.createdAt).toLocaleDateString()}
218-
</p>
242+
<Link
243+
href={`/dsoc/projects/${app.project._id}`}
244+
className="neo-brutal-btn neo-brutal-btn-secondary py-2 px-4 text-sm"
245+
>
246+
<ExternalLink className="w-4 h-4 mr-2" />
247+
View Project
248+
</Link>
219249
</div>
220-
<Link
221-
href={`/dsoc/projects/${app.project._id}`}
222-
className="neo-brutal-btn neo-brutal-btn-secondary py-2 px-4 text-sm"
223-
>
224-
<ExternalLink className="w-4 h-4 mr-2" />
225-
View Project
226-
</Link>
250+
251+
{hasFeedback && app.mentorNotes && (
252+
<div className="mt-4 p-4 bg-[var(--dsoc-light)] border-4 border-[var(--dsoc-dark)]">
253+
<div className="text-xs font-black uppercase tracking-wider text-muted-foreground mb-2">
254+
Mentor Feedback
255+
</div>
256+
<p className="text-sm whitespace-pre-wrap leading-relaxed">
257+
{app.mentorNotes}
258+
</p>
259+
</div>
260+
)}
227261
</div>
228-
</div>
229-
))
262+
);
263+
})
230264
)}
231265
</div>
232266
)}

models/DSOCApplication.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@ export interface IDSOCApplication extends Document {
66
discordUsername: string;
77
proposal: string;
88
coverLetter?: string;
9-
relevantExperience: string;
109
whyThisProject: string;
11-
availability: string;
12-
expectedLearnings: string;
10+
motivation?: string;
11+
relevantExperience: string;
12+
technicalSkills?: string;
13+
githubProfile?: string;
1314
portfolioLinks: string[];
1415
previousContributions?: string;
16+
timeline?: string;
17+
expectedLearnings: string;
18+
challenges?: string;
19+
availability: string;
20+
timezone?: string;
21+
startDate?: Date;
1522
status: 'pending' | 'under-review' | 'accepted' | 'rejected' | 'waitlisted' | 'withdrawn';
1623
mentorNotes?: string;
1724
adminNotes?: string;
@@ -48,34 +55,61 @@ const DSOCApplicationSchema = new Schema<IDSOCApplication>(
4855
type: String,
4956
trim: true,
5057
},
58+
whyThisProject: {
59+
type: String,
60+
required: [true, 'Why this project answer is required'],
61+
trim: true,
62+
},
63+
motivation: {
64+
type: String,
65+
trim: true,
66+
},
5167
relevantExperience: {
5268
type: String,
5369
required: [true, 'Relevant experience is required'],
5470
trim: true,
5571
},
56-
whyThisProject: {
72+
technicalSkills: {
5773
type: String,
58-
required: [true, 'Why this project answer is required'],
5974
trim: true,
6075
},
61-
availability: {
76+
githubProfile: {
77+
type: String,
78+
trim: true,
79+
},
80+
portfolioLinks: [{
81+
type: String,
82+
trim: true,
83+
}],
84+
previousContributions: {
85+
type: String,
86+
trim: true,
87+
},
88+
timeline: {
6289
type: String,
63-
required: [true, 'Availability is required'],
6490
trim: true,
6591
},
6692
expectedLearnings: {
6793
type: String,
6894
required: [true, 'Expected learnings is required'],
6995
trim: true,
7096
},
71-
portfolioLinks: [{
97+
challenges: {
7298
type: String,
7399
trim: true,
74-
}],
75-
previousContributions: {
100+
},
101+
availability: {
102+
type: String,
103+
required: [true, 'Availability is required'],
104+
trim: true,
105+
},
106+
timezone: {
76107
type: String,
77108
trim: true,
78109
},
110+
startDate: {
111+
type: Date,
112+
},
79113
status: {
80114
type: String,
81115
enum: ['pending', 'under-review', 'accepted', 'rejected', 'waitlisted', 'withdrawn'],

0 commit comments

Comments
 (0)