Replace the front page with an admin-driven home
The home page is rebuilt from scratch and configured from a new Front page tab in the admin, backed by migration 017 and served by GET /api/front-page. Hero: brand, photos (crossfading slideshow with progress and pause) or livestream (YouTube/Facebook/Vimeo embed with a LIVE badge), switched by hand. After it, bands the admin can reorder, retitle or hide: a countdown to the next event (series-aware), the National Retreats carousel, a numbers band (typed in or counted from the database), a horizontal rail of featured timeline entries, and a "Find your way in" pathfinder replacing the old connect section. The CRUD engine gains a `singleton` flag: the entity has one row, made by its migration, and create and delete are refused. The list screen opens that row and the editor drops the slug, back link and delete. shapeSeries moves to shape.js so /front-page can share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b4b013209b
commit
6a69084de2
25 changed files with 2299 additions and 494 deletions
|
|
@ -13,6 +13,8 @@
|
|||
value (organizations.kind decides whether a
|
||||
regions or chapters row should exist)
|
||||
children ordered collections, replaced wholesale on save
|
||||
singleton the one id this entity ever has; the engine
|
||||
refuses create and delete (see front_page)
|
||||
|
||||
Replacing children wholesale is only safe because nothing has a
|
||||
foreign key INTO these tables. That is the dividing line, and
|
||||
|
|
@ -650,7 +652,133 @@ const timeline = {
|
|||
],
|
||||
};
|
||||
|
||||
export const ENTITIES = { organizations, events, people, teams, awards, timeline };
|
||||
/* ── Front page ──────────────────────────────────────────────────
|
||||
|
||||
A singleton: one row, id 'home', created by migration 017 and
|
||||
never by the admin. `singleton` tells the engine to refuse create
|
||||
and delete, and the CHECK on front_page.id is what makes a second
|
||||
row impossible even without it.
|
||||
|
||||
Every collection here is owned by page_id and replaced wholesale.
|
||||
That is safe for the same reason it is for links and blocks —
|
||||
nothing has a foreign key into these tables — and paths carry
|
||||
their actions as a nested collection, the shape content blocks
|
||||
and their items already use. */
|
||||
|
||||
const frontPage = {
|
||||
key: "front_page",
|
||||
table: "front_page",
|
||||
idColumn: "id",
|
||||
idKind: "slug",
|
||||
singleton: "home",
|
||||
concurrency: "updated_at",
|
||||
|
||||
list: {
|
||||
columns: ["id", "headline", "hero_mode", "updated_at"],
|
||||
filters: [],
|
||||
search: [],
|
||||
order: "id",
|
||||
},
|
||||
|
||||
columns: [
|
||||
enumeration("hero_mode", ["brand", "photos", "livestream"]),
|
||||
text("eyebrow"),
|
||||
text("headline"),
|
||||
text("subhead"),
|
||||
text("primary_label"),
|
||||
text("primary_url"),
|
||||
text("secondary_label"),
|
||||
text("secondary_url"),
|
||||
int("slide_seconds"),
|
||||
text("livestream_url"),
|
||||
text("livestream_title"),
|
||||
text("countdown_event_id"),
|
||||
],
|
||||
|
||||
children: [
|
||||
{
|
||||
key: "slides",
|
||||
table: "front_page_slides",
|
||||
owner: { column: "page_id" },
|
||||
order: "sort_order",
|
||||
columns: [
|
||||
text("media", { required: true }),
|
||||
text("alt"),
|
||||
text("caption"),
|
||||
text("link_url"),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "sections",
|
||||
table: "front_page_sections",
|
||||
owner: { column: "page_id" },
|
||||
order: "sort_order",
|
||||
columns: [
|
||||
enumeration(
|
||||
"section",
|
||||
["countdown", "retreats", "stats", "timeline", "connect"],
|
||||
{ required: true },
|
||||
),
|
||||
text("title"),
|
||||
text("blurb"),
|
||||
bool("is_hidden"),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "stats",
|
||||
table: "front_page_stats",
|
||||
owner: { column: "page_id" },
|
||||
order: "sort_order",
|
||||
columns: [
|
||||
text("label", { required: true }),
|
||||
enumeration("source", [
|
||||
"manual",
|
||||
"years_since",
|
||||
"regions",
|
||||
"chapters",
|
||||
"partners",
|
||||
"events_held",
|
||||
"retreats_held",
|
||||
"people",
|
||||
"awards_given",
|
||||
]),
|
||||
text("value"),
|
||||
text("suffix"),
|
||||
text("note"),
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "paths",
|
||||
table: "front_page_paths",
|
||||
owner: { column: "page_id" },
|
||||
order: "sort_order",
|
||||
columns: [text("label", { required: true }), text("icon"), text("blurb")],
|
||||
children: [
|
||||
{
|
||||
key: "actions",
|
||||
table: "front_page_path_actions",
|
||||
owner: { column: "path_id" },
|
||||
order: "sort_order",
|
||||
columns: [
|
||||
text("label", { required: true }),
|
||||
text("description"),
|
||||
text("url", { required: true }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const ENTITIES = {
|
||||
organizations,
|
||||
events,
|
||||
people,
|
||||
teams,
|
||||
awards,
|
||||
timeline,
|
||||
front_page: frontPage,
|
||||
};
|
||||
|
||||
/* ── Options for the form's select inputs ────────────────────── */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue