Landing content and branding overrides β
Audience
This article is for GCM platform operators. Church admins should see the Website Builder branding article β that page is for their public site, this one is for the GCM marketing site at geniuschurchmanager.com.
The Landing and Branding tabs together control how the platform looks to a brand-new visitor: the marketing landing page, the login screen, the favicon in their browser tab, the logo in the sidebar of every signed-in workspace, the Open Graph image when somebody shares a link in Slack. All of this lives in two tables, edited from two tabs, and applies platform-wide the instant you save.

Two tabs, one purpose β
- Landing (
/platform/landing) edits the per-locale text and images that render on the marketing landing page. Stored as overrides inlanding_page_content; falls back to the i18n defaults baked into the bundle when no override exists. - Branding (
/platform/branding) edits everything else: platform name, tagline, logo, favicon, footer company, support email, theme colours, social links, the Guided Tour toggle. Stored in the flatplatform_settingskey-value table.
The two tabs share one media library, scoped to the uploads/platform/ Supabase storage path. Images uploaded for the landing page (platform/landing/) are also visible to the Branding tab (platform/branding/) and the PWA tab (platform/pwa/), and vice versa β pick once, reuse anywhere.
Landing content overrides β
The Landing tab lists nine sections of the marketing page, each as an accordion:
- Hero Section β top fold. Badge, headline, subheadline, CTA, "see pricing" link, optional illustration.
- Social Proof Bar β strip under the hero ("Trusted by 200+ churches").
- Problem Section β narrative block. Headline, description, quote, quote author, illustration.
- Features Grid β six product capabilities with title + description each.
- Scripture Interlude β verse + reference.
- How It Works β three-step explainer.
- More Features β bullet list. The
itemsfield is one item per line. - Pricing Preview β teaser block.
- Final CTA β closing banner.
A locale dropdown in the card header switches between English, EspaΓ±ol, FranΓ§ais, PortuguΓͺs. Each locale is a separate set of overrides; switching does not lose unsaved edits in the original locale (per-locale state is independent).
How the prefill works β
Every text input is pre-filled with the current effective text, not the override. That means:
- If no override exists, the input shows the i18n default from
src/i18n/locales/{en,es,fr,pt}.json. - If an override exists, the input shows the override value.
- The status badge tells you which: overridden (purple) or unsaved (amber).
This way, when you tweak the headline from "Know Your Flock" to "Know Your Flock Better", you don't have to retype the whole string β you edit the visible text and the system detects the diff. Blanking the field and saving is the canonical way to reset to the i18n default; the row is deleted from landing_page_content so the bundle text shows through.
Saving β
Three save scopes:
- Field-level Save β appears next to a field once you have edits. Persists just that field.
- Section-level Save β appears at the top of an accordion section when any of its fields are dirty. Persists all dirty fields in that section in one upsert.
- Section-level Reset β appears when any of the section's fields are overridden. Deletes those rows so the i18n defaults come back.
Saves invalidate two query caches: landing-content-admin (this tab) and landing-content (the live marketing page). The change appears on geniuschurchmanager.com on the next render.
Image fields β
The image fields (hero_image, problem_image, cta_image) use the shared StorageMediaLibraryDialog. Click Choose / upload to open the picker, which lets you upload a new file or pick from existing ones in uploads/platform/landing/, uploads/platform/branding/, or uploads/platform/pwa/.
The picker stores the path, not the URL β resolveStorageUrl turns it into a public URL at render time, so if you move the bucket later the references keep working. A small preview thumbnail and a "View full size" link sit next to the input.
Storage paths, not URLs
Pasting a full https://... URL into an image field works for the preview but bypasses the platform-wide URL resolver. Future bucket migrations will break it. Always pick from the library.
Branding tab β
The Branding tab is laid out in five sections:
1. Platform Identity β
- Platform Name β shown on the login page and the browser tab title.
- Tagline / Subtitle β shown under the platform name on the login page.
- Logo β square. Recommended 512x512 PNG or SVG. Picker scopes to
uploads/platform/branding/. - Favicon β small square. Recommended 32x32 or 64x64 ICO/PNG.
- Footer company / URL / support email / copyright year β used in the marketing footer and in transactional emails.
2. Colors & Theme β
Three colour pickers (primary, secondary, accent), each with a live hex input and a Live Preview swatch row at the bottom of the card. Changes apply globally via CSS variables β they affect the marketing site and the signed-in shell.
3. Marketing & SEO β
Hero title / subtitle / CTA / CTA link, plus the OG image URL and meta description (capped at 160 chars with a live counter). These are the platform-wide defaults; the Landing tab's hero override beats them when present.
4. Social Links β
Facebook, Instagram, Twitter / X, YouTube, TikTok URLs. Empty fields are hidden in the rendered footer.
5. Feature Toggles β
Currently one: Guided Tour. When on, new users get a step-by-step tour wizard on first login. The toggle writes to both platform_settings.guided_tour_enabled and platform_config.guided_tour_enabled to keep the two sources of truth aligned; if the mirror write fails, the toggle reverts and a toast tells you.
The "Save All" button β
Branding edits do not save per-field β they batch in local state until you press Save All Branding Settings at the bottom. Saving:
- Upserts every changed row into
platform_settingswith the new value and a freshupdated_at. - Invalidates the
platform_branding_settingscache. - Calls
refetchBranding()on theBrandingContextso the in-page sidebar, header, and theme colours refresh without a hard reload.
If the save fails halfway, the local state is preserved so you can retry β the page does not reset.
What changes are audited β
Branding and landing edits write to platform_audit_log with actions branding_settings_saved and landing_content_updated respectively. The before / after values are captured in metadata, so a future "who changed the hero copy three weeks ago" question has a clean answer in the Audit log.
