('documents');
diff --git a/src/lib/events.ts b/src/lib/events.ts
index 5752b85..ba21e46 100644
--- a/src/lib/events.ts
+++ b/src/lib/events.ts
@@ -1,13 +1,18 @@
import type { EventItem } from './fallback-data';
+function isDateOnly(value: string): boolean {
+ return /^\d{4}-\d{2}-\d{2}$/.test(value);
+}
+
function getEventEndTime(event: EventItem): number {
const value = event.end_datetime || event.start_datetime;
- const timestamp = new Date(value).getTime();
+ const timestamp = new Date(isDateOnly(value) ? `${value}T23:59:59` : value).getTime();
return Number.isFinite(timestamp) ? timestamp : 0;
}
function getEventStartTime(event: EventItem): number {
- const timestamp = new Date(event.start_datetime).getTime();
+ const value = event.start_datetime;
+ const timestamp = new Date(isDateOnly(value) ? `${value}T00:00:00` : value).getTime();
return Number.isFinite(timestamp) ? timestamp : 0;
}
diff --git a/src/lib/fallback-data.ts b/src/lib/fallback-data.ts
index 1bc88d6..aec1a3c 100644
--- a/src/lib/fallback-data.ts
+++ b/src/lib/fallback-data.ts
@@ -18,6 +18,7 @@ export type FuelPrice = {
};
export type EventItem = {
+ id?: number | string;
title: string;
slug: string;
summary?: string;
@@ -27,9 +28,12 @@ export type EventItem = {
location_text?: string;
registration_link?: string;
realimage?: string | { id?: string; filename_download?: string; title?: string };
+ logo?: string | { id?: string; filename_download?: string; title?: string };
status?: string;
is_featured?: boolean;
tags?: string[];
+ template_id?: number | string;
+ date_id?: number | string;
};
export type NewsItem = {
diff --git a/src/lib/format.ts b/src/lib/format.ts
index abb040c..3b70ee4 100644
--- a/src/lib/format.ts
+++ b/src/lib/format.ts
@@ -15,6 +15,10 @@ export function formatDateTime(value?: string) {
return 'To be confirmed';
}
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+ return formatDate(value);
+ }
+
return new Intl.DateTimeFormat('en-GB', {
day: '2-digit',
month: 'short',
@@ -39,6 +43,10 @@ export function formatTime(value?: string) {
return 'Time TBC';
}
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+ return '';
+ }
+
return new Intl.DateTimeFormat('en-GB', {
hour: '2-digit',
minute: '2-digit',
diff --git a/src/pages/about/drones.astro b/src/pages/about/drones.astro
index 09ae5b0..875c4c1 100644
--- a/src/pages/about/drones.astro
+++ b/src/pages/about/drones.astro
@@ -13,33 +13,24 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
- You are likely aware of the map adjacent: the purple rectangles show the Runway Protection
- Zones, the purple circle is the Flight Restriction Zone (FRZ), and the red circle is the
- Parachuting Protection Zone.
+ You are likely aware that Swansea Airport has a Flight Restriction Zone (FRZ) and two Runway Protection Zones (RPZs) that are in place to protect the public and aircraft from the risk of an accident.
- We ask all drone operators operating in the vicinity of the aerodrome to let us know with as
- much notice as possible via email, with the following information:
+
- Request a Drone Flight
+ It is a requirement to obtain permission from Swansea Airport before flying a drone within the FRZ or RPZs. This is to ensure that we can coordinate your flight with any other aircraft that may be operating in the area, and to ensure that you are aware of any operational considerations that may affect your flight.
-
- - Date of flight
- - Estimated take-off time
- - Estimated completion time
- - Location
- - Maximum elevation (feet above mean sea level)
- - Details of the Operator and Flyer ID
-
+
+
Request a Drone Flight
+
We will then reply with any operational considerations and, if required, approve or decline a
- flight if it is within the FRZ or RPZs. We are not in the habit of declining, so please email
- us.
+ flight if it is within the FRZ or RPZs. We are not in the habit of declining, so please do submit your request, and we will do our best to accommodate you.
diff --git a/src/pages/events/[slug].astro b/src/pages/events/[slug].astro
index 1f72055..db98bf5 100644
--- a/src/pages/events/[slug].astro
+++ b/src/pages/events/[slug].astro
@@ -1,7 +1,8 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getEvents } from '../../lib/directus';
-import { formatDateTime } from '../../lib/format';
+import { getUpcomingEvents } from '../../lib/events';
+import { formatDate, formatDateTime } from '../../lib/format';
import { normalizeSlug } from '../../lib/slug';
type EventItem = Awaited>[number];
@@ -37,29 +38,48 @@ function resolveEventImageSource(realimage: EventItem['realimage']): string | nu
}
export async function getStaticPaths() {
- const events = await getEvents();
- const paths = new Map();
+ const events = getUpcomingEvents(await getEvents());
+ const paths = new Map();
for (const item of events) {
const slug = normalizeSlug(item.slug);
- if (!slug || paths.has(slug)) continue;
- paths.set(slug, { params: { slug }, props: { item } });
+ if (!slug) continue;
+
+ const existing = paths.get(slug);
+ if (existing) {
+ existing.props.dates.push(item);
+ continue;
+ }
+
+ paths.set(slug, { params: { slug }, props: { item, dates: [item] } });
}
return Array.from(paths.values());
}
-const { item } = Astro.props as { item: EventItem };
+const { item, dates } = Astro.props as { item: EventItem; dates: EventItem[] };
const imageSrc = resolveEventImageSource(item.realimage);
const imageAlt = item.title;
+const hasHtmlDescription = /<[^>]+>/.test(item.description);
---
- {formatDateTime(item.start_datetime)}
+ {dates.length === 1 ? formatDateTime(item.start_datetime) : `${dates.length} upcoming dates`}
{item.title}
{imageSrc && 
}
- {item.description && {item.description}
}
+ {item.description && hasHtmlDescription && }
+ {item.description && !hasHtmlDescription && {item.description}
}
+ {dates.length > 0 && (
+ <>
+ Upcoming dates
+
+ {dates.map((date) => (
+ - {formatDate(date.start_datetime)}
+ ))}
+
+ >
+ )}
{item.location_text && Location: {item.location_text}
}
{item.registration_link && Register
}
diff --git a/src/pages/index.astro b/src/pages/index.astro
index d7f7002..eaf1060 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -18,7 +18,9 @@ const [notices, fuelPrices, events, news, bannerImages] = await Promise.all([
getHomepageBannerImages(),
]);
-const featuredEvents = getUpcomingEvents(events).filter((event) => event.is_featured).slice(0, 3);
+const upcomingEvents = getUpcomingEvents(events);
+const highlightedEvents = upcomingEvents.filter((event) => event.is_featured);
+const featuredEvents = (highlightedEvents.length > 0 ? highlightedEvents : upcomingEvents).slice(0, 3);
const latestNews = news.slice(0, 3);
const businessPromos = [
diff --git a/src/styles/global.css b/src/styles/global.css
index 8fb35e8..454184b 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -680,6 +680,18 @@ section {
background: linear-gradient(180deg, rgba(11, 79, 122, 0.1), rgba(29, 118, 184, 0.06));
}
+.event-logo {
+ width: 100%;
+ height: 3.6rem;
+ object-fit: contain;
+ margin-top: 0.85rem;
+ padding: 0.45rem;
+ border: 1px solid rgba(16, 34, 51, 0.1);
+ border-radius: 0.65rem;
+ background: rgba(255, 255, 255, 0.82);
+ box-shadow: 0 8px 18px rgba(16, 34, 51, 0.08);
+}
+
.event-weekday,
.event-date,
.event-time {