Skip to content

Commit b826b7e

Browse files
committed
Add sprints POC with demo content
1 parent 6b93b86 commit b826b7e

File tree

7 files changed

+319
-0
lines changed

7 files changed

+319
-0
lines changed

src/components/SprintCard.astro

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
import { getEntry } from 'astro:content';
3+
import Markdown from "@ui/Markdown.astro";
4+
5+
const { slug } = Astro.props;
6+
7+
8+
const sprint = await getEntry("sprints", slug);
9+
10+
if (!sprint) {
11+
throw new Error(`Sprint entry "${slug}" not found`);
12+
}
13+
14+
const { data, body } = sprint;
15+
16+
// <a href={`/sprints/${slug}`} id={slug} class="text-blue-600 hover:text-blue-800 hover:underline"></a>
17+
---
18+
19+
<article class="sprint-card bg-white border border-gray-200 rounded-lg p-6 mb-8 shadow-sm hover:shadow-md transition-shadow duration-200" data-python-level={data.pythonLevel} data-room={data.room}>
20+
<header class="mb-4">
21+
<h3 class="text-2xl font-semibold mb-2">
22+
{data.title}
23+
</h3>
24+
<div class="flex flex-wrap gap-4 text-sm text-gray-600">
25+
<span class="flex items-center gap-1">
26+
<span>👥</span>
27+
<span>{data.numberOfPeople}</span>
28+
</span>
29+
<span class="flex items-center gap-1">
30+
<span>📍</span>
31+
<span>{data.room}</span>
32+
</span>
33+
<span class="flex items-center gap-1">
34+
<span>🐍</span>
35+
<span>{data.pythonLevel}</span>
36+
</span>
37+
</div>
38+
</header>
39+
40+
<div class="space-y-4">
41+
<div class="text-sm">
42+
<span class="font-medium text-gray-900">Contact:</span>
43+
<span class="text-gray-700">{data.contactPerson.name}</span>
44+
{data.contactPerson.email && (
45+
<span class="text-gray-600">
46+
{' '}(<a href={`mailto:${data.contactPerson.email}`} class="text-blue-600 hover:text-blue-800 hover:underline">{data.contactPerson.email}</a>)
47+
</span>
48+
)}
49+
{data.contactPerson.github && (
50+
<span class="text-gray-600">
51+
{' '}(<a href={`https://github.com/${data.contactPerson.github}`} target="_blank" rel="noopener" class="text-blue-600 hover:text-blue-800 hover:underline">@{data.contactPerson.github}</a>)
52+
</span>
53+
)}
54+
</div>
55+
56+
{data.tags && data.tags.length > 0 && (
57+
<div class="flex flex-wrap gap-2 pt-2">
58+
{data.tags.map((tag) => (
59+
<span key={tag} class="bg-gray-100 text-gray-700 px-2 py-1 rounded text-xs font-medium">{tag}</span>
60+
))}
61+
</div>
62+
)}
63+
64+
{data.description && (
65+
<p class="text-gray-700 leading-relaxed">{data.description}</p>
66+
)}
67+
68+
{(data.links) && (
69+
<div class="text-sm">
70+
<span class="font-medium text-gray-900">Links:</span>
71+
<ul class="mt-2 ml-6 space-y-1 list-disc">
72+
{data.links?.map((link) => (
73+
<li key={link.url}>
74+
<a href={link.url} target="_blank" rel="noopener" class="text-blue-600 hover:text-blue-800 hover:underline">{link.title}</a>
75+
</li>
76+
))}
77+
</ul>
78+
</div>
79+
)}
80+
81+
82+
{body &&
83+
<Markdown content={body} />
84+
}
85+
86+
</div>
87+
88+
</article>

src/content/config.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,43 @@ const jobs = defineCollection({
255255
}),
256256
});
257257

258+
const sprints = defineCollection({
259+
type: "content",
260+
schema: z.object({
261+
title: z.string(),
262+
slug: z.string().optional(), // Auto-generated from filename if not provided
263+
numberOfPeople: z.string().or(z.number()),
264+
room: z.string(),
265+
pythonLevel: z.enum(["Any", "Beginner", "Intermediate", "Advanced"]),
266+
contactPerson: z.object({
267+
name: z.string(),
268+
email: z.string().email().optional(),
269+
github: z.string().optional(),
270+
twitter: z.string().optional(),
271+
}),
272+
links: z
273+
.array(
274+
z.object({
275+
title: z.string(),
276+
url: z.string().url(),
277+
})
278+
)
279+
.optional(),
280+
description: z.string().optional(),
281+
tags: z.array(z.string()).optional(),
282+
status: z.enum(["active", "full", "cancelled"]).default("active"),
283+
draft: z.boolean().optional().default(false),
284+
}),
285+
});
286+
258287
export const collections = {
259288
days,
260289
pages,
261290
deadlines,
262291
week,
263292
sessions,
264293
speakers,
294+
sprints,
265295
keynoters,
266296
sponsors,
267297
jobs,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: ""
3+
numberOfPeople: ""
4+
room: ""
5+
pythonLevel: "Any"
6+
contactPerson:
7+
name: ""
8+
9+
status: "active"
10+
draft: true
11+
---
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
title: "Bracelet weaving workshop"
3+
numberOfPeople: "10 (10 weavers + Nika)"
4+
room: "RB 112"
5+
pythonLevel: "Any"
6+
contactPerson:
7+
name: "Nika"
8+
9+
links:
10+
- title: "EuroPython GitHub"
11+
url: "https://github.com/EuroPython"
12+
tags: ["workshop", "creative", "hands-on"]
13+
status: "active"
14+
description:
15+
"Nika's workshop is a wonderful opportunity to create your own unique bracelet
16+
and bring it back as a memorable keepsake from the conference. Whether you are
17+
a seasoned weaver or a complete beginner, all are welcome to participate and
18+
have fun 🙂 We look forward to seeing you there!"
19+
---
20+
21+
Join Nika for a creative break from coding! This hands-on bracelet weaving
22+
workshop offers a wonderful opportunity to create your own unique bracelet as a
23+
memorable keepsake from the conference.
24+
25+
Whether you're a seasoned weaver or a complete beginner, all are welcome to
26+
participate and have fun. It's a great way to meet other attendees and take a
27+
relaxing break from the technical sessions.

src/content/sprints/data-morph.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
title: "Data Morph"
3+
numberOfPeople: "4 (2-3 contributors + 1 maintainer)"
4+
room: "RB 106 (Saturday only)"
5+
pythonLevel: "Intermediate"
6+
contactPerson:
7+
name: "Stefanie Molin"
8+
github: "stefmolin"
9+
repository: "https://github.com/stefmolin/data-morph"
10+
documentation: "https://stefaniemolin.com/data-morph"
11+
demo: "https://stefaniemolin.com/data-morph/stable/_images/panda-to-star.gif"
12+
sprintableIssues: "https://github.com/stefmolin/data-morph/issues?q=is%3Aissue+label%3A%22help+wanted%22+is%3Aopen"
13+
tags: ["data-visualization", "python", "intermediate"]
14+
status: "active"
15+
---
16+
17+
Data Morph is a fascinating project that transforms datasets while preserving
18+
statistical properties. Join Stefanie and other contributors to work on this
19+
innovative data visualization tool.
20+
21+
This sprint is perfect for intermediate Python developers interested in data
22+
science and visualization. Check out the sprintable issues to see what you can
23+
contribute to!
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
title: "SPDT Python Library"
3+
numberOfPeople: "~6 (5 contributors + 1 organiser)"
4+
room: "RB 106"
5+
pythonLevel: "Any"
6+
contactPerson:
7+
name: "Kayode Oladapo"
8+
9+
links:
10+
- title: "Repository"
11+
url: "https://github.com/oladapokayodeabiodun/spdt"
12+
tags: ["python", "library", "open-source"]
13+
status: "active"
14+
---
15+
16+
Join us for the SPDT Python Library sprint! This is a great opportunity to
17+
contribute to an open-source Python library and collaborate with fellow
18+
developers.
19+
20+
Whether you're new to open source or an experienced contributor, we welcome
21+
participants of all skill levels to help improve and expand the SPDT library.

src/pages/sprints.astro

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
import { getCollection } from 'astro:content';
3+
import Layout from '@layouts/SectionLayout.astro'; // Adjust path as needed
4+
import SprintCard from '@components/SprintCard.astro';
5+
import Headline from "@ui/Headline.astro"
6+
7+
// Get all sprint entries
8+
const sprints = await getCollection("sprints", ({ data }) => {
9+
return import.meta.env.MODE === "production" ? data.draft !== true : true;
10+
});
11+
12+
// Sort sprints by title or any other criteria
13+
const sortedSprints = sprints.sort((a, b) =>
14+
a.data.title.localeCompare(b.data.title)
15+
);
16+
17+
// Filter by status if needed
18+
const activeSprints = sortedSprints.filter(sprint =>
19+
sprint.data.status === 'active'
20+
);
21+
22+
// Extract unique rooms for filter
23+
const uniqueRooms = Array.from(new Set(activeSprints.map(sprint => sprint.data.room)));
24+
---
25+
26+
<Layout title="Sprints & Workshops" description="All in one">
27+
<main class="min-h-screen py-8">
28+
<div class="max-w-6xl mx-auto px-4">
29+
<header class="text-center mb-12">
30+
<h1 class="text-5xl font-bold text-gray-900 mb-4">Sprints & Workshops</h1>
31+
<p class="text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
32+
Join collaborative coding sessions and hands-on workshops.
33+
Connect with maintainers, contribute to open source projects,
34+
and learn new skills alongside fellow Python enthusiasts.
35+
</p>
36+
</header>
37+
38+
<div class="flex flex-wrap gap-8 justify-center mb-8">
39+
<div class="flex flex-col gap-2">
40+
<label for="level-filter" class="font-semibold text-gray-700 text-sm">Python Level:</label>
41+
<select id="level-filter" class="px-4 py-2 border border-gray-300 rounded-lg bg-white text-sm min-w-[150px] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
42+
<option value="">All Levels</option>
43+
<option value="Any">Any</option>
44+
<option value="Beginner">Beginner</option>
45+
<option value="Intermediate">Intermediate</option>
46+
<option value="Advanced">Advanced</option>
47+
</select>
48+
</div>
49+
50+
<div class="flex flex-col gap-2">
51+
<label for="room-filter" class="font-semibold text-gray-700 text-sm">Room:</label>
52+
<select id="room-filter" class="px-4 py-2 border border-gray-300 rounded-lg bg-white text-sm min-w-[150px] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
53+
<option value="">All Rooms</option>
54+
{uniqueRooms.map((room) => (
55+
<option value={room}>{room}</option>
56+
))}
57+
</select>
58+
</div>
59+
</div>
60+
61+
<div class="text-center mb-8">
62+
<p class="text-gray-600 font-medium">{activeSprints.length} active {activeSprints.length === 1 ? 'sprint' : 'sprints'}</p>
63+
</div>
64+
65+
<section class="max-w-4xl mx-auto" id="sprints-list">
66+
{activeSprints.map((sprint) => (
67+
<SprintCard slug={sprint.slug} />
68+
))}
69+
</section>
70+
71+
{activeSprints.length === 0 && (
72+
<div class="text-center py-12">
73+
<p class="text-gray-500 text-lg">No sprints are currently available. Check back soon!</p>
74+
</div>
75+
)}
76+
</div>
77+
</main>
78+
79+
<script>
80+
// Client-side filtering
81+
document.addEventListener('DOMContentLoaded', () => {
82+
const levelFilter = document.getElementById('level-filter') as HTMLSelectElement;
83+
const roomFilter = document.getElementById('room-filter') as HTMLSelectElement;
84+
const sprintsList = document.getElementById('sprints-list');
85+
const sprintsCount = document.querySelector('.text-center.mb-8 p');
86+
87+
function filterSprints() {
88+
const selectedLevel = levelFilter.value;
89+
const selectedRoom = roomFilter.value;
90+
const sprintCards = sprintsList?.querySelectorAll('.sprint-card');
91+
92+
let visibleCount = 0;
93+
94+
sprintCards?.forEach((card) => {
95+
const cardElement = card as HTMLElement;
96+
const pythonLevel = cardElement.getAttribute('data-python-level');
97+
const room = cardElement.getAttribute('data-room');
98+
99+
const levelMatch = !selectedLevel || pythonLevel === selectedLevel;
100+
const roomMatch = !selectedRoom || room === selectedRoom;
101+
102+
if (levelMatch && roomMatch) {
103+
cardElement.style.display = 'block';
104+
visibleCount++;
105+
} else {
106+
cardElement.style.display = 'none';
107+
}
108+
});
109+
110+
if (sprintsCount) {
111+
sprintsCount.textContent = `${visibleCount} active ${visibleCount === 1 ? 'sprint' : 'sprints'}`;
112+
}
113+
}
114+
115+
levelFilter?.addEventListener('change', filterSprints);
116+
roomFilter?.addEventListener('change', filterSprints);
117+
});
118+
</script>
119+
</Layout>

0 commit comments

Comments
 (0)