Wire home page content into Sanity CMS with hardcoded fallbacks
Adds homePage singleton document type (hero headline/mission, video section, CTA cards, sponsors). Home page fetches from Sanity when configured and falls back to the existing hardcoded values when not. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
921e03e22e
commit
87182583f8
4 changed files with 183 additions and 39 deletions
|
|
@ -76,6 +76,58 @@ function toNewsArticle(post: SanityPost): NewsArticle {
|
|||
};
|
||||
}
|
||||
|
||||
// ── Home page ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SanityCtaCard {
|
||||
label: string;
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string | null;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export interface SanitySponsor {
|
||||
name: string;
|
||||
logo?: string | null;
|
||||
url?: string | null;
|
||||
}
|
||||
|
||||
export interface HomePageData {
|
||||
heroHeadline: string;
|
||||
heroMission: string;
|
||||
videoHeading: string;
|
||||
videoBody: string;
|
||||
videoYoutubeId: string;
|
||||
ctaCards: SanityCtaCard[];
|
||||
sponsorHeading: string;
|
||||
sponsors: SanitySponsor[];
|
||||
}
|
||||
|
||||
const HOME_PAGE_QUERY = /* groq */ `*[_type == "homePage"][0]{
|
||||
heroHeadline,
|
||||
heroMission,
|
||||
videoHeading,
|
||||
videoBody,
|
||||
videoYoutubeId,
|
||||
ctaCards[]{
|
||||
label,
|
||||
title,
|
||||
description,
|
||||
"image": image.asset->url,
|
||||
href,
|
||||
},
|
||||
sponsorHeading,
|
||||
sponsors[]{
|
||||
name,
|
||||
"logo": logo.asset->url,
|
||||
url,
|
||||
}
|
||||
}`;
|
||||
|
||||
export async function getSanityHomePage(): Promise<HomePageData | null> {
|
||||
return sanityClient.fetch<HomePageData | null>(HOME_PAGE_QUERY);
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────────
|
||||
|
||||
export async function getSanityPosts(): Promise<NewsArticle[]> {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
|||
import SiteHeader from "../components/SiteHeader.astro";
|
||||
import SiteFooter from "../components/SiteFooter.astro";
|
||||
import { navigationItems } from "../lib/navigation";
|
||||
import { isSanityConfigured, getSanityHomePage } from "../lib/sanity";
|
||||
|
||||
const pageSeo = {
|
||||
title: "Highland Group RDA",
|
||||
|
|
@ -13,32 +14,34 @@ const pageSeo = {
|
|||
canonicalPath: "/",
|
||||
};
|
||||
|
||||
const ctaCards = [
|
||||
{
|
||||
label: "Fundraising",
|
||||
title: "Sponsor a pony",
|
||||
sub: "Your support keeps our ponies healthy and our sessions running — from hoof trimming to hay bales.",
|
||||
img: "/alineofponies-1920w.webp",
|
||||
objectPos: "50% 50%",
|
||||
href: "/support-us",
|
||||
},
|
||||
{
|
||||
label: "Get involved",
|
||||
title: "Apply to volunteer",
|
||||
sub: "No horse experience needed. Sidestep walkers, groomers, fundraisers — all roles make a real difference.",
|
||||
img: "/Vols5-2880w-1024x768.avif",
|
||||
objectPos: "50% 30%",
|
||||
href: "/volunteer-application",
|
||||
},
|
||||
{
|
||||
label: "Riding sessions",
|
||||
title: "Apply to participate",
|
||||
sub: "We welcome riders and carriage drivers with a wide range of physical and mental disabilities.",
|
||||
img: "/Samantha-and-Connolly-1280.webp",
|
||||
objectPos: "50% 38%",
|
||||
href: "/participant-application",
|
||||
},
|
||||
];
|
||||
// ── Fallback content (used when Sanity is not configured or document is empty) ─
|
||||
const DEFAULTS = {
|
||||
heroHeadline: "Enriching lives through horses in the Highlands.",
|
||||
heroMission: "Our mission is to bring about meaningful and positive changes in the health and well-being of people with physical or mental disabilities through activities with our horses and ponies.",
|
||||
videoHeading: "What is Riding for the Disabled?",
|
||||
videoBody: "Across the Highlands, we provide riding sessions for people with a wide range of physical and mental disabilities — connecting them with our ponies in a way that's therapeutic, joyful, and life-changing.",
|
||||
videoYoutubeId: "IX2DGd6m8BU",
|
||||
sponsorHeading: "We are grateful for the generous support of our sponsors.",
|
||||
ctaCards: [
|
||||
{ label: "Fundraising", title: "Sponsor a pony", description: "Your support keeps our ponies healthy and our sessions running — from hoof trimming to hay bales.", image: "/alineofponies-1920w.webp", href: "/support-us" },
|
||||
{ label: "Get involved", title: "Apply to volunteer", description: "No horse experience needed. Sidestep walkers, groomers, fundraisers — all roles make a real difference.", image: "/Vols5-2880w-1024x768.avif", href: "/volunteer-application" },
|
||||
{ label: "Riding sessions",title: "Apply to participate", description: "We welcome riders and carriage drivers with a wide range of physical and mental disabilities.", image: "/Samantha-and-Connolly-1280.webp", href: "/participant-application" },
|
||||
],
|
||||
sponsors: [
|
||||
{ name: "Ffordes", logo: "/sponsors/ffordes_logo.png", url: null },
|
||||
],
|
||||
};
|
||||
|
||||
const cms = isSanityConfigured() ? (await getSanityHomePage()) : null;
|
||||
|
||||
const heroHeadline = cms?.heroHeadline || DEFAULTS.heroHeadline;
|
||||
const heroMission = cms?.heroMission || DEFAULTS.heroMission;
|
||||
const videoHeading = cms?.videoHeading || DEFAULTS.videoHeading;
|
||||
const videoBody = cms?.videoBody || DEFAULTS.videoBody;
|
||||
const videoYoutubeId = cms?.videoYoutubeId || DEFAULTS.videoYoutubeId;
|
||||
const sponsorHeading = cms?.sponsorHeading || DEFAULTS.sponsorHeading;
|
||||
const ctaCards = cms?.ctaCards?.length ? cms.ctaCards : DEFAULTS.ctaCards;
|
||||
const sponsors = cms?.sponsors?.length ? cms.sponsors : DEFAULTS.sponsors;
|
||||
---
|
||||
|
||||
<BaseLayout {...pageSeo}>
|
||||
|
|
@ -242,7 +245,7 @@ const ctaCards = [
|
|||
</p>
|
||||
<div style="margin-top: 14px; width: 48px; height: 2px; background: #7DA371;" />
|
||||
<h1 style="margin-top: 18px; font-family: 'Merriweather', serif; font-weight: 900; font-size: clamp(34px, 5vw, 62px); color: #F6F1E8; line-height: 1.12;">
|
||||
Enriching lives through horses in the Highlands.
|
||||
{heroHeadline}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
|
@ -253,7 +256,7 @@ const ctaCards = [
|
|||
>
|
||||
<div style="width: 32px; height: 3px; background: #7DA371; margin-bottom: 14px;" />
|
||||
<blockquote style="font-family: 'Merriweather', serif; font-weight: 700; font-style: italic; font-size: clamp(15px, 1.5vw, 18px); color: #F6F1E8; line-height: 1.55; margin: 0;">
|
||||
Our mission is to bring about meaningful and positive changes in the health and well-being of people with physical or mental disabilities through activities with our horses and ponies.
|
||||
{heroMission}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -289,14 +292,14 @@ const ctaCards = [
|
|||
Our Story
|
||||
</p>
|
||||
<h2 style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(24px, 3vw, 38px); color: #F6F1E8; margin-bottom: 20px; line-height: 1.2; max-width: 600px;">
|
||||
What is Riding for the Disabled?
|
||||
{videoHeading}
|
||||
</h2>
|
||||
<p style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 16px; color: rgba(246,241,232,0.75); line-height: 1.65; max-width: 680px; margin-bottom: 40px;">
|
||||
Across the Highlands, we provide riding sessions for people with a wide range of physical and mental disabilities — connecting them with our ponies in a way that's therapeutic, joyful, and life-changing.
|
||||
{videoBody}
|
||||
</p>
|
||||
<div class="video-embed" style="max-width: 860px; margin: 0 auto; box-shadow: 0 20px 60px rgba(0,0,0,0.5);">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/IX2DGd6m8BU?rel=0&modestbranding=1"
|
||||
src={`https://www.youtube.com/embed/${videoYoutubeId}?rel=0&modestbranding=1`}
|
||||
title="What is Riding for the Disabled Association (RDA)?"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
|
|
@ -329,9 +332,9 @@ const ctaCards = [
|
|||
<a class="cta-card" href={card.href}>
|
||||
<img
|
||||
class="cta-card-img"
|
||||
src={card.img}
|
||||
src={card.image ?? ''}
|
||||
alt={card.title}
|
||||
style={`object-position: ${card.objectPos};`}
|
||||
style="object-position: 50% 35%;"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
|
|
@ -344,7 +347,7 @@ const ctaCards = [
|
|||
{card.title}
|
||||
</h3>
|
||||
<p class="cta-sub" style="font-family: 'Public Sans', sans-serif; font-weight: 600; font-size: 14px; color: rgba(255,255,255,0.8); line-height: 1.6; max-width: 260px;">
|
||||
{card.sub}
|
||||
{card.description}
|
||||
</p>
|
||||
<div class="cta-more" style="margin-top: 16px; display: flex; align-items: center; gap: 8px;">
|
||||
<span style="font-family: 'Public Sans', sans-serif; font-weight: 800; font-size: 13px; color: white; letter-spacing: 0.08em; text-transform: uppercase;">
|
||||
|
|
@ -367,14 +370,19 @@ const ctaCards = [
|
|||
</p>
|
||||
<div style="width: 36px; height: 2px; background: #7DA371; margin: 0 auto 32px;" />
|
||||
<p style="font-family: 'Merriweather', serif; font-weight: 700; font-size: clamp(20px, 2.5vw, 30px); color: #F6F1E8; text-align: center; margin-bottom: 48px; line-height: 1.2;">
|
||||
We are grateful for the generous support of our sponsors.
|
||||
{sponsorHeading}
|
||||
</p>
|
||||
|
||||
<!-- Current sponsors -->
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 40px; justify-content: center; align-items: center; margin-bottom: 64px;">
|
||||
<div class="sponsor-logo">
|
||||
<img src="/sponsors/ffordes_logo.png" alt="Ffordes" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
{sponsors.map((s) => (
|
||||
<div class="sponsor-logo">
|
||||
{s.url
|
||||
? <a href={s.url} target="_blank" rel="noreferrer"><img src={s.logo ?? ''} alt={s.name} loading="lazy" decoding="async" /></a>
|
||||
: <img src={s.logo ?? ''} alt={s.name} loading="lazy" decoding="async" />
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Corporate sponsorship package -->
|
||||
|
|
|
|||
83
studio/schemaTypes/homePageType.ts
Normal file
83
studio/schemaTypes/homePageType.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { defineType, defineField, defineArrayMember } from 'sanity';
|
||||
|
||||
export const homePageType = defineType({
|
||||
name: 'homePage',
|
||||
title: 'Home Page',
|
||||
type: 'document',
|
||||
// Singleton — only one document of this type should exist
|
||||
__experimental_actions: ['update', 'publish', 'unpublish'],
|
||||
fields: [
|
||||
defineField({
|
||||
name: 'heroHeadline',
|
||||
title: 'Hero headline',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'heroMission',
|
||||
title: 'Mission statement (hero blockquote)',
|
||||
type: 'text',
|
||||
rows: 3,
|
||||
}),
|
||||
defineField({
|
||||
name: 'videoHeading',
|
||||
title: 'Video section heading',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'videoBody',
|
||||
title: 'Video section body text',
|
||||
type: 'text',
|
||||
rows: 3,
|
||||
}),
|
||||
defineField({
|
||||
name: 'videoYoutubeId',
|
||||
title: 'YouTube video ID',
|
||||
type: 'string',
|
||||
description: 'Just the ID, e.g. IX2DGd6m8BU — not the full URL',
|
||||
}),
|
||||
defineField({
|
||||
name: 'ctaCards',
|
||||
title: 'CTA cards (editorial panel)',
|
||||
type: 'array',
|
||||
of: [
|
||||
defineArrayMember({
|
||||
type: 'object',
|
||||
name: 'ctaCard',
|
||||
fields: [
|
||||
defineField({ name: 'label', title: 'Eyebrow label', type: 'string' }),
|
||||
defineField({ name: 'title', title: 'Card title', type: 'string' }),
|
||||
defineField({ name: 'description', title: 'Description', type: 'text', rows: 2 }),
|
||||
defineField({ name: 'image', title: 'Background image', type: 'image', options: { hotspot: true } }),
|
||||
defineField({ name: 'href', title: 'Link path', type: 'string' }),
|
||||
],
|
||||
preview: { select: { title: 'title', subtitle: 'label' } },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
defineField({
|
||||
name: 'sponsorHeading',
|
||||
title: 'Sponsors section heading',
|
||||
type: 'string',
|
||||
}),
|
||||
defineField({
|
||||
name: 'sponsors',
|
||||
title: 'Sponsors',
|
||||
type: 'array',
|
||||
of: [
|
||||
defineArrayMember({
|
||||
type: 'object',
|
||||
name: 'sponsor',
|
||||
fields: [
|
||||
defineField({ name: 'name', title: 'Name', type: 'string' }),
|
||||
defineField({ name: 'logo', title: 'Logo', type: 'image' }),
|
||||
defineField({ name: 'url', title: 'Website URL', type: 'url' }),
|
||||
],
|
||||
preview: { select: { title: 'name' } },
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
preview: {
|
||||
prepare: () => ({ title: 'Home Page' }),
|
||||
},
|
||||
});
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { postType } from './postType';
|
||||
import { homePageType } from './homePageType';
|
||||
|
||||
export const schemaTypes = [postType];
|
||||
export const schemaTypes = [postType, homePageType];
|
||||
|
|
|
|||
Loading…
Reference in a new issue