@@ -0,0 +1,84 @@
|
||||
import {
|
||||
fallbackContacts,
|
||||
fallbackDocuments,
|
||||
fallbackEvents,
|
||||
fallbackFuelPrices,
|
||||
fallbackNews,
|
||||
fallbackNotices,
|
||||
type ContactItem,
|
||||
type DocumentItem,
|
||||
type EventItem,
|
||||
type FuelPrice,
|
||||
type NewsItem,
|
||||
type Notice,
|
||||
} from './fallback-data';
|
||||
|
||||
type CollectionName = 'news' | 'events' | 'notices' | 'fuel_prices' | 'documents' | 'contacts';
|
||||
|
||||
const defaultSortByCollection: Partial<Record<CollectionName, string>> = {
|
||||
news: '-publish_date',
|
||||
events: '-start_datetime',
|
||||
notices: '-priority',
|
||||
fuel_prices: '-last_updated',
|
||||
documents: '-uploaded_at',
|
||||
contacts: 'order',
|
||||
};
|
||||
|
||||
declare const process: {
|
||||
env: Record<string, string | undefined>;
|
||||
};
|
||||
|
||||
const directusUrl = process.env.DIRECTUS_URL ?? 'http://directus:8055';
|
||||
const directusToken = process.env.DIRECTUS_ADMIN_TOKEN;
|
||||
|
||||
async function readCollection<T>(collection: CollectionName): Promise<T[]> {
|
||||
const endpoint = new URL(`/items/${collection}`, directusUrl);
|
||||
endpoint.searchParams.set('limit', '100');
|
||||
const sort = defaultSortByCollection[collection];
|
||||
if (sort) {
|
||||
endpoint.searchParams.set('sort', sort);
|
||||
}
|
||||
|
||||
try {
|
||||
const headers: Record<string, string> = {};
|
||||
if (directusToken) {
|
||||
headers['Authorization'] = `Bearer ${directusToken}`;
|
||||
}
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Directus responded with ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { data?: T[] };
|
||||
return payload.data ?? [];
|
||||
} catch {
|
||||
return fallbackFor(collection) as T[];
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackFor(collection: CollectionName) {
|
||||
switch (collection) {
|
||||
case 'news':
|
||||
return fallbackNews;
|
||||
case 'events':
|
||||
return fallbackEvents;
|
||||
case 'notices':
|
||||
return fallbackNotices;
|
||||
case 'fuel_prices':
|
||||
return fallbackFuelPrices;
|
||||
case 'documents':
|
||||
return fallbackDocuments;
|
||||
case 'contacts':
|
||||
return fallbackContacts;
|
||||
}
|
||||
}
|
||||
|
||||
export const getNews = () => readCollection<NewsItem>('news');
|
||||
export const getEvents = () => readCollection<EventItem>('events');
|
||||
export const getNotices = () => readCollection<Notice>('notices');
|
||||
export const getFuelPrices = () => readCollection<FuelPrice>('fuel_prices');
|
||||
export const getDocuments = () => readCollection<DocumentItem>('documents');
|
||||
export const getContacts = () => readCollection<ContactItem>('contacts');
|
||||
@@ -0,0 +1,121 @@
|
||||
export type Notice = {
|
||||
title: string;
|
||||
message: string;
|
||||
severity: 'info' | 'warning' | 'critical';
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
active?: boolean;
|
||||
priority?: number;
|
||||
};
|
||||
|
||||
export type FuelPrice = {
|
||||
fuel_type: string;
|
||||
price_per_litre: number;
|
||||
currency: string;
|
||||
last_updated: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
export type EventItem = {
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
start_datetime: string;
|
||||
end_datetime?: string;
|
||||
location_text?: string;
|
||||
registration_link?: string;
|
||||
status?: string;
|
||||
is_featured?: boolean;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
export type NewsItem = {
|
||||
title: string;
|
||||
slug: string;
|
||||
summary: string;
|
||||
body: string;
|
||||
publish_date: string;
|
||||
status?: string;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
export type DocumentItem = {
|
||||
title: string;
|
||||
category: string;
|
||||
description?: string;
|
||||
fileUrl?: string;
|
||||
uploaded_at?: string;
|
||||
};
|
||||
|
||||
export type ContactItem = {
|
||||
name: string;
|
||||
role: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
is_public?: boolean;
|
||||
order?: number;
|
||||
};
|
||||
|
||||
export const fallbackNotices: Notice[] = [
|
||||
{
|
||||
title: 'Welcome to Swansea Airport',
|
||||
message: 'Operational notices and visitor information will appear here once Directus content is published.',
|
||||
severity: 'info',
|
||||
active: true,
|
||||
priority: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackFuelPrices: FuelPrice[] = [
|
||||
{
|
||||
fuel_type: 'AVGAS',
|
||||
price_per_litre: 2.35,
|
||||
currency: 'GBP',
|
||||
last_updated: '2026-05-11',
|
||||
notes: 'Placeholder rate for the initial scaffold.',
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackEvents: EventItem[] = [
|
||||
{
|
||||
title: 'Airfield open day',
|
||||
slug: 'airfield-open-day',
|
||||
description: 'Example event to verify the listing and detail page flow.',
|
||||
start_datetime: '2026-06-14T09:00:00Z',
|
||||
end_datetime: '2026-06-14T16:00:00Z',
|
||||
location_text: 'Main apron',
|
||||
is_featured: true,
|
||||
tags: ['Public'],
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackNews: NewsItem[] = [
|
||||
{
|
||||
title: 'Site scaffolding started',
|
||||
slug: 'site-scaffolding-started',
|
||||
summary: 'The new Astro and Directus architecture has been scaffolded.',
|
||||
body: '<p>This is a starter article that proves the detail route and rich text rendering.</p>',
|
||||
publish_date: '2026-05-11',
|
||||
tags: ['Website'],
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackDocuments: DocumentItem[] = [
|
||||
{
|
||||
title: 'Pilot information pack',
|
||||
category: 'Pilots',
|
||||
description: 'Starter document entry for the documents listing.',
|
||||
uploaded_at: '2026-05-11',
|
||||
},
|
||||
];
|
||||
|
||||
export const fallbackContacts: ContactItem[] = [
|
||||
{
|
||||
name: 'Airport office',
|
||||
role: 'General enquiries',
|
||||
email: 'info@swansea-airport.wales',
|
||||
phone: '01792 687 042',
|
||||
is_public: true,
|
||||
order: 1,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
export function formatDate(value?: string) {
|
||||
if (!value) {
|
||||
return 'To be confirmed';
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
}).format(new Date(value));
|
||||
}
|
||||
|
||||
export function formatDateTime(value?: string) {
|
||||
if (!value) {
|
||||
return 'To be confirmed';
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(new Date(value));
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
export const site = {
|
||||
name: 'Swansea Airport',
|
||||
tagline: 'The gateway to Gower and Swansea',
|
||||
address: 'Swansea Airport, Fairwood Common, Swansea, SA2 7JU',
|
||||
phone: '01792 687 042',
|
||||
openingHours: '7 days 0900-1600',
|
||||
licensedHours: 'Friday to Sunday 0900-1700',
|
||||
runwayFacts: [
|
||||
'Runway 04/22 concrete 1351m x 30m licensed',
|
||||
'Runway 10/28 asphalt 857m x 18m unlicensed',
|
||||
'Category 1 RFFS',
|
||||
'Air Ground Service 119.705',
|
||||
],
|
||||
navigation: [
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Visiting Pilots', href: '/visiting-pilots/' },
|
||||
{ label: 'Procedures', href: '/procedures-safety-noise-abatement/' },
|
||||
{ label: 'Events', href: '/events/' },
|
||||
{ label: 'News', href: '/news/' },
|
||||
{ label: 'Documents', href: '/documents/' },
|
||||
{ label: 'Contact', href: '/contact/' },
|
||||
],
|
||||
};
|
||||
|
||||
export const homepageHighlights = [
|
||||
{
|
||||
title: 'Operational clarity first',
|
||||
body: 'Notices, fuel, events, and the latest news are kept at the top of the page for fast scanning on mobile.',
|
||||
},
|
||||
{
|
||||
title: 'Layout controlled in code',
|
||||
body: 'Astro owns the structure, spacing, and information hierarchy so the CMS only supplies content.',
|
||||
},
|
||||
{
|
||||
title: 'Static output by default',
|
||||
body: 'The public site remains available from built files even if Directus is offline after publication.',
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user