feat: build home page from home.json sections
- Hero with callsign/locator tags and CTAs on khaki grid - Feature sections for ARD (YM1KTC), DMR Türkiye, QRV73!, QRZ73! - Telegram groups and resource site card grids - Latest posts block with empty state (wired to blog in stage 4) - Section wrapper, decorative numerals, external Button variant Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+62
-1
@@ -5,8 +5,10 @@
|
||||
import { z } from 'astro/zod';
|
||||
import siteJson from '../data/site.json';
|
||||
import socialJson from '../data/social.json';
|
||||
import homeJson from '../data/home.json';
|
||||
|
||||
const link = z.object({ label: z.string().min(1), href: z.string().min(1) });
|
||||
const icon = z.string().startsWith('tabler:');
|
||||
|
||||
const siteSchema = z.object({
|
||||
name: z.string(),
|
||||
@@ -47,11 +49,68 @@ const socialSchema = z.array(
|
||||
label: z.string(),
|
||||
// Empty string = account not set → hidden in the UI.
|
||||
href: z.union([z.url(), z.literal('')]),
|
||||
icon: z.string().startsWith('tabler:'),
|
||||
icon,
|
||||
handle: z.string().optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const homeSchema = z.object({
|
||||
seo: z.object({ description: z.string().max(160) }),
|
||||
hero: z.object({
|
||||
numeral: z.string(),
|
||||
tags: z.array(z.object({ icon, label: z.string() })),
|
||||
asideTitle: z.string(),
|
||||
asideBody: z.string(),
|
||||
asideLink: link,
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
lead: z.string(),
|
||||
ctas: z.array(
|
||||
link.extend({ variant: z.enum(['primary', 'secondary', 'link']), icon: icon.optional() }),
|
||||
),
|
||||
}),
|
||||
sections: z.array(
|
||||
z.discriminatedUnion('type', [
|
||||
z.object({
|
||||
type: z.literal('feature'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
body: z.string(),
|
||||
callsign: z.string().optional(),
|
||||
callsignLabel: z.string().optional(),
|
||||
icon,
|
||||
link: link.extend({ href: z.url() }),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('cards'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional(),
|
||||
href: z.url(),
|
||||
icon,
|
||||
}),
|
||||
)
|
||||
.min(1),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal('latestPosts'),
|
||||
id: z.string(),
|
||||
eyebrow: z.string(),
|
||||
title: z.string(),
|
||||
count: z.number().int().positive(),
|
||||
allLink: link,
|
||||
empty: z.string(),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
});
|
||||
|
||||
function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
||||
const result = schema.safeParse(data);
|
||||
if (!result.success) {
|
||||
@@ -61,7 +120,9 @@ function parse<T>(name: string, schema: z.ZodType<T>, data: unknown): T {
|
||||
}
|
||||
|
||||
export const site = parse('site.json', siteSchema, siteJson);
|
||||
export const home = parse('home.json', homeSchema, homeJson);
|
||||
export const social = parse('social.json', socialSchema, socialJson).filter((s) => s.href);
|
||||
|
||||
export type Site = z.infer<typeof siteSchema>;
|
||||
export type HomeSection = (typeof home.sections)[number];
|
||||
export type SocialLink = (typeof social)[number];
|
||||
|
||||
Reference in New Issue
Block a user