diff --git a/app/admin/dsoc/projects/[id]/edit/page.tsx b/app/admin/dsoc/projects/[id]/edit/page.tsx
index d993d04..4c5da43 100644
--- a/app/admin/dsoc/projects/[id]/edit/page.tsx
+++ b/app/admin/dsoc/projects/[id]/edit/page.tsx
@@ -50,7 +50,7 @@ export default function EditProjectPage() {
description: '',
longDescription: '',
organization: '',
- repositoryUrl: '',
+ repositoryUrls: [''],
websiteUrl: '',
timelineUrl: '',
difficulty: 'intermediate',
@@ -110,7 +110,9 @@ export default function EditProjectPage() {
description: project.description || '',
longDescription: project.longDescription || '',
organization: project.organization || '',
- repositoryUrl: project.repositoryUrl || '',
+ repositoryUrls: Array.isArray(project.repositoryUrls) && project.repositoryUrls.length > 0
+ ? project.repositoryUrls
+ : (project.repositoryUrl ? [project.repositoryUrl] : ['']),
websiteUrl: project.websiteUrl || '',
timelineUrl: project.timelineUrl || '',
difficulty: project.difficulty || 'intermediate',
@@ -146,7 +148,7 @@ export default function EditProjectPage() {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
- const handleArrayChange = (field: 'requirements' | 'learningOutcomes', index: number, value: string) => {
+ const handleArrayChange = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number, value: string) => {
const updated = [...formData[field]];
updated[index] = value;
setFormData({ ...formData, [field]: updated });
@@ -165,11 +167,11 @@ export default function EditProjectPage() {
});
};
- const addArrayItem = (field: 'requirements' | 'learningOutcomes') => {
+ const addArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls') => {
setFormData({ ...formData, [field]: [...formData[field], ''] });
};
- const removeArrayItem = (field: 'requirements' | 'learningOutcomes', index: number) => {
+ const removeArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number) => {
const updated = formData[field].filter((_, i) => i !== index);
setFormData({ ...formData, [field]: updated });
};
@@ -272,7 +274,7 @@ export default function EditProjectPage() {
description: formData.description,
longDescription: formData.longDescription,
organization: formData.organization,
- repositoryUrl: formData.repositoryUrl,
+ repositoryUrls: formData.repositoryUrls.filter(Boolean),
websiteUrl: formData.websiteUrl,
timelineUrl: formData.timelineUrl,
difficulty: formData.difficulty,
@@ -477,32 +479,52 @@ export default function EditProjectPage() {
{/* Links */}
-
Links
-
-
-
- Repository URL *
-
-
-
- Website URL
-
-
+
Links & Repositories
+
+
+
Repository URLs * (At least one is required)
+ {formData.repositoryUrls.map((repoUrl, index) => (
+
+ handleArrayChange('repositoryUrls', index, e.target.value)}
+ required={index === 0}
+ className="neo-brutal-input flex-1"
+ placeholder="https://github.com/org/repo"
+ />
+ {formData.repositoryUrls.length > 1 && (
+ removeArrayItem('repositoryUrls', index)}
+ className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
+ >
+
+
+ )}
+
+ ))}
+
+
addArrayItem('repositoryUrls')}
+ className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--dsoc-success)] text-white font-bold border-4 border-[var(--dsoc-dark)] hover:translate-x-1 transition-transform text-sm"
+ >
+
+ Add Repository URL
+
+
+
+
+ Website URL
+
diff --git a/app/admin/dsoc/projects/new/page.tsx b/app/admin/dsoc/projects/new/page.tsx
index cea2e5e..81b7892 100644
--- a/app/admin/dsoc/projects/new/page.tsx
+++ b/app/admin/dsoc/projects/new/page.tsx
@@ -44,7 +44,7 @@ export default function NewProjectPage() {
description: '',
longDescription: '',
organization: '',
- repositoryUrl: '',
+ repositoryUrls: [''],
websiteUrl: '',
timelineUrl: '',
difficulty: 'intermediate',
@@ -97,7 +97,7 @@ export default function NewProjectPage() {
};
const handleArrayChange = (
- field: 'requirements' | 'learningOutcomes',
+ field: 'requirements' | 'learningOutcomes' | 'repositoryUrls',
index: number,
value: string,
) => {
@@ -106,11 +106,11 @@ export default function NewProjectPage() {
setFormData({ ...formData, [field]: updated });
};
- const addArrayItem = (field: 'requirements' | 'learningOutcomes') => {
+ const addArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls') => {
setFormData({ ...formData, [field]: [...formData[field], ''] });
};
- const removeArrayItem = (field: 'requirements' | 'learningOutcomes', index: number) => {
+ const removeArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number) => {
const updated = formData[field].filter((_, i) => i !== index);
setFormData({ ...formData, [field]: updated });
};
@@ -226,6 +226,7 @@ export default function NewProjectPage() {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...formData,
+ repositoryUrls: formData.repositoryUrls.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,
@@ -485,32 +486,52 @@ export default function NewProjectPage() {
{/* Links */}
-
Links
+
Links & Repositories
+
+
+
Repository URLs * (At least one is required)
+ {formData.repositoryUrls.map((repoUrl, index) => (
+
+ handleArrayChange('repositoryUrls', index, e.target.value)}
+ required={index === 0}
+ className="neo-brutal-input flex-1"
+ placeholder="https://github.com/org/repo"
+ />
+ {formData.repositoryUrls.length > 1 && (
+ removeArrayItem('repositoryUrls', index)}
+ className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
+ >
+
+
+ )}
+
+ ))}
+
+
addArrayItem('repositoryUrls')}
+ className="inline-flex items-center gap-2 px-4 py-2 bg-[var(--dsoc-success)] text-white font-bold border-4 border-[var(--dsoc-dark)] hover:translate-x-1 transition-transform text-sm"
+ >
+
+ Add Repository URL
+
+
-
diff --git a/app/dsoc/page.tsx b/app/dsoc/page.tsx
index 8c5db0e..cf7f6fb 100644
--- a/app/dsoc/page.tsx
+++ b/app/dsoc/page.tsx
@@ -43,6 +43,7 @@ interface Project {
featuredImage?: string;
imageUrl?: string;
repositoryUrl?: string;
+ repositoryUrls?: string[];
wikiUrl?: string;
}
@@ -574,10 +575,15 @@ export default function DSOCPage() {
{project.duration}
- {project.repositoryUrl && (
+ {(project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls.length > 0)) && (
{ e.preventDefault(); e.stopPropagation(); window.open(project.repositoryUrl, '_blank'); }}
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const targetUrl = project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls[0]);
+ if (targetUrl) window.open(targetUrl, '_blank');
+ }}
className="inline-flex items-center gap-1 text-sm font-bold text-[var(--dsoc-dark)] hover:text-[var(--dsoc-primary)] transition-colors"
>
diff --git a/app/dsoc/projects/[id]/page.tsx b/app/dsoc/projects/[id]/page.tsx
index 5da76bc..0706a97 100644
--- a/app/dsoc/projects/[id]/page.tsx
+++ b/app/dsoc/projects/[id]/page.tsx
@@ -16,8 +16,9 @@ import {
BookOpen,
Target,
Code2,
- ImageIcon,
MessageCircle,
+ Star,
+ ImageIcon,
X
} from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
@@ -48,6 +49,7 @@ interface Project {
longDescription?: string;
organization: string;
repositoryUrl: string;
+ repositoryUrls?: string[];
websiteUrl?: string;
timelineUrl?: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
@@ -307,6 +309,25 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
const [isMentee, setIsMentee] = useState(null);
const [lightboxImage, setLightboxImage] = useState(null);
+ const getRepoLabel = (url: string) => {
+ try {
+ const path = new URL(url).pathname;
+ const parts = path.split('/').filter(Boolean);
+ if (parts.length >= 2) {
+ return parts.slice(-2).join('/');
+ }
+ return 'Repository';
+ } catch {
+ return 'Repository';
+ }
+ };
+
+ const repoUrls = project
+ ? Array.isArray(project.repositoryUrls) && project.repositoryUrls.length > 0
+ ? project.repositoryUrls
+ : (project.repositoryUrl ? [project.repositoryUrl] : [])
+ : [];
+
useEffect(() => {
fetchProject();
checkMenteeSession();
@@ -497,16 +518,19 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
-
-
-
- Repository
-
+
+ {/* Star Card */}
+
+
+
+
Support This Project
+
+
+ Love this project? Show your support by starring the repository on GitHub! It helps increase visibility, motivates the mentors, and grows our open-source community. ⭐
+
+
+
+
{project.timelineUrl && (
@@ -849,7 +898,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
{/* Discord Card */}
-
+
Have Questions?
@@ -863,7 +912,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
>
Join Discord
-
+
{/* Tags */}
diff --git a/app/dsoc/projects/page.tsx b/app/dsoc/projects/page.tsx
index a6e10aa..ce0b53d 100644
--- a/app/dsoc/projects/page.tsx
+++ b/app/dsoc/projects/page.tsx
@@ -24,6 +24,7 @@ interface Project {
description: string;
organization: string;
repositoryUrl: string;
+ repositoryUrls?: string[];
websiteUrl?: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
duration: string;
@@ -491,10 +492,15 @@ export default function ProjectsPage() {
{/* GitHub and Wiki Links */}
- {project.repositoryUrl && (
+ {(project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls.length > 0)) && (
{ e.preventDefault(); e.stopPropagation(); window.open(project.repositoryUrl, '_blank'); }}
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const targetUrl = project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls[0]);
+ if (targetUrl) window.open(targetUrl, '_blank');
+ }}
className="inline-flex items-center gap-1.5 text-sm font-bold text-[var(--dsoc-dark)] hover:text-[var(--dsoc-primary)] transition-colors"
>
diff --git a/models/DSOCProject.ts b/models/DSOCProject.ts
index 466f9d1..7e7b0b1 100644
--- a/models/DSOCProject.ts
+++ b/models/DSOCProject.ts
@@ -5,7 +5,7 @@ export interface IDSOCProject extends Document {
description: string;
longDescription?: string;
organization: string;
- repositoryUrl: string;
+ repositoryUrls: string[];
websiteUrl?: string;
timelineUrl?: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
@@ -58,11 +58,11 @@ const DSOCProjectSchema = new Schema(
required: [true, 'Organization name is required'],
trim: true,
},
- repositoryUrl: {
+ repositoryUrls: [{
type: String,
required: [true, 'Repository URL is required'],
trim: true,
- },
+ }],
websiteUrl: {
type: String,
trim: true,