Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 54 additions & 32 deletions app/admin/dsoc/projects/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function EditProjectPage() {
description: '',
longDescription: '',
organization: '',
repositoryUrl: '',
repositoryUrls: [''],
websiteUrl: '',
timelineUrl: '',
difficulty: 'intermediate',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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 });
Expand All @@ -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 });
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -477,32 +479,52 @@ export default function EditProjectPage() {

{/* Links */}
<div className="space-y-4">
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links</h2>

<div className="grid md:grid-cols-2 gap-4">
<div>
<label className="block font-bold text-sm mb-2">Repository URL *</label>
<input
type="url"
name="repositoryUrl"
value={formData.repositoryUrl}
onChange={handleChange}
required
className="neo-brutal-input"
placeholder="https://github.com/org/repo"
/>
</div>
<div>
<label className="block font-bold text-sm mb-2">Website URL</label>
<input
type="url"
name="websiteUrl"
value={formData.websiteUrl}
onChange={handleChange}
className="neo-brutal-input"
placeholder="https://example.com"
/>
</div>
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links & Repositories</h2>

<div className="space-y-3">
<label className="block font-bold text-sm">Repository URLs * (At least one is required)</label>
{formData.repositoryUrls.map((repoUrl, index) => (
<div key={index} className="flex gap-2">
<input
type="url"
value={repoUrl}
onChange={(e) => 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 && (
<button
type="button"
onClick={() => removeArrayItem('repositoryUrls', index)}
className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
>
<Trash2 className="w-5 h-5" />
</button>
)}
</div>
))}

<button
type="button"
onClick={() => 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"
>
<Plus className="w-4 h-4" />
Add Repository URL
</button>
</div>

<div>
<label className="block font-bold text-sm mb-2">Website URL</label>
<input
type="url"
name="websiteUrl"
value={formData.websiteUrl}
onChange={handleChange}
className="neo-brutal-input"
placeholder="https://example.com"
/>
</div>
</div>

Expand Down
79 changes: 50 additions & 29 deletions app/admin/dsoc/projects/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function NewProjectPage() {
description: '',
longDescription: '',
organization: '',
repositoryUrl: '',
repositoryUrls: [''],
websiteUrl: '',
timelineUrl: '',
difficulty: 'intermediate',
Expand Down Expand Up @@ -97,7 +97,7 @@ export default function NewProjectPage() {
};

const handleArrayChange = (
field: 'requirements' | 'learningOutcomes',
field: 'requirements' | 'learningOutcomes' | 'repositoryUrls',
index: number,
value: string,
) => {
Expand All @@ -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 });
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -485,32 +486,52 @@ export default function NewProjectPage() {

{/* Links */}
<div className="space-y-4">
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links</h2>
<h2 className="font-bold text-lg border-b-2 border-[var(--dsoc-dark)] pb-2">Links & Repositories</h2>

<div className="space-y-3">
<label className="block font-bold text-sm">Repository URLs * (At least one is required)</label>
{formData.repositoryUrls.map((repoUrl, index) => (
<div key={index} className="flex gap-2">
<input
type="url"
value={repoUrl}
onChange={(e) => 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 && (
<button
type="button"
onClick={() => removeArrayItem('repositoryUrls', index)}
className="p-3 bg-[var(--dsoc-pink)] text-white border-4 border-[var(--dsoc-dark)]"
>
<Trash2 className="w-5 h-5" />
</button>
)}
</div>
))}

<button
type="button"
onClick={() => 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"
>
<Plus className="w-4 h-4" />
Add Repository URL
</button>
</div>

<div className="grid md:grid-cols-2 gap-4">
<div>
<label className="block font-bold text-sm mb-2">Repository URL *</label>
<input
type="url"
name="repositoryUrl"
value={formData.repositoryUrl}
onChange={handleChange}
required
className="neo-brutal-input"
placeholder="https://github.com/org/repo"
/>
</div>
<div>
<label className="block font-bold text-sm mb-2">Website URL</label>
<input
type="url"
name="websiteUrl"
value={formData.websiteUrl}
onChange={handleChange}
className="neo-brutal-input"
placeholder="https://example.com"
/>
</div>
<div>
<label className="block font-bold text-sm mb-2">Website URL</label>
<input
type="url"
name="websiteUrl"
value={formData.websiteUrl}
onChange={handleChange}
className="neo-brutal-input"
placeholder="https://example.com"
/>
</div>
</div>

Expand Down
10 changes: 8 additions & 2 deletions app/dsoc/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface Project {
featuredImage?: string;
imageUrl?: string;
repositoryUrl?: string;
repositoryUrls?: string[];
wikiUrl?: string;
}

Expand Down Expand Up @@ -574,10 +575,15 @@ export default function DSOCPage() {
{project.duration}
</div>
<div className="flex gap-2">
{project.repositoryUrl && (
{(project.repositoryUrl || (project.repositoryUrls && project.repositoryUrls.length > 0)) && (
<button
type="button"
onClick={(e) => { 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"
>
<Github className="w-4 h-4" />
Expand Down
75 changes: 62 additions & 13 deletions app/dsoc/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
BookOpen,
Target,
Code2,
ImageIcon,
MessageCircle,
Star,
ImageIcon,
X
} from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
Expand Down Expand Up @@ -48,6 +49,7 @@ interface Project {
longDescription?: string;
organization: string;
repositoryUrl: string;
repositoryUrls?: string[];
websiteUrl?: string;
timelineUrl?: string;
difficulty: 'beginner' | 'intermediate' | 'advanced';
Expand Down Expand Up @@ -307,6 +309,25 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
const [isMentee, setIsMentee] = useState<boolean | null>(null);
const [lightboxImage, setLightboxImage] = useState<string | null>(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();
Expand Down Expand Up @@ -497,16 +518,19 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
</p>
</div>

<div className="flex gap-3">
<a
href={project.repositoryUrl}
target="_blank"
rel="noopener noreferrer"
className="neo-brutal-btn bg-[var(--dsoc-dark)] text-white"
>
<Github className="w-5 h-5 mr-2" />
Repository
</a>
<div className="flex flex-wrap gap-3">
{repoUrls.map((url, i) => (
<a
key={i}
href={url}
target="_blank"
rel="noopener noreferrer"
className="neo-brutal-btn bg-[var(--dsoc-dark)] text-white"
>
<Github className="w-5 h-5 mr-2" />
{getRepoLabel(url)}
</a>
))}
{project.timelineUrl && (
<a
href={project.timelineUrl}
Expand Down Expand Up @@ -820,6 +844,31 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
</p>
</div>

{/* Star Card */}
<div className="neo-brutal-card p-6 border-4 border-[var(--dsoc-dark)]">
<div className="flex items-center gap-2 mb-3">
<Star className="w-6 h-6 fill-current text-[var(--dsoc-dark)] animate-pulse" />
<h3 className="text-lg font-black">Support This Project</h3>
</div>
<p className="text-sm opacity-90 mb-4 leading-relaxed font-bold">
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. ⭐
</p>
<div className="space-y-3">
{repoUrls.map((url, i) => (
<a
key={i}
href={url}
target="_blank"
rel="noopener noreferrer"
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)]"
>
<Github className="w-4 h-4 text-[var(--dsoc-dark)]" />
Star {getRepoLabel(url)}
</a>
))}
</div>
</div>

{project.timelineUrl && (
<div className="neo-brutal-card p-6">
<div className="flex items-start justify-between gap-4">
Expand Down Expand Up @@ -849,7 +898,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st

{/* Discord Card */}
<div className="neo-brutal-card p-6">
<div className="text-[var(--dsoc-dark)] dark:text-[var(--dsoc-light)]">

<MessageCircle className="w-8 h-8 mb-3" />
<h3 className="font-bold text-lg mb-2">Have Questions?</h3>
<p className="text-sm opacity-90 mb-4">
Expand All @@ -863,7 +912,7 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st
>
Join Discord
</a>
</div>

</div>

{/* Tags */}
Expand Down
Loading
Loading