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
12 changes: 8 additions & 4 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,22 +247,26 @@ const config = {
to: "/projects/",
},
{
label: "📚 E-books",
label: "E-books",
to: "/ebooks/",
},

{
label: "🛣️ Roadmap",
label: "Roadmap",
to: "/roadmap/",
},
{
label: "🧑‍💻 Live Editor",
label: "Live Editor",
to: "/LiveEditor/",
},
{
label: "📺 Broadcast",
label: "Broadcast",
to: "https://codeharborhub-broadcast-web.vercel.app/",
},
{
label: "Events",
to: "/events/",
}
],
},
// {
Expand Down
46 changes: 46 additions & 0 deletions src/components/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Link from "@docusaurus/Link";
import { Event } from "../data/events";

export default function EventCard({ event }: { event: Event }) {
return (
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 p-6 flex flex-col items-center text-center">
<img
src={event.logo}
alt={event.name}
className="w-24 h-24 object-contain mb-4"
/>
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
{event.name}
</h2>
<p className="text-sm text-gray-600 dark:text-gray-300 mt-2">
{event.dates}
</p>
<p className="text-gray-700 dark:text-gray-400 mt-3">{event.description}</p>
<div className="flex gap-2 flex-wrap mt-3">
{event.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 text-xs rounded-full bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-200"
>
{tag}
</span>
))}
</div>
<div className="mt-5 flex gap-4">
<Link
href={`/events/${event.id}`}
className="px-4 py-2 rounded-xl bg-indigo-600 text-white hover:bg-indigo-700 transition"
>
View Details
</Link>
<a
href={event.link}
target="_blank"
className="px-4 py-2 rounded-xl border border-indigo-600 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-50 dark:hover:bg-gray-700 transition"
>
Official Site
</a>
</div>
</div>
);
}
83 changes: 83 additions & 0 deletions src/components/Events/ContributionGuide.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { BookOpenCheck, ArrowLeft } from 'lucide-react';
import GuideStep from './GuideStep';
import FAQSection from './FAQSection';
import ResourcesSection from './ResourcesSection';
import { GUIDE_STEPS, FAQS, RESOURCES } from './guide';

interface ContributionGuideProps {
onBackToEvents: () => void;
}

export default function ContributionGuide({ onBackToEvents }: ContributionGuideProps) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<div className="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-gray-900 dark:to-gray-800 border-b border-gray-200 dark:border-gray-700">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<button
onClick={onBackToEvents}
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white mb-6 transition-colors"
>
<ArrowLeft className="w-4 h-4" />
<span>Back to Events</span>
</button>

<div className="flex items-center gap-4 mb-6">
<div className="flex items-center justify-center w-16 h-16 bg-green-600 dark:bg-green-500 rounded-2xl shadow-lg">
<BookOpenCheck className="w-8 h-8 text-white" />
</div>
<div>
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white">
Contribution Guide
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Your step-by-step journey into open source
</p>
</div>
</div>

<p className="text-lg text-gray-700 dark:text-gray-300 leading-relaxed max-w-3xl">
Contributing to open source can seem intimidating at first, but it's one of the most rewarding
ways to learn, teach, and build experience. This guide will walk you through every step of making
your first contribution, from finding a project to getting your pull request merged.
</p>
</div>
</div>

<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-16">
<div className="flex items-center gap-3 mb-8">
<div className="h-1 w-12 bg-blue-600 dark:bg-blue-500 rounded-full"></div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
Getting Started
</h2>
</div>

<div className="space-y-0">
{GUIDE_STEPS.map((step, index) => (
<GuideStep key={step.id} step={step} index={index} />
))}
</div>
</div>

<div className="space-y-8">
<FAQSection faqs={FAQS} />
<ResourcesSection resources={RESOURCES} />
</div>

<div className="mt-12 bg-gradient-to-r from-blue-600 to-cyan-600 dark:from-blue-700 dark:to-cyan-700 rounded-2xl p-8 text-center text-white">
<h3 className="text-2xl font-bold mb-3">Ready to Get Started?</h3>
<p className="mb-6 text-blue-50">
Browse open source events and find the perfect project for your first contribution.
</p>
<button
onClick={onBackToEvents}
className="inline-flex items-center gap-2 px-6 py-3 bg-white text-blue-600 font-semibold rounded-lg hover:bg-blue-50 transition-colors shadow-lg"
>
<ArrowLeft className="w-5 h-5" />
Back to Events
</button>
</div>
</div>
</div>
);
}
75 changes: 75 additions & 0 deletions src/components/Events/EventCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Calendar, ExternalLink, CalendarPlus } from 'lucide-react';
import { Event } from './events';

interface EventCardProps {
event: Event;
onAddToCalendar: (event: Event) => void;
}

export default function EventCard({ event, onAddToCalendar }: EventCardProps) {
return (
<div className="group bg-white dark:bg-gray-800 rounded-2xl shadow-md hover:shadow-2xl transition-all duration-300 overflow-hidden border border-gray-100 dark:border-gray-700 flex flex-col h-full">
<div className="p-6 bg-gradient-to-br from-gray-50 to-white dark:from-gray-800 dark:to-gray-750 flex items-center justify-center h-40 relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-blue-50/50 to-cyan-50/50 dark:from-blue-900/10 dark:to-cyan-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
<img
src={event.logo_url}
alt={`${event.name} logo`}
className="max-w-full max-h-full object-contain filter group-hover:scale-105 transition-transform duration-300 relative z-10 bg-gray-700 p-4 rounded-lg"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.style.display = 'none';
const fallback = document.createElement('div');
fallback.className = 'text-4xl font-bold text-gray-400 dark:text-gray-600';
fallback.textContent = event.name.charAt(0);
target.parentElement?.appendChild(fallback);
}}
/>
</div>

<div className="p-6 flex flex-col flex-grow">
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-3 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
{event.name}
</h3>

<div className="flex items-center text-sm text-gray-600 dark:text-gray-400 mb-4">
<Calendar className="w-4 h-4 mr-2 flex-shrink-0" />
<span>{event.start_date} – {event.end_date}</span>
</div>

<p className="text-gray-600 dark:text-gray-300 mb-4 leading-relaxed flex-grow">
{event.description}
</p>

<div className="flex flex-wrap gap-2 mb-4">
{event.tags.map((tag) => (
<span
key={tag}
className="px-3 py-1 bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 text-xs font-medium rounded-full border border-blue-100 dark:border-blue-800"
>
{tag}
</span>
))}
</div>

<div className="flex gap-3 mt-auto">
<a
href={event.official_link}
target="_blank"
rel="noopener noreferrer"
className="flex-1 flex items-center justify-center gap-2 px-4 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 shadow-sm hover:shadow-md"
>
Learn More
</a>

<button
onClick={() => onAddToCalendar(event)}
className="flex items-center justify-center px-4 py-2.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 font-medium rounded-lg transition-colors duration-200 border border-gray-200 dark:border-gray-600 shadow-sm hover:shadow-md"
title="Add to Calendar"
>
<CalendarPlus className="w-4 h-4" />
</button>
</div>
</div>
</div>
);
}
57 changes: 57 additions & 0 deletions src/components/Events/EventsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState, useMemo } from 'react';
import EventCard from './EventCard';
import { Event, MOCK_EVENTS } from './events';

export default function EventsGrid() {
const [searchQuery, setSearchQuery] = useState('');
const [selectedTags, setSelectedTags] = useState<string[]>([]);



const filteredEvents = useMemo(() => {
return MOCK_EVENTS.filter((event) => {
const matchesSearch =
searchQuery === '' ||
event.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
event.description.toLowerCase().includes(searchQuery.toLowerCase());

const matchesTags =
selectedTags.length === 0 ||
selectedTags.some((tag) => event.tags.includes(tag));

return matchesSearch && matchesTags;
});
}, [searchQuery, selectedTags]);



const handleAddToCalendar = (event: Event) => {
const title = encodeURIComponent(event.name);
const details = encodeURIComponent(event.description);
const dates = encodeURIComponent(`${event.start_date} - ${event.end_date}`);

const googleCalendarUrl = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${title}&details=${details}%0A%0A${event.official_link}&dates=${dates}`;

window.open(googleCalendarUrl, '_blank');
};

return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<>
<div className="mb-6 text-sm text-gray-600 dark:text-gray-400">
Showing {filteredEvents.length} {filteredEvents.length === 1 ? 'event' : 'events'}
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredEvents.map((event) => (
<EventCard
key={event.id}
event={event}
onAddToCalendar={handleAddToCalendar}
/>
))}
</div>
</>
</div>
);
}
59 changes: 59 additions & 0 deletions src/components/Events/FAQSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useState } from 'react';
import { ChevronDown, HelpCircle } from 'lucide-react';
import { FAQ } from './guide';

interface FAQSectionProps {
faqs: FAQ[];
}

function FAQItem({ faq }: { faq: FAQ }) {
const [isOpen, setIsOpen] = useState(false);

return (
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between p-4 text-left bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors"
>
<span className="font-semibold text-gray-900 dark:text-white pr-4">
{faq.question}
</span>
<ChevronDown
className={`w-5 h-5 text-gray-500 dark:text-gray-400 flex-shrink-0 transition-transform duration-200 ${
isOpen ? 'transform rotate-180' : ''
}`}
/>
</button>
{isOpen && (
<div className="px-4 pb-4 bg-white dark:bg-gray-800">
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
{faq.answer}
</p>
</div>
)}
</div>
);
}

export default function FAQSection({ faqs }: FAQSectionProps) {
return (
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-gray-900 dark:to-gray-800 rounded-2xl p-8 md:p-12">
<div className="flex items-center gap-3 mb-6">
<div className="flex items-center justify-center w-12 h-12 bg-blue-600 dark:bg-blue-500 rounded-xl">
<HelpCircle className="w-6 h-6 text-white" />
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
Frequently Asked Questions
</h2>
</div>
<p className="text-gray-600 dark:text-gray-400 mb-8">
Common questions from new contributors, answered by the community.
</p>
<div className="space-y-3">
{faqs.map((faq) => (
<FAQItem key={faq.id} faq={faq} />
))}
</div>
</div>
);
}
Loading
Loading