refactor(homepage): drive homepage from structured frontmatter

- replace the flat homepage sections array with a named home: block
- validate homepage sections with typed Zod schemas
- make HomePage.astro render the structured content file data
- keep English and Spanish homepage copy easier to scan and edit
This commit is contained in:
2026-06-11 13:16:36 -07:00
parent 36f82a9a76
commit 1be3ad0410
5 changed files with 240 additions and 124 deletions
+49 -14
View File
@@ -11,26 +11,61 @@ import HomeProcess from './home/HomeProcess.astro';
import HomeServicesIntro from './home/HomeServicesIntro.astro';
import HomeSkills from './home/HomeSkills.astro';
import HomeTestimonials from './home/HomeTestimonials.astro';
import type { HomeSection } from '../types/home-sections';
import type { HomePageContent, HomeSection } from '../types/home-sections';
interface Props { lang?: string }
const { lang = 'en' } = Astro.props;
const es = lang === 'es';
const entry = await getEntry('pages', es ? 'es/index' : 'en/index');
if (!entry) throw new Error(`[HomePage] Content entry not found for lang="${lang}". If running in dev, restart the dev server to rebuild the content layer cache.`);
const { title, description, canonical, sections = [] } = entry.data;
const { title, description, canonical } = entry.data;
function fromHomeContent(home: HomePageContent): HomeSection[] {
return [
{ type: 'hero', ...home.hero },
{ type: 'services-intro', ...home.servicesIntro },
{ type: 'benefits', ...home.benefits },
{ type: 'skills', ...home.skills },
{ type: 'insurance', ...home.insurance },
{ type: 'esa', ...home.esa },
{ type: 'financial-help', ...home.financialHelp },
{ type: 'process', ...home.process },
{ type: 'director', ...home.director },
{ type: 'testimonials', ...home.testimonials },
];
}
const sections: HomeSection[] = entry.data.home
? fromHomeContent(entry.data.home)
: ((entry.data.sections ?? []) as HomeSection[]);
function renderSection(section: HomeSection) {
switch (section.type) {
case 'hero':
return <HomeHero section={section} />;
case 'services-intro':
return <HomeServicesIntro section={section} />;
case 'benefits':
return <HomeBenefits section={section} />;
case 'skills':
return <HomeSkills section={section} />;
case 'insurance':
return <HomeInsurance section={section} />;
case 'esa':
return <HomeEsa section={section} />;
case 'financial-help':
return <HomeFinancialHelp section={section} />;
case 'process':
return <HomeProcess section={section} />;
case 'director':
return <HomeDirector section={section} />;
case 'testimonials':
return <HomeTestimonials section={section} />;
}
return null;
}
---
<BaseLayout {title} {description} {canonical} {lang}>
{(sections as HomeSection[]).map((section) => {
if (section.type === 'hero') return <HomeHero section={section} />;
if (section.type === 'services-intro') return <HomeServicesIntro section={section} />;
if (section.type === 'benefits') return <HomeBenefits section={section} />;
if (section.type === 'skills') return <HomeSkills section={section} />;
if (section.type === 'insurance') return <HomeInsurance section={section} />;
if (section.type === 'esa') return <HomeEsa section={section} />;
if (section.type === 'financial-help') return <HomeFinancialHelp section={section} />;
if (section.type === 'process') return <HomeProcess section={section} />;
if (section.type === 'director') return <HomeDirector section={section} />;
if (section.type === 'testimonials') return <HomeTestimonials section={section} />;
})}
{sections.map(renderSection)}
</BaseLayout>
+3 -1
View File
@@ -1,5 +1,6 @@
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
import { homePageSchema, homeSectionsSchema } from './types/home-sections';
const language = z.enum(['en', 'ar', 'es']);
const shared = {
@@ -12,7 +13,8 @@ const shared = {
lang: language,
translationKey: z.string().optional(),
draft: z.boolean().default(false),
sections: z.array(z.record(z.unknown())).optional()
home: homePageSchema.optional(),
sections: homeSectionsSchema.optional()
};
const pages = defineCollection({
+21 -11
View File
@@ -6,8 +6,9 @@ canonical: "https://www.azinstitute4autism.com/"
lang: "en"
translationKey: "index"
draft: false
sections:
- type: hero
home:
# Hero
hero:
eyebrow: "Arizona's Leading Experts"
heading: "Behavioral Health<br />&amp; Special<br />Education"
body: "Here at the Arizona Institute for Autism (AIA), we provide expert clinical care for children and teens who have an autism diagnosis. We currently serve families in Scottsdale, Gilbert, Mesa, Tempe, and Phoenix Metropolitan areas."
@@ -17,7 +18,8 @@ sections:
image: bcba-with-happy-toddler.webp
imageAlt: BCBA therapist with happy toddler
- type: services-intro
# Services and commitments
servicesIntro:
servicesHeading: ABA Therapy Services for Children and Teens Diagnosed with Autism Spectrum Disorder
services:
- Autism Advocacy Support
@@ -42,7 +44,8 @@ sections:
commitmentsImage: playing-boy.webp
commitmentsImageAlt: Boy playing while learning
- type: benefits
# Benefits
benefits:
heading: Benefits that go Beyond a Typical Integrated Therapy Provider
subheading: The AIA Difference
items:
@@ -55,7 +58,8 @@ sections:
videoImage: learner-journey.webp
videoImageAlt: Learner journey for children with autism video
- type: skills
# Skills
skills:
heading: Does Your Learner Struggle With These Skills?
skills:
- icon: icon-Social_Engagement.png
@@ -71,7 +75,8 @@ sections:
- icon: icon-Speach.png
label: Speech
- type: insurance
# Insurance
insurance:
heading: "Yes, We Take Insurance"
body: "An all-in-one integrated service for your kiddo's special education needs, the Arizona Institute for Autism (AIA) offers and accepts most insurance plans that cover ABA therapy. These include BCBS AZ, Aetna, Optum, Tricare, United Healthcare AHCCCS, and UnitedHealth."
logos:
@@ -88,7 +93,8 @@ sections:
- file: united-healthcare-logo-1.webp
alt: United Healthcare
- type: esa
# ESA
esa:
heading: Arizona Scholarship Account (ESA)
body: >-
Thousands of Arizona learners are currently eligible for the state funded ESA,
@@ -100,14 +106,16 @@ sections:
image: logo-az-dept-of-education.webp
imageAlt: Arizona Department of Education
- type: financial-help
# Financial help
financialHelp:
heading: Need Help Paying for Your Care?
body: The Arizona Institute for Autism offers flexible financial assistance to the uninsured. Get the care you want or need and pay over time.
cta:
label: Request an Appointment
href: /client-consultation
- type: process
# Process
process:
heading: Our Process
steps:
- icon: learner-journey-step-1a.svg
@@ -123,7 +131,8 @@ sections:
- icon: learner-journey-step-6.svg
label: Collaborate on a Care Plan
- type: director
# Director
director:
heading: An Extension of Your Family
quote: '"At AIA, We strive to provide excellent and compassionate care to all communities we serve. We could not accomplish that without excellent, passionate, and committed staff and families. We look forward to our collaboration with you as we continue to grow, serve and support our Learners in the pursuit of their individual potential."'
photo: rula-diab.webp
@@ -133,7 +142,8 @@ sections:
signature: clinical-director-rula-diab.png
signatureAlt: Clinical Director Rula Diab
- type: testimonials
# Testimonials
testimonials:
heading: What Clients Are Saying
items:
- author: Claudia
+21 -11
View File
@@ -6,8 +6,9 @@ canonical: "https://www.azinstitute4autism.com/es"
lang: "es"
translationKey: "index"
draft: false
sections:
- type: hero
home:
# Hero
hero:
eyebrow: Los principales expertos de Arizona
heading: "Salud Mental y<br />Educación Especial"
body: "Aquí en el Instituto de Autismo de Arizona (AIA), brindamos atención clínica experta para niños y adolescentes con diagnóstico de autismo. Actualmente atendemos a familias en las áreas metropolitanas de Scottsdale, Gilbert, Mesa, Tempe y Phoenix."
@@ -17,7 +18,8 @@ sections:
image: bcba-with-happy-toddler.webp
imageAlt: Terapeuta BCBA con niño feliz
- type: services-intro
# Servicios y compromisos
servicesIntro:
servicesHeading: Servicios de terapia ABA para niños y adolescentes diagnosticados con TEA
services:
- Apoyo a la defensa del autismo
@@ -42,7 +44,8 @@ sections:
commitmentsImage: playing-boy.webp
commitmentsImageAlt: Niño jugando mientras aprende
- type: benefits
# Beneficios
benefits:
heading: Beneficios que van más allá de un proveedor de terapia integrada típico
subheading: La Diferencia AIA
items:
@@ -55,7 +58,8 @@ sections:
videoImage: learner-journey.webp
videoImageAlt: Video del recorrido del estudiante
- type: skills
# Habilidades
skills:
heading: ¿Su alumno tiene dificultades con estas habilidades?
skills:
- icon: icon-Social_Engagement.png
@@ -71,7 +75,8 @@ sections:
- icon: icon-Speach.png
label: Discurso
- type: insurance
# Seguros
insurance:
heading: "Sí, aceptamos seguros."
body: "Un servicio integrado todo en uno para las necesidades de educación especial de su hijo, el Instituto de Autismo de Arizona (AIA) ofrece y acepta la mayoría de los planes de seguro que cubren la terapia ABA. Estos incluyen BCBS AZ, Aetna, Optum, Tricare, United Healthcare AHCCCS y UnitedHealth."
logos:
@@ -88,7 +93,8 @@ sections:
- file: united-healthcare-logo-1.webp
alt: United Healthcare
- type: esa
# ESA
esa:
heading: Cuenta de Becas de Arizona (ESA)
body: >-
Miles de estudiantes de Arizona son actualmente elegibles para la ESA financiada
@@ -101,14 +107,16 @@ sections:
image: logo-az-dept-of-education.webp
imageAlt: Departamento de Educación de Arizona
- type: financial-help
# Ayuda financiera
financialHelp:
heading: ¿Necesita ayuda para pagar su atención?
body: El Instituto de Autismo de Arizona ofrece asistencia financiera flexible a las personas sin seguro. Obtén la atención que deseas o necesitas y paga a plazos.
cta:
label: Solicitar cita
href: /es/client-consultation
- type: process
# Proceso
process:
heading: Nuestro proceso
steps:
- icon: learner-journey-step-1a.svg
@@ -124,7 +132,8 @@ sections:
- icon: learner-journey-step-6.svg
label: Colaborar en un Plan de Cuidados
- type: director
# Directora
director:
heading: Una extensión de tu familia
quote: '"En AIA, nos esforzamos por brindar atención excelente y compasiva a todas las comunidades a las que servimos. No podríamos lograr eso sin un personal y familias excelentes, apasionados y comprometidos. Esperamos con ansias nuestra colaboración con usted a medida que continuamos creciendo, sirviendo y apoyando a nuestros estudiantes en la búsqueda de su potencial individual."'
photo: rula-diab.webp
@@ -134,7 +143,8 @@ sections:
signature: clinical-director-rula-diab.png
signatureAlt: Directora Clínica Rula Diab
- type: testimonials
# Testimonios
testimonials:
heading: Lo que dicen los clientes
items:
- author: Claudia
+146 -87
View File
@@ -1,97 +1,156 @@
export interface HeroSection {
type: 'hero';
eyebrow: string;
heading: string;
body: string;
cta: { label: string; href: string };
image: string;
imageAlt: string;
}
import { z } from 'astro:content';
export interface ServicesIntroSection {
type: 'services-intro';
servicesHeading: string;
services: string[];
servicesImage: string;
servicesImageAlt: string;
commitmentsHeading: string;
commitments: string[];
commitmentsCta: { label: string; href: string };
commitmentsImage: string;
commitmentsImageAlt: string;
}
const ctaSchema = z.object({
label: z.string(),
href: z.string(),
});
export interface BenefitsSection {
type: 'benefits';
heading: string;
subheading: string;
items: string[];
videoUrl: string;
videoTitle: string;
videoImage: string;
videoImageAlt: string;
}
const heroSectionSchema = z.object({
type: z.literal('hero'),
eyebrow: z.string(),
heading: z.string(),
body: z.string(),
cta: ctaSchema,
image: z.string(),
imageAlt: z.string(),
});
export interface SkillsSection {
type: 'skills';
heading: string;
skills: { icon: string; label: string }[];
}
const servicesIntroSectionSchema = z.object({
type: z.literal('services-intro'),
servicesHeading: z.string(),
services: z.array(z.string()),
servicesImage: z.string(),
servicesImageAlt: z.string(),
commitmentsHeading: z.string(),
commitments: z.array(z.string()),
commitmentsCta: ctaSchema,
commitmentsImage: z.string(),
commitmentsImageAlt: z.string(),
});
export interface InsuranceSection {
type: 'insurance';
heading: string;
body: string;
logos: { file: string; alt: string }[];
}
const benefitsSectionSchema = z.object({
type: z.literal('benefits'),
heading: z.string(),
subheading: z.string(),
items: z.array(z.string()),
videoUrl: z.string(),
videoTitle: z.string(),
videoImage: z.string(),
videoImageAlt: z.string(),
});
export interface EsaSection {
type: 'esa';
heading: string;
body: string;
image: string;
imageAlt: string;
}
const skillsSectionSchema = z.object({
type: z.literal('skills'),
heading: z.string(),
skills: z.array(z.object({
icon: z.string(),
label: z.string(),
})),
});
export interface FinancialHelpSection {
type: 'financial-help';
heading: string;
body: string;
cta: { label: string; href: string };
}
const insuranceSectionSchema = z.object({
type: z.literal('insurance'),
heading: z.string(),
body: z.string(),
logos: z.array(z.object({
file: z.string(),
alt: z.string(),
})),
});
export interface ProcessSection {
type: 'process';
heading: string;
steps: { icon: string; label: string }[];
}
const esaSectionSchema = z.object({
type: z.literal('esa'),
heading: z.string(),
body: z.string(),
image: z.string(),
imageAlt: z.string(),
});
export interface DirectorSection {
type: 'director';
heading: string;
quote: string;
photo: string;
photoAlt: string;
name: string;
credentials: string;
signature: string;
signatureAlt: string;
}
const financialHelpSectionSchema = z.object({
type: z.literal('financial-help'),
heading: z.string(),
body: z.string(),
cta: ctaSchema,
});
export interface TestimonialsSection {
type: 'testimonials';
heading: string;
items: { author: string; text: string }[];
}
const processSectionSchema = z.object({
type: z.literal('process'),
heading: z.string(),
steps: z.array(z.object({
icon: z.string(),
label: z.string(),
})),
});
export type HomeSection =
| HeroSection
| ServicesIntroSection
| BenefitsSection
| SkillsSection
| InsuranceSection
| EsaSection
| FinancialHelpSection
| ProcessSection
| DirectorSection
| TestimonialsSection;
const directorSectionSchema = z.object({
type: z.literal('director'),
heading: z.string(),
quote: z.string(),
photo: z.string(),
photoAlt: z.string(),
name: z.string(),
credentials: z.string(),
signature: z.string(),
signatureAlt: z.string(),
});
const testimonialsSectionSchema = z.object({
type: z.literal('testimonials'),
heading: z.string(),
items: z.array(z.object({
author: z.string(),
text: z.string(),
})),
});
export const homeSectionSchema = z.discriminatedUnion('type', [
heroSectionSchema,
servicesIntroSectionSchema,
benefitsSectionSchema,
skillsSectionSchema,
insuranceSectionSchema,
esaSectionSchema,
financialHelpSectionSchema,
processSectionSchema,
directorSectionSchema,
testimonialsSectionSchema,
]);
export const homeSectionsSchema = z.array(homeSectionSchema);
const homeHeroSchema = heroSectionSchema.omit({ type: true });
const homeServicesIntroSchema = servicesIntroSectionSchema.omit({ type: true });
const homeBenefitsSchema = benefitsSectionSchema.omit({ type: true });
const homeSkillsSchema = skillsSectionSchema.omit({ type: true });
const homeInsuranceSchema = insuranceSectionSchema.omit({ type: true });
const homeEsaSchema = esaSectionSchema.omit({ type: true });
const homeFinancialHelpSchema = financialHelpSectionSchema.omit({ type: true });
const homeProcessSchema = processSectionSchema.omit({ type: true });
const homeDirectorSchema = directorSectionSchema.omit({ type: true });
const homeTestimonialsSchema = testimonialsSectionSchema.omit({ type: true });
export const homePageSchema = z.object({
hero: homeHeroSchema,
servicesIntro: homeServicesIntroSchema,
benefits: homeBenefitsSchema,
skills: homeSkillsSchema,
insurance: homeInsuranceSchema,
esa: homeEsaSchema,
financialHelp: homeFinancialHelpSchema,
process: homeProcessSchema,
director: homeDirectorSchema,
testimonials: homeTestimonialsSchema,
});
export type HomeSection = z.infer<typeof homeSectionSchema>;
export type HeroSection = z.infer<typeof heroSectionSchema>;
export type ServicesIntroSection = z.infer<typeof servicesIntroSectionSchema>;
export type BenefitsSection = z.infer<typeof benefitsSectionSchema>;
export type SkillsSection = z.infer<typeof skillsSectionSchema>;
export type InsuranceSection = z.infer<typeof insuranceSectionSchema>;
export type EsaSection = z.infer<typeof esaSectionSchema>;
export type FinancialHelpSection = z.infer<typeof financialHelpSectionSchema>;
export type ProcessSection = z.infer<typeof processSectionSchema>;
export type DirectorSection = z.infer<typeof directorSectionSchema>;
export type TestimonialsSection = z.infer<typeof testimonialsSectionSchema>;
export type HomePageContent = z.infer<typeof homePageSchema>;