From 95d5661e738bd5fb5dc251cb32ce689d572d5bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 20:24:00 +0100 Subject: [PATCH] Intro and Hero are their own sections, and Intro comes first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The page reads hero-then-intro and this deliberately does not. The intro is the thing that gets edited; the hero is a path that changes once a season. Ordering by what somebody came to do beats mirroring the layout of the page they are describing. One PATCH per card, carrying only that card's field. `take()` on the server treats an absent key as leave-it-alone, so sending the whole object from either card would have each one saving the other's unsaved edits — a half-finished intro riding along with a hero somebody meant to change. Co-Authored-By: Claude Opus 5 --- web/sections/HomePanel.tsx | 60 ++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/web/sections/HomePanel.tsx b/web/sections/HomePanel.tsx index 9a3cbe9..8bcdd90 100644 --- a/web/sections/HomePanel.tsx +++ b/web/sections/HomePanel.tsx @@ -92,13 +92,18 @@ export const HomePanel = () => { } }; - const saveContent = () => - send('content', () => - clientRef.current.patch<{ data: Home }>('/fofos-admin/home', { - hero_url: heroUrl.trim() || null, - intro_text: intro.trim() || null, - }), - ); + /* + One PATCH per card, carrying only that card's field. + + `take()` on the server treats an absent key as "leave it alone", so sending the whole object from + either card would have each one saving the other's unsaved edits. Two cards, two independent + saves, and a half-finished intro cannot ride along with a hero somebody meant to change. + */ + const saveIntro = () => + send('intro', () => clientRef.current.patch<{ data: Home }>('/fofos-admin/home', { intro_text: intro.trim() || null })); + + const saveHero = () => + send('hero', () => clientRef.current.patch<{ data: Home }>('/fofos-admin/home', { hero_url: heroUrl.trim() || null })); const add = () => send('add', async () => { @@ -137,15 +142,23 @@ export const HomePanel = () => { ); } - const contentDirty = heroUrl.trim() !== (home.hero.url ?? '') || intro.trim() !== (home.intro_text ?? ''); + const introDirty = intro.trim() !== (home.intro_text ?? ''); + const heroDirty = heroUrl.trim() !== (home.hero.url ?? ''); return (
+ {/* + Intro first, then Hero. + + The page reads hero-then-intro, and this does not, deliberately: the intro is the thing that + gets edited, and the hero is a path that changes once a season. Ordering by what somebody came + to do beats mirroring the layout of the page they are describing. + */}
-

Hero

-

The video or image across the top of the page.

+

Intro

+

The paragraph under the hero.

{saved && ( @@ -155,6 +168,23 @@ export const HomePanel = () => { )}
+ {/* HTML in and HTML out — it is what the site's database already holds and renders. */} + + +
+ +
+
+ +
+
+

Hero

+

The video or image across the top of the page.

+
+ setHeroUrl(event.target.value)} @@ -172,17 +202,11 @@ export const HomePanel = () => {

)} -
-

Intro

- {/* HTML in and HTML out — it is what the site's database already holds and renders. */} - -
- {error &&

{error}

}
-