Skip to content

Commit 09be7de

Browse files
Merge pull request #49 from devweekends/refactor-repo-things
refactor: Add repo urls and star them
2 parents c10cbf3 + 2150f92 commit 09be7de

6 files changed

Lines changed: 185 additions & 81 deletions

File tree

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

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function EditProjectPage() {
5050
description: '',
5151
longDescription: '',
5252
organization: '',
53-
repositoryUrl: '',
53+
repositoryUrls: [''],
5454
websiteUrl: '',
5555
timelineUrl: '',
5656
difficulty: 'intermediate',
@@ -110,7 +110,9 @@ export default function EditProjectPage() {
110110
description: project.description || '',
111111
longDescription: project.longDescription || '',
112112
organization: project.organization || '',
113-
repositoryUrl: project.repositoryUrl || '',
113+
repositoryUrls: Array.isArray(project.repositoryUrls) && project.repositoryUrls.length > 0
114+
? project.repositoryUrls
115+
: (project.repositoryUrl ? [project.repositoryUrl] : ['']),
114116
websiteUrl: project.websiteUrl || '',
115117
timelineUrl: project.timelineUrl || '',
116118
difficulty: project.difficulty || 'intermediate',
@@ -146,7 +148,7 @@ export default function EditProjectPage() {
146148
setFormData({ ...formData, [e.target.name]: e.target.value });
147149
};
148150

149-
const handleArrayChange = (field: 'requirements' | 'learningOutcomes', index: number, value: string) => {
151+
const handleArrayChange = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number, value: string) => {
150152
const updated = [...formData[field]];
151153
updated[index] = value;
152154
setFormData({ ...formData, [field]: updated });
@@ -165,11 +167,11 @@ export default function EditProjectPage() {
165167
});
166168
};
167169

168-
const addArrayItem = (field: 'requirements' | 'learningOutcomes') => {
170+
const addArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls') => {
169171
setFormData({ ...formData, [field]: [...formData[field], ''] });
170172
};
171173

172-
const removeArrayItem = (field: 'requirements' | 'learningOutcomes', index: number) => {
174+
const removeArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number) => {
173175
const updated = formData[field].filter((_, i) => i !== index);
174176
setFormData({ ...formData, [field]: updated });
175177
};
@@ -272,7 +274,7 @@ export default function EditProjectPage() {
272274
description: formData.description,
273275
longDescription: formData.longDescription,
274276
organization: formData.organization,
275-
repositoryUrl: formData.repositoryUrl,
277+
repositoryUrls: formData.repositoryUrls.filter(Boolean),
276278
websiteUrl: formData.websiteUrl,
277279
timelineUrl: formData.timelineUrl,
278280
difficulty: formData.difficulty,
@@ -477,32 +479,52 @@ export default function EditProjectPage() {
477479

478480
{/* Links */}
479481
<div className="space-y-4">
480-
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links</h2>
481-
482-
<div className="grid md:grid-cols-2 gap-4">
483-
<div>
484-
<label className="block font-bold text-sm mb-2">Repository URL *</label>
485-
<input
486-
type="url"
487-
name="repositoryUrl"
488-
value={formData.repositoryUrl}
489-
onChange={handleChange}
490-
required
491-
className="neo-brutal-input"
492-
placeholder="https://github.com/org/repo"
493-
/>
494-
</div>
495-
<div>
496-
<label className="block font-bold text-sm mb-2">Website URL</label>
497-
<input
498-
type="url"
499-
name="websiteUrl"
500-
value={formData.websiteUrl}
501-
onChange={handleChange}
502-
className="neo-brutal-input"
503-
placeholder="https://example.com"
504-
/>
505-
</div>
482+
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links & Repositories</h2>
483+
484+
<div className="space-y-3">
485+
<label className="block font-bold text-sm">Repository URLs * (At least one is required)</label>
486+
{formData.repositoryUrls.map((repoUrl, index) => (
487+
<div key={index} className="flex gap-2">
488+
<input
489+
type="url"
490+
value={repoUrl}
491+
onChange={(e) => handleArrayChange('repositoryUrls', index, e.target.value)}
492+
required={index === 0}
493+
className="neo-brutal-input flex-1"
494+
placeholder="https://github.com/org/repo"
495+
/>
496+
{formData.repositoryUrls.length > 1 && (
497+
<button
498+
type="button"
499+
onClick={() => removeArrayItem('repositoryUrls', index)}
500+
className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
501+
>
502+
<Trash2 className="w-5 h-5" />
503+
</button>
504+
)}
505+
</div>
506+
))}
507+
508+
<button
509+
type="button"
510+
onClick={() => addArrayItem('repositoryUrls')}
511+
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"
512+
>
513+
<Plus className="w-4 h-4" />
514+
Add Repository URL
515+
</button>
516+
</div>
517+
518+
<div>
519+
<label className="block font-bold text-sm mb-2">Website URL</label>
520+
<input
521+
type="url"
522+
name="websiteUrl"
523+
value={formData.websiteUrl}
524+
onChange={handleChange}
525+
className="neo-brutal-input"
526+
placeholder="https://example.com"
527+
/>
506528
</div>
507529
</div>
508530

app/admin/dsoc/projects/new/page.tsx

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default function NewProjectPage() {
4444
description: '',
4545
longDescription: '',
4646
organization: '',
47-
repositoryUrl: '',
47+
repositoryUrls: [''],
4848
websiteUrl: '',
4949
timelineUrl: '',
5050
difficulty: 'intermediate',
@@ -97,7 +97,7 @@ export default function NewProjectPage() {
9797
};
9898

9999
const handleArrayChange = (
100-
field: 'requirements' | 'learningOutcomes',
100+
field: 'requirements' | 'learningOutcomes' | 'repositoryUrls',
101101
index: number,
102102
value: string,
103103
) => {
@@ -106,11 +106,11 @@ export default function NewProjectPage() {
106106
setFormData({ ...formData, [field]: updated });
107107
};
108108

109-
const addArrayItem = (field: 'requirements' | 'learningOutcomes') => {
109+
const addArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls') => {
110110
setFormData({ ...formData, [field]: [...formData[field], ''] });
111111
};
112112

113-
const removeArrayItem = (field: 'requirements' | 'learningOutcomes', index: number) => {
113+
const removeArrayItem = (field: 'requirements' | 'learningOutcomes' | 'repositoryUrls', index: number) => {
114114
const updated = formData[field].filter((_, i) => i !== index);
115115
setFormData({ ...formData, [field]: updated });
116116
};
@@ -226,6 +226,7 @@ export default function NewProjectPage() {
226226
headers: { 'Content-Type': 'application/json' },
227227
body: JSON.stringify({
228228
...formData,
229+
repositoryUrls: formData.repositoryUrls.filter(Boolean),
229230
technologies: formData.technologies.split(',').map((s) => s.trim()).filter(Boolean),
230231
tags: formData.tags.split(',').map((s) => s.trim()).filter(Boolean),
231232
mentors: formData.mentors,
@@ -485,32 +486,52 @@ export default function NewProjectPage() {
485486

486487
{/* Links */}
487488
<div className="space-y-4">
488-
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links</h2>
489+
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links & Repositories</h2>
490+
491+
<div className="space-y-3">
492+
<label className="block font-bold text-sm">Repository URLs * (At least one is required)</label>
493+
{formData.repositoryUrls.map((repoUrl, index) => (
494+
<div key={index} className="flex gap-2">
495+
<input
496+
type="url"
497+
value={repoUrl}
498+
onChange={(e) => handleArrayChange('repositoryUrls', index, e.target.value)}
499+
required={index === 0}
500+
className="neo-brutal-input flex-1"
501+
placeholder="https://github.com/org/repo"
502+
/>
503+
{formData.repositoryUrls.length > 1 && (
504+
<button
505+
type="button"
506+
onClick={() => removeArrayItem('repositoryUrls', index)}
507+
className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
508+
>
509+
<Trash2 className="w-5 h-5" />
510+
</button>
511+
)}
512+
</div>
513+
))}
514+
515+
<button
516+
type="button"
517+
onClick={() => addArrayItem('repositoryUrls')}
518+
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"
519+
>
520+
<Plus className="w-4 h-4" />
521+
Add Repository URL
522+
</button>
523+
</div>
489524

490-
<div className="grid md:grid-cols-2 gap-4">
491-
<div>
492-
<label className="block font-bold text-sm mb-2">Repository URL *</label>
493-
<input
494-
type="url"
495-
name="repositoryUrl"
496-
value={formData.repositoryUrl}
497-
onChange={handleChange}
498-
required
499-
className="neo-brutal-input"
500-
placeholder="https://github.com/org/repo"
501-
/>
502-
</div>
503-
<div>
504-
<label className="block font-bold text-sm mb-2">Website URL</label>
505-
<input
506-
type="url"
507-
name="websiteUrl"
508-
value={formData.websiteUrl}
509-
onChange={handleChange}
510-
className="neo-brutal-input"
511-
placeholder="https://example.com"
512-
/>
513-
</div>
525+
<div>
526+
<label className="block font-bold text-sm mb-2">Website URL</label>
527+
<input
528+
type="url"
529+
name="websiteUrl"
530+
value={formData.websiteUrl}
531+
onChange={handleChange}
532+
className="neo-brutal-input"
533+
placeholder="https://example.com"
534+
/>
514535
</div>
515536
</div>
516537

app/dsoc/page.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ interface Project {
4343
featuredImage?: string;
4444
imageUrl?: string;
4545
repositoryUrl?: string;
46+
repositoryUrls?: string[];
4647
wikiUrl?: string;
4748
}
4849

@@ -574,10 +575,15 @@ export default function DSOCPage() {
574575
{project.duration}
575576
</div>
576577
<div className="flex gap-2">
577-
{project.repositoryUrl && (
578+
{(project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls.length > 0)) && (
578579
<button
579580
type="button"
580-
onClick={(e) => { e.preventDefault(); e.stopPropagation(); window.open(project.repositoryUrl, '_blank'); }}
581+
onClick={(e) => {
582+
e.preventDefault();
583+
e.stopPropagation();
584+
const targetUrl = project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls[0]);
585+
if (targetUrl) window.open(targetUrl, '_blank');
586+
}}
581587
className="inline-flex items-center gap-1 text-sm font-bold text-[var(--dsoc-dark)] hover:text-[var(--dsoc-primary)] transition-colors"
582588
>
583589
<Github className="w-4 h-4" />

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

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import {
1616
BookOpen,
1717
Target,
1818
Code2,
19-
ImageIcon,
2019
MessageCircle,
20+
Star,
21+
ImageIcon,
2122
X
2223
} from "lucide-react";
2324
import { Swiper, SwiperSlide } from "swiper/react";
@@ -48,6 +49,7 @@ interface Project {
4849
longDescription?: string;
4950
organization: string;
5051
repositoryUrl: string;
52+
repositoryUrls?: string[];
5153
websiteUrl?: string;
5254
timelineUrl?: string;
5355
difficulty: 'beginner' | 'intermediate' | 'advanced';
@@ -307,6 +309,25 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
307309
const [isMentee, setIsMentee] = useState<boolean | null>(null);
308310
const [lightboxImage, setLightboxImage] = useState<string | null>(null);
309311

312+
const getRepoLabel = (url: string) => {
313+
try {
314+
const path = new URL(url).pathname;
315+
const parts = path.split('/').filter(Boolean);
316+
if (parts.length >= 2) {
317+
return parts.slice(-2).join('/');
318+
}
319+
return 'Repository';
320+
} catch {
321+
return 'Repository';
322+
}
323+
};
324+
325+
const repoUrls = project
326+
? Array.isArray(project.repositoryUrls) && project.repositoryUrls.length > 0
327+
? project.repositoryUrls
328+
: (project.repositoryUrl ? [project.repositoryUrl] : [])
329+
: [];
330+
310331
useEffect(() => {
311332
fetchProject();
312333
checkMenteeSession();
@@ -497,16 +518,19 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
497518
</p>
498519
</div>
499520

500-
<div className="flex gap-3">
501-
<a
502-
href={project.repositoryUrl}
503-
target="_blank"
504-
rel="noopener noreferrer"
505-
className="neo-brutal-btn bg-[var(--dsoc-dark)] text-white"
506-
>
507-
<Github className="w-5 h-5 mr-2" />
508-
Repository
509-
</a>
521+
<div className="flex flex-wrap gap-3">
522+
{repoUrls.map((url, i) => (
523+
<a
524+
key={i}
525+
href={url}
526+
target="_blank"
527+
rel="noopener noreferrer"
528+
className="neo-brutal-btn bg-[var(--dsoc-dark)] text-white"
529+
>
530+
<Github className="w-5 h-5 mr-2" />
531+
{getRepoLabel(url)}
532+
</a>
533+
))}
510534
{project.timelineUrl && (
511535
<a
512536
href={project.timelineUrl}
@@ -820,6 +844,31 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
820844
</p>
821845
</div>
822846

847+
{/* Star Card */}
848+
<div className="neo-brutal-card p-6 border-4 border-[var(--dsoc-dark)]">
849+
<div className="flex items-center gap-2 mb-3">
850+
<Star className="w-6 h-6 fill-current text-[var(--dsoc-dark)] animate-pulse" />
851+
<h3 className="text-lg font-black">Support This Project</h3>
852+
</div>
853+
<p className="text-sm opacity-90 mb-4 leading-relaxed font-bold">
854+
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. ⭐
855+
</p>
856+
<div className="space-y-3">
857+
{repoUrls.map((url, i) => (
858+
<a
859+
key={i}
860+
href={url}
861+
target="_blank"
862+
rel="noopener noreferrer"
863+
className="neo-brutal-btn bg-white text-[var(--dsoc-dark)] hover:bg-yellow-50 w-full flex items-center justify-center gap-2 border-4 border-[var(--dsoc-dark)]"
864+
>
865+
<Github className="w-4 h-4 text-[var(--dsoc-dark)]" />
866+
Star {getRepoLabel(url)}
867+
</a>
868+
))}
869+
</div>
870+
</div>
871+
823872
{project.timelineUrl && (
824873
<div className="neo-brutal-card p-6">
825874
<div className="flex items-start justify-between gap-4">
@@ -849,7 +898,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
849898

850899
{/* Discord Card */}
851900
<div className="neo-brutal-card p-6">
852-
<div className="text-[var(--dsoc-dark)] dark:text-[var(--dsoc-light)]">
901+
853902
<MessageCircle className="w-8 h-8 mb-3" />
854903
<h3 className="font-bold text-lg mb-2">Have Questions?</h3>
855904
<p className="text-sm opacity-90 mb-4">
@@ -863,7 +912,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
863912
>
864913
Join Discord
865914
</a>
866-
</div>
915+
867916
</div>
868917

869918
{/* Tags */}

0 commit comments

Comments
 (0)