diff --git a/.env.development b/.env.development
index 6792a8ba4..c41c4c5c6 100644
--- a/.env.development
+++ b/.env.development
@@ -1,2 +1,3 @@
EP_SESSIONS_API="https://static.europython.eu/programme/ep2025/releases/current/sessions.json"
EP_SPEAKERS_API="https://static.europython.eu/programme/ep2025/releases/current/speakers.json"
+EP_SCHEDULE_API="https://static.europython.eu/programme/ep2025/releases/current/schedule.json"
diff --git a/.env.preview b/.env.preview
index 91bc3a8bc..c41c4c5c6 100644
--- a/.env.preview
+++ b/.env.preview
@@ -1,3 +1,3 @@
-EP_SESSIONS_API="https://static.europython.eu/programme/ep2024/releases/current/sessions.json"
-EP_SPEAKERS_API="https://gist.githubusercontent.com/nikoshell/24f8c99de3758dc4ed1391876c382b43/raw/bdf11fe89e5010f35dc1351e9b71c02a19adefe2/24speakers.json"
-EP_SCHEDULE_API="https://static.europython.eu/programme/ep2024/releases/current/schedule.json"
+EP_SESSIONS_API="https://static.europython.eu/programme/ep2025/releases/current/sessions.json"
+EP_SPEAKERS_API="https://static.europython.eu/programme/ep2025/releases/current/speakers.json"
+EP_SCHEDULE_API="https://static.europython.eu/programme/ep2025/releases/current/schedule.json"
diff --git a/.env.production b/.env.production
index 6792a8ba4..c41c4c5c6 100644
--- a/.env.production
+++ b/.env.production
@@ -1,2 +1,3 @@
EP_SESSIONS_API="https://static.europython.eu/programme/ep2025/releases/current/sessions.json"
EP_SPEAKERS_API="https://static.europython.eu/programme/ep2025/releases/current/speakers.json"
+EP_SCHEDULE_API="https://static.europython.eu/programme/ep2025/releases/current/schedule.json"
diff --git a/package.json b/package.json
index ecfd184f5..2f6685d2d 100644
--- a/package.json
+++ b/package.json
@@ -16,21 +16,21 @@
"@astrojs/mdx": "^4.2.3",
"@astrojs/react": "^4.2.3",
"@astrojs/sitemap": "^3.3.0",
- "@astrojs/tailwind": "^5.1.4",
- "@fontsource-variable/inter": "^5.1.1",
+ "@astrojs/tailwind": "^5.1.5",
+ "@fontsource-variable/inter": "^5.2.5",
"@fortawesome/fontawesome-free": "^6.7.2",
"@tailwindcss/typography": "^0.5.16",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",
- "astro": "^5.1.6",
+ "astro": "^5.5.2",
"astro-delete-unused-images": "^1.0.3",
"astro-meta-tags": "^0.3.1",
- "astro-pagefind": "^1.8.1",
+ "astro-pagefind": "^1.8.3",
"astro-preload": "^1.1.2",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
- "hastscript": "^9.0.0",
+ "hastscript": "^9.0.1",
"js-yaml": "^4.1.0",
"marked": "^15.0.7",
"pagefind": "^1.3.0",
@@ -45,7 +45,7 @@
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
- "prettier": "^3.4.2",
+ "prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1"
},
"prettier": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 15eb290db..082bf68ee 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -24,10 +24,10 @@ importers:
specifier: ^3.3.0
version: 3.3.0
'@astrojs/tailwind':
- specifier: ^5.1.4
+ specifier: ^5.1.5
version: 5.1.5(astro@5.5.2(jiti@1.21.7)(rollup@4.39.0)(typescript@5.8.3)(yaml@2.7.0))(tailwindcss@3.4.17)
'@fontsource-variable/inter':
- specifier: ^5.1.1
+ specifier: ^5.2.5
version: 5.2.5
'@fortawesome/fontawesome-free':
specifier: ^6.7.2
@@ -42,7 +42,7 @@ importers:
specifier: ^19.1.1
version: 19.1.1(@types/react@19.1.0)
astro:
- specifier: ^5.1.6
+ specifier: ^5.5.2
version: 5.5.2(jiti@1.21.7)(rollup@4.39.0)(typescript@5.8.3)(yaml@2.7.0)
astro-delete-unused-images:
specifier: ^1.0.3
@@ -51,7 +51,7 @@ importers:
specifier: ^0.3.1
version: 0.3.1(astro@5.5.2(jiti@1.21.7)(rollup@4.39.0)(typescript@5.8.3)(yaml@2.7.0))
astro-pagefind:
- specifier: ^1.8.1
+ specifier: ^1.8.3
version: 1.8.3(astro@5.5.2(jiti@1.21.7)(rollup@4.39.0)(typescript@5.8.3)(yaml@2.7.0))
astro-preload:
specifier: ^1.1.2
@@ -66,7 +66,7 @@ importers:
specifier: ^3.2.0
version: 3.2.0(date-fns@4.1.0)
hastscript:
- specifier: ^9.0.0
+ specifier: ^9.0.1
version: 9.0.1
js-yaml:
specifier: ^4.1.0
@@ -106,7 +106,7 @@ importers:
specifier: ^4.0.9
version: 4.0.9
prettier:
- specifier: ^3.4.2
+ specifier: ^3.5.3
version: 3.5.3
prettier-plugin-astro:
specifier: ^0.14.1
diff --git a/src/components/schedule/break.astro b/src/components/schedule/break.astro
index affbcd255..3da93b677 100644
--- a/src/components/schedule/break.astro
+++ b/src/components/schedule/break.astro
@@ -8,18 +8,19 @@ export interface Props {
const { time, title, style }: Props = Astro.props;
---
-
+
{time}{" "}
{title}
+ {time} {title}
diff --git a/src/pages/schedule/[day].astro b/src/components/schedule/day.astro
similarity index 85%
rename from src/pages/schedule/[day].astro
rename to src/components/schedule/day.astro
index c909548e7..e60260705 100644
--- a/src/pages/schedule/[day].astro
+++ b/src/components/schedule/day.astro
@@ -1,13 +1,20 @@
+
---
-import { getEntry, getCollection } from "astro:content";
-import Layout from "../../layouts/Layout.astro";
-import Break from "../../components/schedule/break.astro";
-import Session from "../../components/schedule/session.astro";
+import { type CollectionEntry, getEntry, getCollection } from "astro:content";
+import Break from "@components/schedule/break.astro";
+import Session from "@components/schedule/session.astro";
import { addMinutes, differenceInMinutes, parseISO } from "date-fns";
-import { Title } from "../../components/typography/title";
-import { Select } from "../../components/form/select";
+//import { Title } from "@components/typography/title";
+import Headline from "@components/ui/Headline.astro";
+import Button from "@ui/Button.astro";
+import { Select } from "@components/form/select";
import { formatInTimeZone } from "date-fns-tz";
+
+interface Props {
+ day: CollectionEntry<"days">;
+}
+
const format = (date: Date, format: string) => {
return formatInTimeZone(date, "Europe/Prague", format);
};
@@ -31,10 +38,12 @@ export const getStaticPaths = async () => {
});
};
-const days = await getCollection("days");
-const day = await getEntry("days", Astro.params.day);
-const ROOMS = (day?.data.rooms ?? [])
+const { day } = Astro.props;
+const dayName = day.id;
+//const day = getEntry("days", dayName);
+
+const ROOMS = (day.data.rooms ?? [])
.filter((room) => room.toLowerCase() !== "exhibit hall")
.sort();
@@ -53,7 +62,7 @@ type ScheduleSession = {
// hack for posters :)
const posters: Array
= [];
-const sessions = day?.data.events
+const sessions = day.data.events
.map((event) => {
const start = parseISO(event.start);
const startTime = format(start, "HH:mm");
@@ -212,7 +221,7 @@ for (let i = 0; i < slots.length; i++) {
const sizeInMinutes = nextTime - currentTime;
let size = sizeInMinutes / 5;
- let sizeValue = `repeat(${size}, fit-content(200px))`;
+ let sizeValue = `repeat(${size}, fit-content(100px))`;
if (current.type === "break") {
// we might have some "breaks" that are quite short, for example
@@ -310,39 +319,25 @@ posters.forEach((poster) => {
postersByTime[poster.startTime].push(poster);
});
+
+
+const date = parseISO(dayName);
+const dateText = format(date, "eeee - do MMMM");
---
-
-
-
Schedule
-
-
-
+
-
+
+
-
+
+
+
diff --git a/src/components/schedule/speakers.astro b/src/components/schedule/speakers.astro
index 33e851e80..fdee5ce05 100644
--- a/src/components/schedule/speakers.astro
+++ b/src/components/schedule/speakers.astro
@@ -31,4 +31,8 @@ const speakers = Astro.props.speakers
a:hover {
text-decoration: underline;
}
+
+ a {
+ font-weight: 500;
+ }
diff --git a/src/components/ui/Headline.astro b/src/components/ui/Headline.astro
index 3940a768e..8fa66493d 100644
--- a/src/components/ui/Headline.astro
+++ b/src/components/ui/Headline.astro
@@ -8,7 +8,7 @@ const isCenter = center ? "text-center" : "";
---
-
+
{isAnchor && (
{Title}
diff --git a/src/content/config.ts b/src/content/config.ts
index 58ca12181..45ad98f54 100644
--- a/src/content/config.ts
+++ b/src/content/config.ts
@@ -1,5 +1,5 @@
import { defineCollection, reference, z } from "astro:content";
-import { loadData } from "../utils/dataLoader";
+import { loadData } from "@utils/dataLoader";
const mode = import.meta.env.MODE;
console.log(`\x1b[35m[EP]\x1b[0m Current MODE: \x1b[1m\x1b[34m${mode}\x1b[0m`);
@@ -159,17 +159,29 @@ const sessions = defineCollection({
});
const days = defineCollection({
- type: "data",
+ loader: async (): Promise => {
+ const schedule = await loadData(import.meta.env.EP_SCHEDULE_API);
+
+ if (Object.keys(schedule).length === 0) {
+ return schedule;
+ }
+ return Object.entries(schedule.days).map(([date, data]: [string, any]) => ({
+ id: date,
+ ...data,
+ }));
+ },
schema: z.object({
- rooms: z.array(z.string()),
+ id: z.string(),
+ rooms: z.array(z.string()).optional(),
events: z.array(
z.object({
- rooms: z.array(z.string()),
- event_type: z.string(),
code: z.string().optional(),
- title: z.string(),
+ duration: z.number(),
+ event_type: z.string(),
+ level: z.string().optional().nullable(),
+ rooms: z.array(z.string()),
+ session_type: z.string().optional(),
slug: z.string().optional(),
- session_type: z.string().optional(), // why?
speakers: z
.array(
z.object({
@@ -179,11 +191,11 @@ const days = defineCollection({
})
)
.optional(),
- tweet: z.string().optional().nullable(),
- level: z.string().optional().nullable(),
start: z.string(),
+ title: z.string(),
+ track: z.string().optional().nullable(),
+ tweet: z.string().optional().nullable(),
website_url: z.string().optional().nullable(),
- duration: z.number(),
})
),
}),
diff --git a/src/data/links.json b/src/data/links.json
index d95c6703e..5477b6471 100644
--- a/src/data/links.json
+++ b/src/data/links.json
@@ -54,6 +54,19 @@
}
]
},
+ {
+ "name": "Schedule",
+ "items": [
+ {
+ "name": "Tutorials",
+ "path": "/schedule/tutorials"
+ },
+ {
+ "name": "Talks",
+ "path": "/schedule/talks"
+ }
+ ]
+ },
{
"name": "Prague & Venue",
"items": [
diff --git a/src/env.d.ts b/src/env.d.ts
index ccf023d42..107ecdf18 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -4,6 +4,7 @@
interface ImportMetaEnv {
readonly EP_SESSIONS_API: string;
readonly EP_SPEAKERS_API: string;
+ readonly EP_SCHEDULE_API: string;
}
interface ImportMeta {
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index fe4b0a03e..9040dab23 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -1,4 +1,5 @@
---
+import BaseLayout from "@layouts/BaseLayout.astro";
import BaseHead from "@components/BaseHead.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
@@ -16,14 +17,18 @@ export interface Props {
}
const { title, description } = Astro.props;
+
+if (!title || !description) {
+throw new Error(`${Astro.url.pathname} Both 'title' and 'description' must be set.`);
+}
---
-
-
-
+
+
-
+
+
Skip to main content
@@ -37,4 +42,4 @@ const { title, description } = Astro.props;
-
+
diff --git a/src/layouts/ScheduleLayout.astro b/src/layouts/ScheduleLayout.astro
new file mode 100644
index 000000000..793525a1f
--- /dev/null
+++ b/src/layouts/ScheduleLayout.astro
@@ -0,0 +1,124 @@
+---
+import Layout from "@layouts/Layout.astro";
+import Headline from "@ui/Headline.astro";
+
+export interface Props {
+ title?: string;
+ description: string;
+}
+
+const { title, description } = Astro.props;
+---
+
+
+
+
+
+ <>
+
+
+
+
+ >
+
+
+
+
+
+
+
diff --git a/src/pages/schedule.astro b/src/pages/schedule.astro
new file mode 100644
index 000000000..819508eb6
--- /dev/null
+++ b/src/pages/schedule.astro
@@ -0,0 +1,29 @@
+---
+import { getEntry, getCollection } from "astro:content";
+import Layout from "@layouts/ScheduleLayout.astro";
+import ScheduleDay from "@components/schedule/day.astro";
+
+interface Props {
+ dayName: string;
+}
+
+export const getStaticPaths = async () => {
+ const days = await getCollection("days");
+
+ return days.map((day) => {
+ return { params: { day: day.id } };
+ });
+};
+
+const days = await getCollection("days");
+
+---
+
+
+
+ {
+ days.map((day) => (
+
+ ))
+ }
+
diff --git a/src/pages/schedule/day/[day].astro b/src/pages/schedule/day/[day].astro
new file mode 100644
index 000000000..d0eac6358
--- /dev/null
+++ b/src/pages/schedule/day/[day].astro
@@ -0,0 +1,27 @@
+---
+import { type CollectionEntry, getEntry, getCollection } from "astro:content";
+import Layout from "@layouts/ScheduleLayout.astro";
+import ScheduleDay from "@components/schedule/day.astro";
+
+export const getStaticPaths = async () => {
+ const days = await getCollection("days");
+
+ return days.map((day) => {
+ return { params: { day: day.id } };
+ });
+};
+
+
+type Day = CollectionEntry<"days">;
+const dayEntry = await getEntry("days", Astro.params.day);
+
+if (!dayEntry) {
+ throw new Error(`Day entry "${Astro.params.day}" not found`);
+}
+
+const day: Day = dayEntry;
+---
+
+
+
+
diff --git a/src/pages/schedule/talks.astro b/src/pages/schedule/talks.astro
new file mode 100644
index 000000000..4956cf6ae
--- /dev/null
+++ b/src/pages/schedule/talks.astro
@@ -0,0 +1,37 @@
+---
+import { type CollectionEntry, getCollection } from "astro:content";
+import Layout from "@layouts/ScheduleLayout.astro";
+import ScheduleDay from "@components/schedule/day.astro";
+import { slugify } from '@utils/content';
+
+export const getStaticPaths = async () => {
+ const sessions = await getCollection("sessions");
+
+ const allTypes = Array.from(
+ new Set(
+ sessions
+ .map((session) => slugify(session.data.session_type))
+ .filter((type) => type)
+ )
+ ).sort();
+ return allTypes.map((type) => {
+ return { params: { type } };
+ });
+};
+
+const days = await getCollection("days");
+
+---
+
+
+ {
+ days
+ .filter((day) => {
+ const dayOfWeek = new Date(day.id).getDay();
+ return dayOfWeek !== 1 && dayOfWeek !== 2;
+ })
+ .map((day) => (
+
+ ))
+ }
+
diff --git a/src/pages/schedule/tutorials.astro b/src/pages/schedule/tutorials.astro
new file mode 100644
index 000000000..34a6b9cd8
--- /dev/null
+++ b/src/pages/schedule/tutorials.astro
@@ -0,0 +1,38 @@
+---
+import { getCollection } from "astro:content";
+import Layout from "@layouts/ScheduleLayout.astro";
+import ScheduleDay from "@components/schedule/day.astro";
+import { slugify } from '@utils/content';
+import Headline from "@ui/Headline.astro"
+
+export const getStaticPaths = async () => {
+ const sessions = await getCollection("sessions");
+
+ const allTypes = Array.from(
+ new Set(
+ sessions
+ .map((session) => slugify(session.data.session_type))
+ .filter((type) => type)
+ )
+ ).sort();
+ return allTypes.map((type) => {
+ return { params: { type } };
+ });
+};
+
+const days = await getCollection("days");
+
+---
+
+
+ {
+ days
+ .filter((day) => {
+ const dayOfWeek = new Date(day.id).getDay();
+ return dayOfWeek == 1 || dayOfWeek == 2;
+ })
+ .map((day) => (
+
+ ))
+ }
+
diff --git a/src/pages/test_components.astro b/src/pages/test_components.astro
index 738795d55..62ec1906a 100644
--- a/src/pages/test_components.astro
+++ b/src/pages/test_components.astro
@@ -7,8 +7,8 @@ import Icon from "@ui/Icon.astro"
---
diff --git a/src/utils/content.ts b/src/utils/content.ts
new file mode 100644
index 000000000..9a7c964a7
--- /dev/null
+++ b/src/utils/content.ts
@@ -0,0 +1,10 @@
+export function slugify(text) {
+ return text
+ .toString()
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, "-") // Replace spaces with -
+ .replace(/&/g, "-and-") // Replace & with 'and'
+ .replace(/[^\w\-]+/g, "") // Remove all non-word chars
+ .replace(/\-\-+/g, "-"); // Replace multiple - with single -
+}
diff --git a/src/utils/dataLoader.ts b/src/utils/dataLoader.ts
index 9b7147cdf..53f639d53 100644
--- a/src/utils/dataLoader.ts
+++ b/src/utils/dataLoader.ts
@@ -1,4 +1,3 @@
-const mode = import.meta.env.MODE;
export async function loadData(apiUrl: any) {
if (!apiUrl) {
console.warn(`No API URL provided`);