diff --git a/.DS_Store b/.DS_Store index ab0c8f1..45c1a6e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app/admin/dsoc/projects/[id]/edit/page.tsx b/app/admin/dsoc/projects/[id]/edit/page.tsx index 0293b7e..b1b2379 100644 --- a/app/admin/dsoc/projects/[id]/edit/page.tsx +++ b/app/admin/dsoc/projects/[id]/edit/page.tsx @@ -8,17 +8,34 @@ import { Save, Plus, Trash2, - AlertCircle + AlertCircle, + CheckCircle2, + Users } from "lucide-react"; import "../../../../../dsoc/styles.css"; +interface MentorOption { + _id: string; + name: string; + company?: string; + jobTitle?: string; + picture?: string; + expertise?: string[]; +} + export default function EditProjectPage({ params }: { params: Promise<{ id: string }> }) { const resolvedParams = use(params); const router = useRouter(); const [loading, setLoading] = useState(true); + const [mentorLoading, setMentorLoading] = useState(true); + const [mentorError, setMentorError] = useState(''); const [submitting, setSubmitting] = useState(false); + const [imageUploading, setImageUploading] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); + const [availableMentors, setAvailableMentors] = useState([]); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(''); const [formData, setFormData] = useState({ title: '', @@ -35,16 +52,37 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri applicationDeadline: '', startDate: '', endDate: '', + mentors: [] as string[], requirements: [''], learningOutcomes: [''], - season: '2025', - status: 'draft' + season: '2026', + status: 'draft', + featuredImage: '' }); useEffect(() => { fetchProject(); + fetchMentors(); }, [resolvedParams.id]); + const fetchMentors = async () => { + try { + const res = await fetch('/api/dsoc/mentors'); + const data = await res.json(); + + if (data.success) { + setAvailableMentors(data.data || []); + } else { + setMentorError(data.error || 'Failed to load mentors'); + } + } catch (err) { + console.error('Error fetching mentors:', err); + setMentorError('Failed to load mentors'); + } finally { + setMentorLoading(false); + } + }; + const fetchProject = async () => { try { const res = await fetch(`/api/dsoc/projects/${resolvedParams.id}`); @@ -64,13 +102,17 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri technologies: Array.isArray(project.technologies) ? project.technologies.join(', ') : '', tags: Array.isArray(project.tags) ? project.tags.join(', ') : '', maxMentees: project.maxMentees || 3, + mentors: Array.isArray(project.mentors) + ? project.mentors.map((mentor: any) => (typeof mentor === 'string' ? mentor : mentor?._id)).filter(Boolean) + : [], applicationDeadline: project.applicationDeadline ? new Date(project.applicationDeadline).toISOString().split('T')[0] : '', startDate: project.startDate ? new Date(project.startDate).toISOString().split('T')[0] : '', endDate: project.endDate ? new Date(project.endDate).toISOString().split('T')[0] : '', requirements: project.requirements && project.requirements.length > 0 ? project.requirements : [''], learningOutcomes: project.learningOutcomes && project.learningOutcomes.length > 0 ? project.learningOutcomes : [''], season: project.season || '2025', - status: project.status || 'draft' + status: project.status || 'draft', + featuredImage: project.featuredImage || project.imageUrl || '' }); } else { setError(data.error || 'Failed to load project'); @@ -93,6 +135,19 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri setFormData({ ...formData, [field]: updated }); }; + const toggleMentor = (mentorId: string) => { + setFormData((current) => { + const isSelected = current.mentors.includes(mentorId); + + return { + ...current, + mentors: isSelected + ? current.mentors.filter((id) => id !== mentorId) + : [...current.mentors, mentorId] + }; + }); + }; + const addArrayItem = (field: 'requirements' | 'learningOutcomes') => { setFormData({ ...formData, [field]: [...formData[field], ''] }); }; @@ -102,6 +157,39 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri setFormData({ ...formData, [field]: updated }); }; + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] || null; + setImageFile(file); + + if (!file) { + setImagePreview(''); + return; + } + + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + + const uploadImageToCloudinary = async (file: File) => { + const uploadFormData = new FormData(); + uploadFormData.append('file', file); + + const uploadRes = await fetch('/api/upload', { + method: 'POST', + body: uploadFormData, + }); + + if (!uploadRes.ok) { + throw new Error('Image upload failed'); + } + + const uploadData = await uploadRes.json(); + return uploadData.url as string; + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); @@ -109,6 +197,13 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri setSubmitting(true); try { + let featuredImage = formData.featuredImage; + + if (imageFile) { + setImageUploading(true); + featuredImage = await uploadImageToCloudinary(imageFile); + } + const res = await fetch(`/api/dsoc/projects/${resolvedParams.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, @@ -124,13 +219,16 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri technologies: formData.technologies.split(',').map(s => s.trim()).filter(Boolean), tags: formData.tags.split(',').map(s => s.trim()).filter(Boolean), maxMentees: parseInt(formData.maxMentees as unknown as string), + mentors: formData.mentors, applicationDeadline: formData.applicationDeadline, startDate: formData.startDate, endDate: formData.endDate, requirements: formData.requirements.filter(Boolean), learningOutcomes: formData.learningOutcomes.filter(Boolean), season: formData.season, - status: formData.status + status: formData.status, + featuredImage, + imageUrl: featuredImage }) }); @@ -146,8 +244,9 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri } } catch (err) { console.error('Error updating project:', err); - setError('Something went wrong. Please try again.'); + setError(err instanceof Error ? err.message : 'Something went wrong. Please try again.'); } finally { + setImageUploading(false); setSubmitting(false); } }; @@ -246,6 +345,26 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri placeholder="Detailed description (shown on project page)" /> + +
+ + + {(imagePreview || formData.featuredImage) && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Project preview +
+ )} +
{/* Links */} @@ -368,6 +487,90 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri + {/* Mentors */} +
+

+ + Select Mentors +

+ + {mentorLoading ? ( +
+ Loading mentors... +
+ ) : mentorError ? ( +
+ {mentorError} +
+ ) : availableMentors.length === 0 ? ( +
+ No mentors found. Create and verify a mentor first. +
+ ) : ( +
+ {availableMentors.map((mentor) => { + const isSelected = formData.mentors.includes(mentor._id); + + return ( + + ); + })} +
+ )} + + {formData.mentors.length > 0 && ( +

+ {formData.mentors.length} mentor{formData.mentors.length > 1 ? 's' : ''} selected +

+ )} +
+ {/* Timeline */}

Timeline

@@ -500,7 +703,7 @@ export default function EditProjectPage({ params }: { params: Promise<{ id: stri className="flex items-center gap-2 px-6 py-3 bg-[var(--dsoc-dark)] text-white font-bold border-4 border-[var(--dsoc-dark)] hover:translate-y-1 transition-transform disabled:opacity-50" > - {submitting ? 'Saving...' : 'Save Changes'} + {imageUploading ? 'Uploading Image...' : submitting ? 'Saving...' : 'Save Changes'} ([]); + const [imageFile, setImageFile] = useState(null); + const [imagePreview, setImagePreview] = useState(''); + const [formData, setFormData] = useState({ title: '', description: '', @@ -31,16 +48,51 @@ export default function NewProjectPage() { applicationDeadline: '', startDate: '', endDate: '', + mentors: [] as string[], requirements: [''], learningOutcomes: [''], - season: '2025' + season: '2026', + featuredImage: '', }); - const handleChange = (e: React.ChangeEvent) => { - setFormData({ ...formData, [e.target.name]: e.target.value }); + useEffect(() => { + fetchMentors(); + }, []); + + const fetchMentors = async () => { + try { + const res = await fetch('/api/dsoc/mentors'); + const data = await res.json(); + + if (data.success) { + setAvailableMentors(data.data || []); + } else { + setMentorError(data.error || 'Failed to load mentors'); + } + } catch (err) { + console.error('Error fetching mentors:', err); + setMentorError('Failed to load mentors'); + } finally { + setMentorLoading(false); + } + }; + + const handleChange = ( + e: React.ChangeEvent, + ) => { + const { name, value } = e.target; + + setFormData((current) => ({ + ...current, + [name]: name === 'maxMentees' ? Number(value) : value, + })); }; - const handleArrayChange = (field: 'requirements' | 'learningOutcomes', index: number, value: string) => { + const handleArrayChange = ( + field: 'requirements' | 'learningOutcomes', + index: number, + value: string, + ) => { const updated = [...formData[field]]; updated[index] = value; setFormData({ ...formData, [field]: updated }); @@ -55,23 +107,83 @@ export default function NewProjectPage() { setFormData({ ...formData, [field]: updated }); }; + const toggleMentor = (mentorId: string) => { + setFormData((current) => { + const isSelected = current.mentors.includes(mentorId); + + return { + ...current, + mentors: isSelected + ? current.mentors.filter((id) => id !== mentorId) + : [...current.mentors, mentorId], + }; + }); + }; + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] || null; + setImageFile(file); + + if (!file) { + setImagePreview(''); + return; + } + + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + }; + + const uploadImageToCloudinary = async (file: File) => { + const uploadFormData = new FormData(); + uploadFormData.append('file', file); + + const uploadRes = await fetch('/api/upload', { + method: 'POST', + body: uploadFormData, + }); + + if (!uploadRes.ok) { + throw new Error('Image upload failed'); + } + + const uploadData = await uploadRes.json(); + return uploadData.url as string; + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setLoading(true); try { + let featuredImage = formData.featuredImage; + + if (imageFile) { + setImageUploading(true); + featuredImage = await uploadImageToCloudinary(imageFile); + } + + if (!featuredImage) { + throw new Error('Project image is required'); + } + const res = await fetch('/api/dsoc/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, - technologies: formData.technologies.split(',').map(s => s.trim()).filter(Boolean), - tags: formData.tags.split(',').map(s => s.trim()).filter(Boolean), + technologies: formData.technologies.split(',').map((s) => s.trim()).filter(Boolean), + tags: formData.tags.split(',').map((s) => s.trim()).filter(Boolean), + mentors: formData.mentors, requirements: formData.requirements.filter(Boolean), learningOutcomes: formData.learningOutcomes.filter(Boolean), - status: 'draft' - }) + featuredImage, + imageUrl: featuredImage, + status: 'draft', + }), }); const data = await res.json(); @@ -83,8 +195,9 @@ export default function NewProjectPage() { } } catch (err) { console.error('Error creating project:', err); - setError('Something went wrong. Please try again.'); + setError(err instanceof Error ? err.message : 'Something went wrong. Please try again.'); } finally { + setImageUploading(false); setLoading(false); } }; @@ -93,7 +206,7 @@ export default function NewProjectPage() {
- @@ -114,7 +227,7 @@ export default function NewProjectPage() { {/* Basic Info */}

Basic Information

- +
+ +
+ + + {(imagePreview || formData.featuredImage) && ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Project preview +
+ )} +
+
+ + {/* Mentors */} +
+

+ + Select Mentors +

+ + {mentorLoading ? ( +
+ Loading mentors... +
+ ) : mentorError ? ( +
+ {mentorError} +
+ ) : availableMentors.length === 0 ? ( +
+ No mentors found. Create and verify a mentor first. +
+ ) : ( +
+ {availableMentors.map((mentor) => { + const isSelected = formData.mentors.includes(mentor._id); + + return ( + + ); + })} +
+ )} + + {formData.mentors.length > 0 && ( +

+ {formData.mentors.length} mentor{formData.mentors.length > 1 ? 's' : ''} selected +

+ )}
{/* Links */}

Links

- +
@@ -201,7 +419,7 @@ export default function NewProjectPage() { {/* Project Details */}

Project Details

- +
@@ -274,7 +492,7 @@ export default function NewProjectPage() { {/* Timeline */}

Timeline

- +
@@ -328,7 +546,7 @@ export default function NewProjectPage() { {/* Requirements */}

Requirements

- + {formData.requirements.map((req, index) => (
))} +
@@ -362,7 +581,7 @@ export default function NewProjectPage() { {/* Learning Outcomes */}

Learning Outcomes

- + {formData.learningOutcomes.map((outcome, index) => (
))} +
@@ -396,10 +616,10 @@ export default function NewProjectPage() {
@@ -407,4 +627,4 @@ export default function NewProjectPage() {
); -} +} \ No newline at end of file diff --git a/app/api/dsoc/mentors/route.ts b/app/api/dsoc/mentors/route.ts new file mode 100644 index 0000000..82430d6 --- /dev/null +++ b/app/api/dsoc/mentors/route.ts @@ -0,0 +1,29 @@ +import { NextResponse } from 'next/server'; +import connectDB from '@/lib/db'; +import { DSOCMentor } from '@/models/DSOCMentor'; + +// GET active, verified mentors for project assignment +export async function GET() { + try { + await connectDB(); + + const mentors = await DSOCMentor.find({ + isActive: true, + isVerified: true, + }) + .select('_id name company jobTitle picture expertise') + .sort({ name: 1 }) + .lean(); + + return NextResponse.json({ + success: true, + data: mentors, + }); + } catch (error) { + console.error('Error fetching DSOC mentors:', error); + return NextResponse.json( + { success: false, error: 'Failed to fetch mentors' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/dsoc/projects/[id]/route.ts b/app/api/dsoc/projects/[id]/route.ts index eb84877..6b04aec 100644 --- a/app/api/dsoc/projects/[id]/route.ts +++ b/app/api/dsoc/projects/[id]/route.ts @@ -47,10 +47,16 @@ export async function PUT( // TODO: Add admin/mentor authentication check const body = await request.json(); + + // Support both legacy imageUrl and schema-native featuredImage. + const normalizedBody = { + ...body, + featuredImage: body.featuredImage || body.imageUrl, + }; const project = await DSOCProject.findByIdAndUpdate( id, - { $set: body }, + { $set: normalizedBody }, { new: true, runValidators: true } ); diff --git a/app/api/dsoc/projects/route.ts b/app/api/dsoc/projects/route.ts index 1d1a27d..646900a 100644 --- a/app/api/dsoc/projects/route.ts +++ b/app/api/dsoc/projects/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import connectDB from '@/lib/db'; +import '@/models/DSOCMentor'; import { DSOCProject } from '@/models/DSOCProject'; // GET all projects with filtering @@ -70,8 +71,14 @@ export async function POST(request: NextRequest) { // TODO: Add admin authentication check const body = await request.json(); - - const project = new DSOCProject(body); + + // Support both legacy imageUrl and schema-native featuredImage. + const normalizedBody = { + ...body, + featuredImage: body.featuredImage || body.imageUrl, + }; + + const project = new DSOCProject(normalizedBody); await project.save(); return NextResponse.json({ diff --git a/app/dsoc/apply/[id]/page.tsx b/app/dsoc/apply/[id]/page.tsx index 56ae6f3..5d55463 100644 --- a/app/dsoc/apply/[id]/page.tsx +++ b/app/dsoc/apply/[id]/page.tsx @@ -246,7 +246,7 @@ export default function ApplyPage({ params }: { params: Promise<{ id: string }>
{/* Project Info Card */}
-
+
diff --git a/app/dsoc/page.tsx b/app/dsoc/page.tsx index b4f126e..baf66f0 100644 --- a/app/dsoc/page.tsx +++ b/app/dsoc/page.tsx @@ -52,7 +52,7 @@ const SAMPLE_PROJECTS: Project[] = [ title: 'VoiceyBill', description: 'VoiceyBill is an open source AI-powered finance tracker that lets people log income and expenses by voice, receipt scan, or manual entry.\nIt uses Gemini and UpliftAI to classify transactions, supports CSV imports and recurring entries, and keeps the experience fast for multilingual users.\nThe product is designed around a responsive, multi-theme UI with analytics and monthly reports.', organization: 'Dev Weekends', - difficulty: 'advanced', + difficulty: 'intermediate', technologies: ['MongoDB', 'Express.js', 'React', 'Node.js', 'TypeScript', 'Google Gemini AI', 'UpliftAI', 'Cloudinary', 'Redux Toolkit', 'RTK Query'], status: 'open', duration: '10-12 weeks', @@ -64,7 +64,7 @@ const SAMPLE_PROJECTS: Project[] = [ title: 'Pathment', description: 'Pathment is a SaaS mentorship platform for organizations that want structured, mentor-guided employee training.\nIt connects mentees with real mentors, uses task-based workflows and progress tracking, and adds gamification to boost engagement and measurable outcomes.', organization: 'Dev Weekends', - difficulty: 'advanced', + difficulty: 'intermediate', technologies: ['Next.js', 'TypeScript', 'Node.js', 'Express', 'PostgreSQL', 'Sequelize', 'Socket.io', 'Tailwind CSS', 'Tiptap', 'Cloudinary'], status: 'open', duration: '10-12 weeks', @@ -279,14 +279,14 @@ export default function DSOCPage() { Apply as Mentee - + {/* Become a Mentor - + */}
{/* Quick Links */} -
+ {/* +
*/}
diff --git a/app/dsoc/projects/[id]/page.tsx b/app/dsoc/projects/[id]/page.tsx index aa0162e..4c89ed9 100644 --- a/app/dsoc/projects/[id]/page.tsx +++ b/app/dsoc/projects/[id]/page.tsx @@ -72,7 +72,7 @@ The goal is to help investors in Pakistan make better decisions and keep portfol organization: 'Dev Weekends', repositoryUrl: 'https://github.com/Wajahat43/psxworth', websiteUrl: 'https://psxworth.com', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS', 'shadcn/ui', 'Radix UI', 'Clerk', 'Drizzle ORM', 'PostgreSQL', 'TanStack Query', 'TanStack Table', 'PostHog', 'Upstash Redis', 'AI SDK', 'ApexCharts', 'Recharts', 'React Hook Form', 'Zod'], tags: ['fintech', 'portfolio-tracking', 'dashboard', 'open-source', 'psx'], @@ -128,7 +128,7 @@ The project is built on the MERN stack with TypeScript and integrates Google Gem organization: 'Dev Weekends', repositoryUrl: 'https://github.com/voiceyBill/voiceyBill-web', websiteUrl: 'https://www.voiceybill.com/', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['MongoDB', 'Express.js', 'React', 'Node.js', 'TypeScript', 'Google Gemini AI', 'UpliftAI', 'Cloudinary', 'Redux Toolkit', 'RTK Query'], tags: ['full-stack', 'ai', 'finance', 'voice-input', 'open-source'], @@ -180,7 +180,7 @@ The project is built on the MERN stack with TypeScript and integrates Google Gem organization: 'Dev Weekends', repositoryUrl: 'https://github.com/Sheryar-Ahmed/pathment.git', websiteUrl: 'https://pathment.me/', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['Next.js', 'TypeScript', 'Node.js', 'Express', 'PostgreSQL', 'Sequelize', 'Socket.io', 'Tailwind CSS', 'Tiptap', 'Cloudinary'], tags: ['full-stack', 'real-time', 'developer-tools', 'collaboration'], @@ -235,6 +235,55 @@ The project is built on the MERN stack with TypeScript and integrates Google Gem ], season: 'DSOC 2026' }, + 'goalslot': { + _id: 'goalslot', + title: 'GoalSlot', + description: "GoalSlot connects your goals to your calendar and tracks every hour you work on them. Finally know if you're actually making progress.", + longDescription: `GoalSlot helps users tie their goals directly to calendar events and measure the time spent working on them. Contributors will work on syncing calendar events, building time-tracking UX, improving analytics, and making the onboarding experience delightful. Project images and demo videos are available in the provided Drive links. + +Contributions range from frontend Next.js improvements to backend NestJS API work, integrations, and analytics features. This is a real-user product with an existing live site, so contributors should be cautious when touching production integrations and focus on well-scoped, testable changes.`, + organization: 'Dev Weekends', + repositoryUrl: 'https://github.com/ZeeshanAdilButt/goal-slot-web', + websiteUrl: 'https://www.goalslot.io/', + difficulty: 'intermediate', + duration: '8-12 weeks', + technologies: ['Next.js', 'NestJS', 'TypeScript'], + tags: ['productivity', 'calendar', 'time-tracking', 'open-source'], + mentors: [ + { + _id: 'mentor-psx-1', + name: 'Wajahat Islam Gul', + email: 'amina@devweekends.org', + company: 'Nector Social', + jobTitle: 'Software Engineer', + linkedin: 'https://www.linkedin.com/in/wajahatx1/', + github: 'https://github.com/Wajahat43' + } + ], + selectedMentees: [], + maxMentees: 3, + status: 'open', + applicationDeadline: '2026-06-30', + startDate: '2026-07-01', + endDate: '2026-09-30', + requirements: [ + 'Familiarity with React and Next.js', + 'Comfort with TypeScript', + 'Willingness to work with calendar integrations and APIs', + 'Ability to commit 8-12 hours per week' + ], + learningOutcomes: [ + 'Implement reliable calendar syncing and time-tracking features', + 'Build end-to-end features across Next.js frontend and NestJS backend', + 'Design analytics and reporting for goal progress' + ], + milestones: [ + { title: 'Onboarding & Setup', description: 'Get the monorepo and local dev environment working', dueDate: '2026-07-07', completed: false }, + { title: 'Calendar Sync', description: 'Implement calendar integration and event mapping', dueDate: '2026-08-01', completed: false }, + { title: 'Time Tracking UI', description: 'Add time tracking flows and analytics dashboards', dueDate: '2026-08-31', completed: false } + ], + season: 'DSOC 2026' + }, }; export default function ProjectDetailPage({ params }: { params: Promise<{ id: string }> }) { diff --git a/app/dsoc/projects/page.tsx b/app/dsoc/projects/page.tsx index 780da67..96ec7b9 100644 --- a/app/dsoc/projects/page.tsx +++ b/app/dsoc/projects/page.tsx @@ -40,6 +40,7 @@ interface Project { jobTitle?: string; }[]; applicationDeadline: string; + featuredImage?: string; imageUrl?: string; wikiUrl?: string; } @@ -53,7 +54,7 @@ const SAMPLE_PROJECTS: Project[] = [ organization: 'Dev Weekends', repositoryUrl: 'https://github.com/Wajahat43/psxworth', websiteUrl: 'https://psxworth.com', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['Next.js', 'React', 'TypeScript', 'Tailwind CSS', 'shadcn/ui', 'Radix UI', 'Clerk', 'Drizzle ORM', 'PostgreSQL', 'TanStack Query', 'TanStack Table', 'PostHog', 'Upstash Redis', 'AI SDK', 'ApexCharts', 'Recharts', 'React Hook Form', 'Zod', 'NestJS'], tags: ['full-stack', 'fintech', 'portfolio-tracking', 'dashboard', 'open-source'], @@ -79,7 +80,7 @@ const SAMPLE_PROJECTS: Project[] = [ organization: 'Dev Weekends', repositoryUrl: 'https://github.com/voiceyBill/voiceyBill-web', websiteUrl: 'https://www.voiceybill.com/', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['MongoDB', 'Express.js', 'React', 'Node.js', 'TypeScript', 'Google Gemini AI', 'UpliftAI', 'Cloudinary', 'Redux Toolkit', 'RTK Query'], tags: ['full-stack', 'ai', 'finance', 'voice-input', 'open-source'], @@ -104,7 +105,7 @@ const SAMPLE_PROJECTS: Project[] = [ organization: 'Dev Weekends', repositoryUrl: 'https://github.com/Sheryar-Ahmed/pathment.git', websiteUrl: 'https://pathment.me/', - difficulty: 'advanced', + difficulty: 'intermediate', duration: '10-12 weeks', technologies: ['Next.js', 'TypeScript', 'Node.js', 'Express', 'PostgreSQL', 'Sequelize', 'Socket.io', 'Tailwind CSS', 'Tiptap', 'Cloudinary'], tags: ['full-stack', 'real-time', 'developer-tools', 'collaboration'], @@ -122,6 +123,32 @@ const SAMPLE_PROJECTS: Project[] = [ applicationDeadline: '2026-05-25', imageUrl: '/images/dsoc/pathment.png', }, + { + _id: 'goalslot', + title: 'GoalSlot', + description: "GoalSlot connects your goals to your calendar and tracks every hour you work on them. Finally know if you're actually making progress.", + organization: 'Dev Weekends', + repositoryUrl: 'https://github.com/ZeeshanAdilButt/goal-slot-web', + websiteUrl: 'https://www.goalslot.io/', + difficulty: 'intermediate', + duration: '8-12 weeks', + technologies: ['Next.js', 'NestJS', 'TypeScript'], + tags: ['productivity', 'calendar', 'time-tracking', 'open-source'], + status: 'open', + maxMentees: 3, + selectedMentees: [], + mentors: [ + { + _id: 'mentor-psx-1', + name: 'Wajahat Ali', + picture: undefined, + company: 'Nector Social', + jobTitle: 'Software Engineer' + } + ], + applicationDeadline: '2026-06-30', + imageUrl: '/images/dsoc/goalslot.png', + }, ]; export default function ProjectsPage() { @@ -355,14 +382,17 @@ export default function ProjectsPage() {
- {projects.map((project) => ( + {projects.map((project) => { + const projectImage = project.featuredImage || project.imageUrl; + + return (
{/* Project Image */} - {project.imageUrl && ( + {projectImage && (
{project.title} @@ -379,7 +409,7 @@ export default function ProjectsPage() { {project.organization} - {!project.imageUrl && ( + {!projectImage && (
{project.status} @@ -482,7 +512,8 @@ export default function ProjectsPage() {
- ))} + ); + })}
)}