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 HomeServicesIntro from './home/HomeServicesIntro.astro';
import HomeSkills from './home/HomeSkills.astro'; import HomeSkills from './home/HomeSkills.astro';
import HomeTestimonials from './home/HomeTestimonials.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 } interface Props { lang?: string }
const { lang = 'en' } = Astro.props; const { lang = 'en' } = Astro.props;
const es = lang === 'es'; const es = lang === 'es';
const entry = await getEntry('pages', es ? 'es/index' : 'en/index'); 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.`); 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}> <BaseLayout {title} {description} {canonical} {lang}>
{(sections as HomeSection[]).map((section) => { {sections.map(renderSection)}
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} />;
})}
</BaseLayout> </BaseLayout>
+3 -1
View File
@@ -1,5 +1,6 @@
import { defineCollection, z } from 'astro:content'; import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders'; import { glob } from 'astro/loaders';
import { homePageSchema, homeSectionsSchema } from './types/home-sections';
const language = z.enum(['en', 'ar', 'es']); const language = z.enum(['en', 'ar', 'es']);
const shared = { const shared = {
@@ -12,7 +13,8 @@ const shared = {
lang: language, lang: language,
translationKey: z.string().optional(), translationKey: z.string().optional(),
draft: z.boolean().default(false), draft: z.boolean().default(false),
sections: z.array(z.record(z.unknown())).optional() home: homePageSchema.optional(),
sections: homeSectionsSchema.optional()
}; };
const pages = defineCollection({ const pages = defineCollection({
+21 -11
View File
@@ -6,8 +6,9 @@ canonical: "https://www.azinstitute4autism.com/"
lang: "en" lang: "en"
translationKey: "index" translationKey: "index"
draft: false draft: false
sections: home:
- type: hero # Hero
hero:
eyebrow: "Arizona's Leading Experts" eyebrow: "Arizona's Leading Experts"
heading: "Behavioral Health<br />&amp; Special<br />Education" 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." 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 image: bcba-with-happy-toddler.webp
imageAlt: BCBA therapist with happy toddler 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 servicesHeading: ABA Therapy Services for Children and Teens Diagnosed with Autism Spectrum Disorder
services: services:
- Autism Advocacy Support - Autism Advocacy Support
@@ -42,7 +44,8 @@ sections:
commitmentsImage: playing-boy.webp commitmentsImage: playing-boy.webp
commitmentsImageAlt: Boy playing while learning commitmentsImageAlt: Boy playing while learning
- type: benefits # Benefits
benefits:
heading: Benefits that go Beyond a Typical Integrated Therapy Provider heading: Benefits that go Beyond a Typical Integrated Therapy Provider
subheading: The AIA Difference subheading: The AIA Difference
items: items:
@@ -55,7 +58,8 @@ sections:
videoImage: learner-journey.webp videoImage: learner-journey.webp
videoImageAlt: Learner journey for children with autism video videoImageAlt: Learner journey for children with autism video
- type: skills # Skills
skills:
heading: Does Your Learner Struggle With These Skills? heading: Does Your Learner Struggle With These Skills?
skills: skills:
- icon: icon-Social_Engagement.png - icon: icon-Social_Engagement.png
@@ -71,7 +75,8 @@ sections:
- icon: icon-Speach.png - icon: icon-Speach.png
label: Speech label: Speech
- type: insurance # Insurance
insurance:
heading: "Yes, We Take 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." 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: logos:
@@ -88,7 +93,8 @@ sections:
- file: united-healthcare-logo-1.webp - file: united-healthcare-logo-1.webp
alt: United Healthcare alt: United Healthcare
- type: esa # ESA
esa:
heading: Arizona Scholarship Account (ESA) heading: Arizona Scholarship Account (ESA)
body: >- body: >-
Thousands of Arizona learners are currently eligible for the state funded ESA, Thousands of Arizona learners are currently eligible for the state funded ESA,
@@ -100,14 +106,16 @@ sections:
image: logo-az-dept-of-education.webp image: logo-az-dept-of-education.webp
imageAlt: Arizona Department of Education imageAlt: Arizona Department of Education
- type: financial-help # Financial help
financialHelp:
heading: Need Help Paying for Your Care? 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. 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: cta:
label: Request an Appointment label: Request an Appointment
href: /client-consultation href: /client-consultation
- type: process # Process
process:
heading: Our Process heading: Our Process
steps: steps:
- icon: learner-journey-step-1a.svg - icon: learner-journey-step-1a.svg
@@ -123,7 +131,8 @@ sections:
- icon: learner-journey-step-6.svg - icon: learner-journey-step-6.svg
label: Collaborate on a Care Plan label: Collaborate on a Care Plan
- type: director # Director
director:
heading: An Extension of Your Family 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."' 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 photo: rula-diab.webp
@@ -133,7 +142,8 @@ sections:
signature: clinical-director-rula-diab.png signature: clinical-director-rula-diab.png
signatureAlt: Clinical Director Rula Diab signatureAlt: Clinical Director Rula Diab
- type: testimonials # Testimonials
testimonials:
heading: What Clients Are Saying heading: What Clients Are Saying
items: items:
- author: Claudia - author: Claudia
+21 -11
View File
@@ -6,8 +6,9 @@ canonical: "https://www.azinstitute4autism.com/es"
lang: "es" lang: "es"
translationKey: "index" translationKey: "index"
draft: false draft: false
sections: home:
- type: hero # Hero
hero:
eyebrow: Los principales expertos de Arizona eyebrow: Los principales expertos de Arizona
heading: "Salud Mental y<br />Educación Especial" 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." 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 image: bcba-with-happy-toddler.webp
imageAlt: Terapeuta BCBA con niño feliz 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 servicesHeading: Servicios de terapia ABA para niños y adolescentes diagnosticados con TEA
services: services:
- Apoyo a la defensa del autismo - Apoyo a la defensa del autismo
@@ -42,7 +44,8 @@ sections:
commitmentsImage: playing-boy.webp commitmentsImage: playing-boy.webp
commitmentsImageAlt: Niño jugando mientras aprende 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 heading: Beneficios que van más allá de un proveedor de terapia integrada típico
subheading: La Diferencia AIA subheading: La Diferencia AIA
items: items:
@@ -55,7 +58,8 @@ sections:
videoImage: learner-journey.webp videoImage: learner-journey.webp
videoImageAlt: Video del recorrido del estudiante videoImageAlt: Video del recorrido del estudiante
- type: skills # Habilidades
skills:
heading: ¿Su alumno tiene dificultades con estas habilidades? heading: ¿Su alumno tiene dificultades con estas habilidades?
skills: skills:
- icon: icon-Social_Engagement.png - icon: icon-Social_Engagement.png
@@ -71,7 +75,8 @@ sections:
- icon: icon-Speach.png - icon: icon-Speach.png
label: Discurso label: Discurso
- type: insurance # Seguros
insurance:
heading: "Sí, aceptamos seguros." 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." 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: logos:
@@ -88,7 +93,8 @@ sections:
- file: united-healthcare-logo-1.webp - file: united-healthcare-logo-1.webp
alt: United Healthcare alt: United Healthcare
- type: esa # ESA
esa:
heading: Cuenta de Becas de Arizona (ESA) heading: Cuenta de Becas de Arizona (ESA)
body: >- body: >-
Miles de estudiantes de Arizona son actualmente elegibles para la ESA financiada Miles de estudiantes de Arizona son actualmente elegibles para la ESA financiada
@@ -101,14 +107,16 @@ sections:
image: logo-az-dept-of-education.webp image: logo-az-dept-of-education.webp
imageAlt: Departamento de Educación de Arizona imageAlt: Departamento de Educación de Arizona
- type: financial-help # Ayuda financiera
financialHelp:
heading: ¿Necesita ayuda para pagar su atención? 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. 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: cta:
label: Solicitar cita label: Solicitar cita
href: /es/client-consultation href: /es/client-consultation
- type: process # Proceso
process:
heading: Nuestro proceso heading: Nuestro proceso
steps: steps:
- icon: learner-journey-step-1a.svg - icon: learner-journey-step-1a.svg
@@ -124,7 +132,8 @@ sections:
- icon: learner-journey-step-6.svg - icon: learner-journey-step-6.svg
label: Colaborar en un Plan de Cuidados label: Colaborar en un Plan de Cuidados
- type: director # Directora
director:
heading: Una extensión de tu familia 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."' 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 photo: rula-diab.webp
@@ -134,7 +143,8 @@ sections:
signature: clinical-director-rula-diab.png signature: clinical-director-rula-diab.png
signatureAlt: Directora Clínica Rula Diab signatureAlt: Directora Clínica Rula Diab
- type: testimonials # Testimonios
testimonials:
heading: Lo que dicen los clientes heading: Lo que dicen los clientes
items: items:
- author: Claudia - author: Claudia
+146 -87
View File
@@ -1,97 +1,156 @@
export interface HeroSection { import { z } from 'astro:content';
type: 'hero';
eyebrow: string;
heading: string;
body: string;
cta: { label: string; href: string };
image: string;
imageAlt: string;
}
export interface ServicesIntroSection { const ctaSchema = z.object({
type: 'services-intro'; label: z.string(),
servicesHeading: string; href: z.string(),
services: string[]; });
servicesImage: string;
servicesImageAlt: string;
commitmentsHeading: string;
commitments: string[];
commitmentsCta: { label: string; href: string };
commitmentsImage: string;
commitmentsImageAlt: string;
}
export interface BenefitsSection { const heroSectionSchema = z.object({
type: 'benefits'; type: z.literal('hero'),
heading: string; eyebrow: z.string(),
subheading: string; heading: z.string(),
items: string[]; body: z.string(),
videoUrl: string; cta: ctaSchema,
videoTitle: string; image: z.string(),
videoImage: string; imageAlt: z.string(),
videoImageAlt: string; });
}
export interface SkillsSection { const servicesIntroSectionSchema = z.object({
type: 'skills'; type: z.literal('services-intro'),
heading: string; servicesHeading: z.string(),
skills: { icon: string; label: 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 { const benefitsSectionSchema = z.object({
type: 'insurance'; type: z.literal('benefits'),
heading: string; heading: z.string(),
body: string; subheading: z.string(),
logos: { file: string; alt: string }[]; items: z.array(z.string()),
} videoUrl: z.string(),
videoTitle: z.string(),
videoImage: z.string(),
videoImageAlt: z.string(),
});
export interface EsaSection { const skillsSectionSchema = z.object({
type: 'esa'; type: z.literal('skills'),
heading: string; heading: z.string(),
body: string; skills: z.array(z.object({
image: string; icon: z.string(),
imageAlt: string; label: z.string(),
} })),
});
export interface FinancialHelpSection { const insuranceSectionSchema = z.object({
type: 'financial-help'; type: z.literal('insurance'),
heading: string; heading: z.string(),
body: string; body: z.string(),
cta: { label: string; href: string }; logos: z.array(z.object({
} file: z.string(),
alt: z.string(),
})),
});
export interface ProcessSection { const esaSectionSchema = z.object({
type: 'process'; type: z.literal('esa'),
heading: string; heading: z.string(),
steps: { icon: string; label: string }[]; body: z.string(),
} image: z.string(),
imageAlt: z.string(),
});
export interface DirectorSection { const financialHelpSectionSchema = z.object({
type: 'director'; type: z.literal('financial-help'),
heading: string; heading: z.string(),
quote: string; body: z.string(),
photo: string; cta: ctaSchema,
photoAlt: string; });
name: string;
credentials: string;
signature: string;
signatureAlt: string;
}
export interface TestimonialsSection { const processSectionSchema = z.object({
type: 'testimonials'; type: z.literal('process'),
heading: string; heading: z.string(),
items: { author: string; text: string }[]; steps: z.array(z.object({
} icon: z.string(),
label: z.string(),
})),
});
export type HomeSection = const directorSectionSchema = z.object({
| HeroSection type: z.literal('director'),
| ServicesIntroSection heading: z.string(),
| BenefitsSection quote: z.string(),
| SkillsSection photo: z.string(),
| InsuranceSection photoAlt: z.string(),
| EsaSection name: z.string(),
| FinancialHelpSection credentials: z.string(),
| ProcessSection signature: z.string(),
| DirectorSection signatureAlt: z.string(),
| TestimonialsSection; });
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>;