Deploy
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import HeroSlideshow, { type HeroSlide } from "@/components/hero/HeroSlideshow"
|
||||
import { getAdvertBanners } from "@/lib/cms"
|
||||
|
||||
// The slides this site always shows. Banners published in Content Studio
|
||||
// are shown first, while they are within their start and end dates.
|
||||
const SLIDES: HeroSlide[] = [
|
||||
{
|
||||
primary: true,
|
||||
image: "https://images.pexels.com/photos/4083586/pexels-photo-4083586.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",
|
||||
alt: "Ben's Noodle House",
|
||||
eyebrow: "Ben's Noodle House",
|
||||
headline: "Madadeni's first dedicated Chinese noodle House.",
|
||||
subtitle: "Where authentic wok technique meets a South African kick, served piping hot in classic takeaway boxes right in the heart of the township.",
|
||||
ctaText: "View our menu",
|
||||
ctaHref: "/menu",
|
||||
},
|
||||
]
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
async function bannerSlides(): Promise<HeroSlide[]> {
|
||||
const now = Date.now()
|
||||
const items = await getAdvertBanners()
|
||||
return items.flatMap((item) => {
|
||||
const d = item.data as {
|
||||
headline?: string; subheadline?: string; cta_text?: string; cta_url?: string
|
||||
image?: string; start_date?: string; end_date?: string
|
||||
}
|
||||
const headline = (d.headline ?? "").trim()
|
||||
const image = (d.image ?? "").trim()
|
||||
if (!headline && !image) return []
|
||||
const start = d.start_date ? new Date(d.start_date).getTime() : -Infinity
|
||||
// A plain date ends at the END of that day, not the first second of it.
|
||||
const end = d.end_date ? new Date(d.end_date).getTime() + (d.end_date.length === 10 ? DAY_MS : 0) : Infinity
|
||||
if (!(now >= start && now < end)) return []
|
||||
const ownButton = Boolean(d.cta_text && d.cta_url)
|
||||
if (!headline) {
|
||||
// A photo with no words is a poster or flyer: its message is in the
|
||||
// picture, so it is shown whole, with a button only if it has its own.
|
||||
return [{
|
||||
image,
|
||||
alt: item.title || "Banner",
|
||||
eyebrow: "",
|
||||
headline: "",
|
||||
subtitle: "",
|
||||
ctaText: ownButton ? d.cta_text! : "",
|
||||
ctaHref: ownButton ? d.cta_url! : "",
|
||||
}]
|
||||
}
|
||||
return [{
|
||||
image: image || SLIDES[0].image,
|
||||
alt: headline,
|
||||
eyebrow: "",
|
||||
headline,
|
||||
subtitle: d.subheadline ?? "",
|
||||
ctaText: ownButton ? d.cta_text! : SLIDES[0].ctaText,
|
||||
ctaHref: ownButton ? d.cta_url! : SLIDES[0].ctaHref,
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
const STORE_URL = process.env.VULA_CMS_URL || "https://ide.vulai.co.za"
|
||||
const STORE_ID = process.env.VULA_CMS_PROJECT_ID || ""
|
||||
const CURRENCY: Record<string, string> = { zar: "R", usd: "$", eur: "€", gbp: "£" }
|
||||
|
||||
interface StoreItem {
|
||||
id: string
|
||||
title: string
|
||||
thumbnail: string | null
|
||||
variants?: Array<{ prices?: Array<{ amount: number; currency_code: string }> }>
|
||||
}
|
||||
|
||||
function priceOf(item: StoreItem): string {
|
||||
const price = item.variants?.[0]?.prices?.[0]
|
||||
if (!price) return ""
|
||||
const amount = price.amount / 100
|
||||
const symbol = CURRENCY[price.currency_code?.toLowerCase()] ?? ""
|
||||
return symbol + (Number.isInteger(amount) ? String(amount) : amount.toFixed(2))
|
||||
}
|
||||
|
||||
// Two things this business actually sells, with their photos — read from its
|
||||
// own catalogue, so the slides change when the catalogue does.
|
||||
async function catalogueSlides(): Promise<HeroSlide[]> {
|
||||
if (!STORE_ID) return []
|
||||
try {
|
||||
const res = await fetch(STORE_URL + "/api/store/" + STORE_ID + "/products?limit=12", { next: { revalidate: 300 } })
|
||||
if (!res.ok) return []
|
||||
const data = (await res.json()) as { products?: StoreItem[] }
|
||||
return (data.products ?? [])
|
||||
.filter((p) => p.title && typeof p.thumbnail === "string" && p.thumbnail.startsWith("https://"))
|
||||
.slice(0, 2)
|
||||
.map((p) => ({
|
||||
image: p.thumbnail as string,
|
||||
alt: p.title,
|
||||
eyebrow: "Ben's Noodle House",
|
||||
headline: p.title,
|
||||
subtitle: priceOf(p),
|
||||
ctaText: SLIDES[0].ctaText,
|
||||
ctaHref: SLIDES[0].ctaHref,
|
||||
}))
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export default async function HeroSection() {
|
||||
const [banners, catalogue] = await Promise.all([bannerSlides(), catalogueSlides()])
|
||||
const slides = [...banners, ...SLIDES, ...catalogue].slice(0, 6)
|
||||
return <HeroSlideshow slides={slides} />
|
||||
}
|
||||
Reference in New Issue
Block a user