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
189 changes: 143 additions & 46 deletions src/components/home-sections/events-section.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,112 @@
import { events, communityLinks } from "@/src/lib/constants";
import { Card } from "@/src/components/ui/card";
import { Button } from "@/src/components/ui/button";
import { Calendar, MapPin, Users, Clock } from "lucide-react";
import { Badge } from "@/src/components/ui/badge";
import { Calendar, MapPin, Users, Clock, ArrowUpRight } from "lucide-react";
import Link from "next/link";

type CommunityEvent = (typeof events)[number];

const upcomingEvents = events.filter((e) => e.status === "upcoming");
const pastEvents = events.filter((e) => e.status === "past");

function EventMeta({ event }: { event: CommunityEvent }) {
const isPast = event.status === "past";
return (
<div className="grid gap-3 pt-2 sm:grid-cols-2">
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Calendar className="h-4 w-4 text-primary" />
<span>{event.date}</span>
</div>
{event.time && (
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Clock className="h-4 w-4 text-primary" />
<span>{event.time}</span>
</div>
)}
{event.location && (
<div className="flex items-center gap-2 text-sm text-foreground/60">
<MapPin className="h-4 w-4 text-primary" />
<span>{event.location}</span>
</div>
)}
{event.attendees > 0 && (
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Users className="h-4 w-4 text-primary" />
<span>
{event.attendees} {isPast ? "attended" : "going"}
</span>
</div>
)}
</div>
);
}

function UpcomingCard({ event }: { event: CommunityEvent }) {
return (
<Card className="group overflow-hidden border border-border/50 transition-all hover:border-primary/50 hover:shadow-lg">
<div className="flex flex-col gap-6 p-6 md:flex-row md:items-center md:justify-between">
<div className="flex-1 space-y-3">
<h4 className="text-xl font-bold text-foreground">{event.title}</h4>
<p className="text-foreground/70">{event.description}</p>
<EventMeta event={event} />
</div>
<Link
href={event.registerUrl || communityLinks.whatsapp}
target="_blank"
className="block md:w-auto"
>
<Button variant="gradient" className="w-full md:w-auto">
Register
</Button>
</Link>
</div>
</Card>
);
}

function PastCard({ event }: { event: CommunityEvent }) {
return (
<Card className="overflow-hidden border border-border/50 bg-muted/30 transition-all hover:border-border">
<div className="flex flex-col gap-6 p-6 md:flex-row md:items-center md:justify-between">
<div className="flex-1 space-y-3">
<div className="flex items-center gap-3">
<h4 className="text-xl font-bold text-foreground/90">
{event.title}
</h4>
<Badge
variant="secondary"
className="bg-foreground/10 text-foreground/60"
>
Past
</Badge>
</div>
<p className="text-foreground/60">{event.description}</p>
<EventMeta event={event} />
</div>
{event.recapUrl ? (
<Link
href={event.recapUrl}
target="_blank"
className="block md:w-auto"
>
<Button variant="outline" className="w-full md:w-auto">
View recap
<ArrowUpRight className="ml-2 h-4 w-4" />
</Button>
</Link>
) : null}
</div>
</Card>
);
}

export function EventsSection() {
return (
<section id="events" className="relative py-20 md:py-32">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Section header */}
<div className="mb-16 space-y-4">
<div className="mb-12 space-y-4">
<h2 className="text-3xl font-bold text-foreground md:text-5xl">
Events & Meetups
</h2>
Expand All @@ -19,52 +116,52 @@ export function EventsSection() {
</p>
</div>

{/* Events grid */}
<div className="space-y-4">
{events.map((event) => (
<Card
key={event.id}
className="group overflow-hidden border border-border/50 transition-all hover:border-primary/50 hover:shadow-lg"
>
<div className="flex flex-col gap-6 p-6 md:flex-row md:items-center md:justify-between">
{/* Event info */}
<div className="flex-1 space-y-3">
<h3 className="text-xl font-bold text-foreground">
{event.title}
</h3>
<p className="text-foreground/70">{event.description}</p>

{/* Details */}
<div className="grid gap-3 pt-2 sm:grid-cols-2">
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Calendar className="h-4 w-4 text-primary" />
<span>{event.date}</span>
</div>
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Clock className="h-4 w-4 text-primary" />
<span>{event.time}</span>
</div>
<div className="flex items-center gap-2 text-sm text-foreground/60">
<MapPin className="h-4 w-4 text-primary" />
<span>{event.location}</span>
</div>
<div className="flex items-center gap-2 text-sm text-foreground/60">
<Users className="h-4 w-4 text-primary" />
<span>{event.attendees} attending</span>
</div>
</div>
</div>

{/* CTA */}
<Link href={communityLinks.whatsapp} target="_blank" className="block md:w-auto">
<Button variant="gradient" className="w-full md:w-auto">
Register
</Button>
</Link>
</div>
{/* Upcoming */}
<div className="space-y-6">
<div className="flex items-center gap-3">
<h3 className="text-2xl font-bold text-foreground">
Upcoming Events
</h3>
{upcomingEvents.length > 0 && (
<Badge className="bg-primary/10 text-primary">
{upcomingEvents.length}
</Badge>
)}
</div>
{upcomingEvents.length > 0 ? (
<div className="space-y-4">
{upcomingEvents.map((event) => (
<UpcomingCard key={event.id} event={event} />
))}
</div>
) : (
<Card className="border border-dashed border-border/60 p-8 text-center">
<p className="text-foreground/60">
No upcoming events scheduled right now.{" "}
<Link
href={communityLinks.whatsapp}
target="_blank"
className="font-medium text-primary hover:underline"
>
Join the community
</Link>{" "}
to be the first to know.
</p>
</Card>
))}
)}
</div>

{/* Past */}
{pastEvents.length > 0 && (
<div className="mt-16 space-y-6">
<h3 className="text-2xl font-bold text-foreground">Past Events</h3>
<div className="space-y-4">
{pastEvents.map((event) => (
<PastCard key={event.id} event={event} />
))}
</div>
</div>
)}
</div>
</section>
);
Expand Down
23 changes: 1 addition & 22 deletions src/components/home-sections/team-section.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { teamMembers, communityLinks } from "@/src/lib/constants";
import { teamMembers } from "@/src/lib/constants";
import { Card } from "@/src/components/ui/card";
import Image from "next/image";
import { Github, Linkedin, Twitter } from "lucide-react";
Expand Down Expand Up @@ -76,27 +76,6 @@ export function TeamSection() {
</Card>
))}
</div>

{/* Join the team CTA */}
<div className="mt-16 text-center">
<div className="inline-flex flex-col items-center gap-4 rounded-2xl border border-border/50 bg-card p-8">
<h3 className="text-xl font-bold text-foreground">
Want to Join Our Team?
</h3>
<p className="max-w-md text-foreground/70">
We&apos;re always looking for passionate individuals to help grow
the Django community in Rwanda.
</p>
<a
href={communityLinks.whatsapp}
target="_blank"
rel="noreferrer"
className="inline-flex items-center rounded-lg bg-gradient-to-r from-primary to-primary-light px-6 py-3 font-medium text-primary-foreground transition-all hover:opacity-90"
>
Get Involved
</a>
</div>
</div>
</div>
</section>
);
Expand Down
39 changes: 27 additions & 12 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const navigationItems = [
export const communityLinks = {
// Primary "Join Community" entry point — drives every Join/Register CTA
// (header, hero, team, events, footer "Get Involved" + "Join Us").
whatsapp: "https://chat.whatsapp.com/your-invite-code", // TODO: real WhatsApp invite link
whatsapp: "https://chat.whatsapp.com/GvIF9Mw1E2ZBf2S5wazfOo?mode=gi_t",

// Social / code
github: "https://github.com/djangorwanda",
Expand Down Expand Up @@ -87,24 +87,39 @@ export const workshops = [
},
]

// Events. `status` drives the Upcoming vs Past subsections.
// Optional fields render only when set: `time`, `location`, `attendees` (>0),
// `registerUrl` (upcoming — falls back to the WhatsApp community), and
// `recapUrl` (past — links a recording / photos / writeup).
export const events = [
// ── Upcoming ────────────────────────────────────────────────────────────
{
id: 1,
title: "Monthly Meetup - Kigali",
date: "Every 2nd Saturday",
title: "Python/Django July Meetup 2026",
date: "Friday, 24 July 2026",
time: "2:00 PM",
location: "Kigali Innovation Hub",
description: "Join fellow Django developers for networking, knowledge sharing, and coffee.",
attendees: 45,
location: "GIZ Digital Transformation Center, Rwanda",
description:
"A community meetup for Python and Django developers — talks, networking, and hands-on sessions.",
status: "upcoming",
attendees: 0,
registerUrl:
"https://docs.google.com/forms/d/e/1FAIpQLSetBc2-fgyo1IRR3_8hQ1mMDc-LCIwD5fWv7FxcsfXUTrf2Ww/viewform",
recapUrl: "",
},
// ── Past ────────────────────────────────────────────────────────────────
{
id: 2,
title: "Django Bootcamp - Q1 2026",
date: "January 15 - March 15",
time: "Flexible",
location: "Online + In-person",
description: "Intensive bootcamp to launch your Django journey and build real projects.",
attendees: 200,
title: "Python & Django Rwanda Bootcamp",
date: "February – April 2026",
time: "",
location: "",
description:
"A 3-month intensive bootcamp covering Python and Django through hands-on, real-world projects.",
status: "past",
attendees: 0,
registerUrl: "",
recapUrl: "", // TODO: link recording / photos / writeup
},
]

Expand Down
Loading