Shipping
@@ -257,7 +257,7 @@ export default function CheckoutPage() {
Total
- £{subtotal.toLocaleString()}
+ ${subtotal.toLocaleString()}
+ shipping (calculated at next step)
diff --git a/frontend/src/app/events/page.tsx b/frontend/src/app/events/page.tsx
index 7dccaab..487437d 100644
--- a/frontend/src/app/events/page.tsx
+++ b/frontend/src/app/events/page.tsx
@@ -12,13 +12,9 @@ export const revalidate = 60;
async function getEventsList(): Promise<{ upcoming: Event[]; past: Event[] }> {
try {
- const events = await getEvents({ status: 'published' });
- const now = new Date();
-
- const upcoming = events.filter(
- (e) => !e.end_date || new Date(e.end_date) >= now
- );
- const past = events.filter((e) => e.end_date && new Date(e.end_date) < now);
+ const allEvents = await getEvents();
+ const upcoming = allEvents.filter((e) => e.status === 'published');
+ const past = allEvents.filter((e) => e.status === 'past');
return { upcoming, past };
} catch (error) {
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 4b4fea8..f0dd61a 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
import Image from 'next/image';
-import { getArtworks, Artwork } from '@/lib/directus';
+import { getArtworks, getEvents, Artwork, Event } from '@/lib/directus';
import { ArtworkCard } from '@/components/artwork-card';
import { WisdomWordsCarousel } from '@/components/wisdom-words-carousel';
@@ -8,16 +8,41 @@ export const revalidate = 60;
async function getFeaturedArtworks(): Promise {
try {
- const artworks = await getArtworks({ status: 'published', limit: 6 });
- return artworks;
+ // Fetch more than needed and prefer artworks with images
+ const artworks = await getArtworks({ status: 'published', limit: 50 });
+ const withImages = artworks.filter((a) => a.image);
+ // Return up to 6 artworks that have images, or fall back to whatever is available
+ return withImages.length >= 6 ? withImages.slice(0, 6) : artworks.slice(0, 6);
} catch (error) {
console.error('Error fetching artworks:', error);
return [];
}
}
+async function getUpcomingEvents(): Promise {
+ try {
+ const events = await getEvents({ status: 'published', limit: 4 });
+ return events;
+ } catch (error) {
+ console.error('Error fetching events:', error);
+ return [];
+ }
+}
+
+function formatEventDate(start?: string, end?: string): string {
+ if (!start) return '';
+ const s = new Date(start);
+ const opts: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' };
+ if (!end) return s.toLocaleDateString('en-GB', opts);
+ const e = new Date(end);
+ return `${s.toLocaleDateString('en-GB', opts)} - ${e.toLocaleDateString('en-GB', opts)}`;
+}
+
export default async function HomePage() {
- const artworks = await getFeaturedArtworks();
+ const [artworks, upcomingEvents] = await Promise.all([
+ getFeaturedArtworks(),
+ getUpcomingEvents(),
+ ]);
// Wisdom words gallery - all quote images from original site
const wisdomImages = [
@@ -305,41 +330,27 @@ export default async function HomePage() {
- {/* Event 1 */}
-
-
Feb 2, 2026
-
Secret Screening
-
- An exclusive preview event for the creative community.
-
-
- Learn More
-
-
-
- {/* Event 2 */}
-
-
Apr 4-11, 2026
-
UnEarthing Templer Way at Birdwood House
-
- A week-long creative retreat exploring art and nature.
-
-
- Learn More
-
-
-
- {/* Event 3 */}
-
-
Apr 16-23, 2026
-
UNEARTHING TEMPLER WAY EXHIBITION
-
- Exhibition showcasing works created during the retreat.
-
-
- Learn More
-
-
+ {upcomingEvents.length > 0 ? (
+ upcomingEvents.map((event, index) => (
+
+
+ {formatEventDate(event.start_date, event.end_date)}
+
+
{event.title}
+ {event.location && (
+
{event.location}
+ )}
+ {event.description && (
+
{event.description}
+ )}
+
+ Learn More
+
+
+ ))
+ ) : (
+
No upcoming events at the moment.
+ )}
diff --git a/frontend/src/app/store/[slug]/page.tsx b/frontend/src/app/store/[slug]/page.tsx
index 656e864..e00fb95 100644
--- a/frontend/src/app/store/[slug]/page.tsx
+++ b/frontend/src/app/store/[slug]/page.tsx
@@ -164,7 +164,7 @@ export default function ArtworkDetailPage() {
{isSold ? (
Sold
) : artwork.price ? (
-
£{artwork.price.toLocaleString()}
+
{artwork.currency === 'GBP' ? '£' : '$'}{artwork.price.toLocaleString()}
) : (
Price on request
)}
@@ -279,7 +279,7 @@ export default function ArtworkDetailPage() {
{work.title}
{work.price && (
-
£{work.price.toLocaleString()}
+
{work.currency === 'GBP' ? '£' : '$'}{work.price.toLocaleString()}
)}
diff --git a/frontend/src/components/artwork-card.tsx b/frontend/src/components/artwork-card.tsx
index 0637bf5..b542de6 100644
--- a/frontend/src/components/artwork-card.tsx
+++ b/frontend/src/components/artwork-card.tsx
@@ -85,7 +85,9 @@ export function ArtworkCard({
{isSold ? (
Sold
) : artwork.price ? (
-
£{artwork.price.toLocaleString()}
+
+ {artwork.currency === 'GBP' ? '£' : '$'}{artwork.price.toLocaleString()}
+
) : (
Price on request
)}
diff --git a/frontend/src/components/cart-drawer.tsx b/frontend/src/components/cart-drawer.tsx
index baf1319..e394ad1 100644
--- a/frontend/src/components/cart-drawer.tsx
+++ b/frontend/src/components/cart-drawer.tsx
@@ -84,7 +84,7 @@ export function CartDrawer() {
)}
- £{item.price.toLocaleString()}
+ {item.artwork.currency === 'GBP' ? '£' : '$'}{item.price.toLocaleString()}
Subtotal
- £{subtotal.toLocaleString()}
+ ${subtotal.toLocaleString()}
Shipping and taxes calculated at checkout
diff --git a/frontend/src/lib/directus.ts b/frontend/src/lib/directus.ts
index 80088ff..842da5a 100644
--- a/frontend/src/lib/directus.ts
+++ b/frontend/src/lib/directus.ts
@@ -11,6 +11,7 @@ export interface Artwork {
medium?: string;
dimensions?: string;
price?: number;
+ currency?: 'GBP' | 'USD';
description?: string;
image?: string | DirectusFile;
gallery?: string[];
@@ -153,23 +154,45 @@ export const directus = createDirectus(directusUrl)
.with(staticToken(apiToken))
.with(rest());
-// Helper to get asset URL
+// Helper to get asset URL (includes auth token since Directus assets require authentication)
export function getAssetUrl(fileId: string | DirectusFile | undefined, options?: { width?: number; height?: number; quality?: number; format?: 'webp' | 'jpg' | 'png' }): string {
if (!fileId) return '/placeholder.jpg';
const id = typeof fileId === 'string' ? fileId : fileId.id;
- let url = `${directusUrl}/assets/${id}`;
+ const params = new URLSearchParams();
+
+ // Auth token required for asset access
+ if (apiToken) params.append('access_token', apiToken);
if (options) {
- const params = new URLSearchParams();
if (options.width) params.append('width', options.width.toString());
if (options.height) params.append('height', options.height.toString());
if (options.quality) params.append('quality', options.quality.toString());
if (options.format) params.append('format', options.format);
- if (params.toString()) url += `?${params.toString()}`;
}
- return url;
+ return `${directusUrl}/assets/${id}?${params.toString()}`;
+}
+
+// Map Directus artwork fields to frontend Artwork interface
+// Directus has: name, price_gbp, price_usd, notes, creation_date
+// Frontend expects: title, price, description, slug, year
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function mapArtwork(raw: any): Artwork {
+ const gbp = parseFloat(raw.price_gbp);
+ const usd = parseFloat(raw.price_usd);
+ const hasGbp = !isNaN(gbp) && gbp > 0;
+ const hasUsd = !isNaN(usd) && usd > 0;
+
+ return {
+ ...raw,
+ title: raw.name || raw.title || '',
+ price: hasGbp ? gbp : hasUsd ? usd : (raw.price || 0),
+ currency: hasGbp ? 'GBP' : 'USD',
+ description: raw.notes || raw.description || '',
+ slug: raw.slug || String(raw.id),
+ year: raw.creation_date ? new Date(raw.creation_date).getFullYear() : raw.year,
+ };
}
// API functions with explicit return types
@@ -188,30 +211,33 @@ export async function getArtworks(options?: {
filter: Object.keys(filter).length > 0 ? filter : undefined,
limit: options?.limit || -1,
sort: ['-date_created'],
- fields: options?.fields || ['*', { series: ['id', 'name', 'slug'] }],
+ fields: options?.fields || ['*'],
})
);
- return result as Artwork[];
+ return (result as Artwork[]).map(mapArtwork);
}
export async function getArtwork(idOrSlug: string): Promise {
- // Try to find by slug first, then by ID
- const bySlug = await directus.request(
- readItems('artworks', {
- filter: { slug: { _eq: idOrSlug } },
- limit: 1,
- fields: ['*', { series: ['*'] }],
- })
- );
-
- if (bySlug.length > 0) return bySlug[0] as Artwork;
-
- const result = await directus.request(
- readItem('artworks', idOrSlug, {
- fields: ['*', { series: ['*'] }],
- })
- );
- return result as Artwork;
+ // Try by ID first (since Directus artworks use numeric IDs as slugs)
+ try {
+ const result = await directus.request(
+ readItem('artworks', idOrSlug, {
+ fields: ['*'],
+ })
+ );
+ return mapArtwork(result);
+ } catch {
+ // Fall back to slug search
+ const bySlug = await directus.request(
+ readItems('artworks', {
+ filter: { slug: { _eq: idOrSlug } },
+ limit: 1,
+ fields: ['*'],
+ })
+ );
+ if (bySlug.length > 0) return mapArtwork(bySlug[0]);
+ throw new Error(`Artwork not found: ${idOrSlug}`);
+ }
}
export async function getSeries(options?: { limit?: number }): Promise {
@@ -244,18 +270,24 @@ export async function getSeriesItem(idOrSlug: string): Promise {
}
export async function getEvents(options?: { status?: string; limit?: number }): Promise {
- const filter: Record = {};
- if (options?.status) filter.status = { _eq: options.status };
+ try {
+ const filter: Record = {};
+ if (options?.status) filter.status = { _eq: options.status };
- const result = await directus.request(
- readItems('events', {
- filter: Object.keys(filter).length > 0 ? filter : undefined,
- limit: options?.limit || -1,
- sort: ['-start_date'],
- fields: ['*'],
- })
- );
- return result as Event[];
+ const result = await directus.request(
+ readItems('events', {
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
+ limit: options?.limit || -1,
+ sort: ['-start_date'],
+ fields: ['*'],
+ })
+ );
+ return result as Event[];
+ } catch {
+ // Events collection may not exist yet
+ console.warn('Failed to fetch events - collection may not exist yet');
+ return [];
+ }
}
export async function getPages(): Promise {