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:
@@ -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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 />& 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
|
||||
|
||||
@@ -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
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user