--- import Base from '../../layouts/Base.astro'; import CalendarEventCard from '../CalendarEventCard.astro'; import LinkedEventCard from '../LinkedEventCard.astro'; import PostCard from '../PostCard.astro'; import { useTranslations, type Lang } from '../../i18n/ui'; import { linkedEvents } from '../../data/linked-events'; import { pastMeetupPosts } from '../../data/meetups'; import { getCalendarEvents } from '../../lib/atproto/events'; import { getScope } from '../../config/scopes'; import { getPosts } from '../../lib/atproto/client'; import type { CalendarEvent } from '../../lib/atproto/events'; import type { LinkedEvent } from '../../data/linked-events'; const { lang } = Astro.props as { lang: Lang }; const t = useTranslations(lang); // Calendar events are country-tagged (see events-account.ts): a national site shows events tagged // with its country, the .eu aggregate shows all. Past meetups are NL photos, so they stay on .nl and // the aggregate only. Other national sites keep the generic organizer guides regardless. const scope = getScope(import.meta.env.PUBLIC_SITE_SCOPE); const cc = scope.aggregate || scope.countries === 'all' ? null : scope.countries[0]; const allEvents = await getCalendarEvents(); const events = scope.aggregate ? allEvents : allEvents.filter((e) => cc != null && e.countries.includes(cc)); // Ecosystem link cards are country-tagged too: a national site shows only its own, the aggregate all. const scopedLinkedEvents = scope.aggregate ? linkedEvents : linkedEvents.filter((e) => cc != null && e.countries.includes(cc)); const showMeetups = scope.aggregate || cc === 'nl'; const meetups = showMeetups ? await getPosts(pastMeetupPosts) : []; // Merge our own calendar events (full cards) with ecosystem events (stripped link cards) // into single Upcoming / Past lists, sorted chronologically. interface Row { sort: number; upcoming: boolean; cal?: CalendarEvent; linked?: LinkedEvent; } const rows: Row[] = [ ...events.map((e) => ({ sort: Date.parse(e.startsAt), cal: e, upcoming: e.upcoming })), ...scopedLinkedEvents.map((e) => ({ sort: Date.parse(e.startsAt), linked: e, upcoming: e.when === 'upcoming' })), ]; const upcomingRows = rows.filter((r) => r.upcoming).sort((a, b) => a.sort - b.sort); const pastRows = rows.filter((r) => !r.upcoming).sort((a, b) => b.sort - a.sort); ---

{t('nav.events')}

{t('events.subtitle')}

{ upcomingRows.length > 0 && (

{t('events.heading')}

{upcomingRows.map((r) => r.cal ? ( ) : ( ), )}
) } { pastRows.length > 0 && (

{t('events.past.events')}

{pastRows.map((r) => r.cal ? ( ) : ( ), )}
) } { meetups.length > 0 && (

{t('events.past.h')}

{meetups.map((p) => )}
) } {/* The organizer guides now live in the EU-wide handbook at docs.atproto.eu (one source for every country), so these cards deep-link out there rather than to local /events/* pages. */}

{t('events.guides.h')}

{t('events.guides.note')}

{t('events.guides.hackathon')} {t('events.guides.hackathon.d')} {t('events.guides.meetup')} {t('events.guides.meetup.d')} {t('events.guides.unconference')} {t('events.guides.unconference.d')}