feat: structured victories/education with photos, links, and editorial profile layout
- Add VictoryItem type (place, category, competition, location, date, image, link) - Add RichListItem type for education with image/link support - Backward-compatible DB parsing for old string[] formats - Admin forms with structured fields and image upload per item - Victory/education cards with photo overlay and lightbox - Remove max-width constraint from trainer profile for full-width layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import Database from "better-sqlite3";
|
||||
import path from "path";
|
||||
import type { SiteContent, TeamMember } from "@/types/content";
|
||||
import type { SiteContent, TeamMember, RichListItem, VictoryItem } from "@/types/content";
|
||||
|
||||
const DB_PATH =
|
||||
process.env.DATABASE_PATH ||
|
||||
@@ -88,6 +88,32 @@ function parseJsonArray(val: string | null): string[] | undefined {
|
||||
try { const arr = JSON.parse(val); return Array.isArray(arr) && arr.length > 0 ? arr : undefined; } catch { return undefined; }
|
||||
}
|
||||
|
||||
function parseRichList(val: string | null): RichListItem[] | undefined {
|
||||
if (!val) return undefined;
|
||||
try {
|
||||
const arr = JSON.parse(val);
|
||||
if (!Array.isArray(arr) || arr.length === 0) return undefined;
|
||||
// Handle both old string[] and new RichListItem[] formats
|
||||
return arr.map((item: string | RichListItem) =>
|
||||
typeof item === "string" ? { text: item } : item
|
||||
);
|
||||
} catch { return undefined; }
|
||||
}
|
||||
|
||||
function parseVictories(val: string | null): VictoryItem[] | undefined {
|
||||
if (!val) return undefined;
|
||||
try {
|
||||
const arr = JSON.parse(val);
|
||||
if (!Array.isArray(arr) || arr.length === 0) return undefined;
|
||||
// Handle old string[], old RichListItem[], and new VictoryItem[] formats
|
||||
return arr.map((item: string | Record<string, unknown>) => {
|
||||
if (typeof item === "string") return { place: "", category: "", competition: item };
|
||||
if ("text" in item && !("competition" in item)) return { place: "", category: "", competition: item.text as string, image: item.image as string | undefined, link: item.link as string | undefined };
|
||||
return item as unknown as VictoryItem;
|
||||
});
|
||||
} catch { return undefined; }
|
||||
}
|
||||
|
||||
export function getTeamMembers(): (TeamMember & { id: number })[] {
|
||||
const db = getDb();
|
||||
const rows = db
|
||||
@@ -101,8 +127,8 @@ export function getTeamMembers(): (TeamMember & { id: number })[] {
|
||||
instagram: r.instagram ?? undefined,
|
||||
description: r.description ?? undefined,
|
||||
experience: parseJsonArray(r.experience),
|
||||
victories: parseJsonArray(r.victories),
|
||||
education: parseJsonArray(r.education),
|
||||
victories: parseVictories(r.victories),
|
||||
education: parseRichList(r.education),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -122,8 +148,8 @@ export function getTeamMember(
|
||||
instagram: r.instagram ?? undefined,
|
||||
description: r.description ?? undefined,
|
||||
experience: parseJsonArray(r.experience),
|
||||
victories: parseJsonArray(r.victories),
|
||||
education: parseJsonArray(r.education),
|
||||
victories: parseVictories(r.victories),
|
||||
education: parseRichList(r.education),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user