import { createClient } from '@sanity/client'; import { toHTML } from '@portabletext/to-html'; import type { NewsArticle } from './news'; // ── Client ──────────────────────────────────────────────────────────────────── export const sanityClient = createClient({ projectId: import.meta.env.SANITY_PROJECT_ID ?? '', dataset: import.meta.env.SANITY_DATASET ?? 'production', apiVersion: '2024-01-01', // useCdn in production; always false during dev so edits show immediately useCdn: import.meta.env.PROD ?? false, // Token only needed for draft previews; public published content works without one token: import.meta.env.SANITY_API_TOKEN, }); export function isSanityConfigured(): boolean { return Boolean(import.meta.env.SANITY_PROJECT_ID); } // ── GROQ queries ────────────────────────────────────────────────────────────── const POST_FIELDS = /* groq */ ` _id, title, "slug": slug.current, date, category, summary, "image": image.asset->url, body `; const ALL_POSTS_QUERY = /* groq */ `*[_type == "post"] | order(date desc) { ${POST_FIELDS} }`; const POST_BY_SLUG_QUERY = /* groq */ `*[_type == "post" && slug.current == $slug][0] { ${POST_FIELDS} }`; // ── Raw Sanity shape ────────────────────────────────────────────────────────── interface SanityPost { _id: string; title: string; slug: string; date: string; category: string; summary: string; image?: string | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any body?: any[]; } // ── Converters ──────────────────────────────────────────────────────────────── function toNewsArticle(post: SanityPost): NewsArticle { const html = post.body ? toHTML(post.body, { components: { marks: { link: ({ children, value }) => { const target = value?.blank ? ' target="_blank" rel="noreferrer"' : ''; return `${children}`; }, }, }, }) : ''; return { title: post.title, date: post.date ?? '', summary: post.summary ?? '', image: post.image ?? undefined, slug: post.slug, category: post.category ?? 'News', body: '', // raw markdown not used when coming from Sanity html, }; } // ── 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 { return sanityClient.fetch(HOME_PAGE_QUERY); } // ── Public API ──────────────────────────────────────────────────────────────── export async function getSanityPosts(): Promise { const posts = await sanityClient.fetch(ALL_POSTS_QUERY); return posts.map(toNewsArticle); } export async function getSanityPost(slug: string): Promise { const post = await sanityClient.fetch(POST_BY_SLUG_QUERY, { slug }); return post ? toNewsArticle(post) : undefined; }