From 8d988515f31fd32a4f2132c4e15f1880bbd096e8 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Tue, 8 Sep 2026 22:32:00 +0200 Subject: [PATCH] feat: make /imkerei and /herkunft hero images admin-editable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both pages are the ones most directly meant to give customers "ein gutes Bild von der Imkerei" (per the active /goal), but their hero images were still hardcoded PlaceholderImage tiles with no way to ever swap in a real photo — unlike every homepage section, which already went through this exact upload flow. Added imkerei.image / herkunft.image to the content registry (new "pages" group, since neither belongs under "Homepage") and switched both pages from PlaceholderImage to ContentImage + getContentValue, the same pattern already proven on the homepage sections. Falls back to the identical placeholder tile until a photo is uploaded, so this is a pure addition with no visual change until the owner uploads one. Verified: tsc clean, 151/151 tests pass, both pages still render correctly (curl 200 + screenshot) with the placeholder fallback. Could not verify the admin upload UI end-to-end live — the local admin account has 2FA enabled and I don't have the second factor; confidence instead comes from reusing the identical, already-verified AdminImageField/ContentImage wiring used for the four home.*.image fields, plus generic (group-agnostic) admin field loading confirmed by reading admin/inhalte/page.tsx. Co-Authored-By: Claude Sonnet 5 --- src/app/admin/inhalte/page.tsx | 1 + src/app/herkunft/page.tsx | 11 ++++++++--- src/app/imkerei/page.tsx | 15 ++++++++++++--- src/i18n/dictionaries/de.ts | 1 + src/i18n/dictionaries/en.ts | 1 + src/i18n/dictionary-type.ts | 1 + src/lib/content-registry.ts | 6 +++++- 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/app/admin/inhalte/page.tsx b/src/app/admin/inhalte/page.tsx index a16c274..c808e2e 100644 --- a/src/app/admin/inhalte/page.tsx +++ b/src/app/admin/inhalte/page.tsx @@ -33,6 +33,7 @@ export default async function AdminInhaltePage(props: PageProps<"/admin/inhalte" const groups: { key: ContentFieldDef["group"]; heading: string }[] = [ { key: "company", heading: t.adminContentPage.groupCompany }, { key: "home", heading: t.adminContentPage.groupHome }, + { key: "pages", heading: t.adminContentPage.groupPages }, { key: "social", heading: t.adminContentPage.groupSocial }, { key: "legal", heading: t.adminContentPage.groupLegal }, ]; diff --git a/src/app/herkunft/page.tsx b/src/app/herkunft/page.tsx index 443de57..78fd45b 100644 --- a/src/app/herkunft/page.tsx +++ b/src/app/herkunft/page.tsx @@ -1,8 +1,9 @@ import type { Metadata } from "next"; import { Container, SectionHeading } from "@/components/ui"; -import { PlaceholderImage } from "@/components/placeholder-image"; +import { ContentImage } from "@/components/content-image"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; +import { getContentValue } from "@/lib/content"; export async function generateMetadata(): Promise { const t = getDictionary(await getLocale()); @@ -14,10 +15,14 @@ export async function generateMetadata(): Promise { } export default async function HerkunftPage() { - const t = getDictionary(await getLocale()); + const locale = await getLocale(); + const t = getDictionary(locale); + const image = await getContentValue("herkunft.image", locale); return (
- { const t = getDictionary(await getLocale()); @@ -14,7 +15,9 @@ export async function generateMetadata(): Promise { } export default async function ImkereiPage() { - const t = getDictionary(await getLocale()); + const locale = await getLocale(); + const t = getDictionary(locale); + const image = await getContentValue("imkerei.image", locale); return (
@@ -30,7 +33,13 @@ export default async function ImkereiPage() { ))}
- + ); diff --git a/src/i18n/dictionaries/de.ts b/src/i18n/dictionaries/de.ts index 5bd4762..0e52266 100644 --- a/src/i18n/dictionaries/de.ts +++ b/src/i18n/dictionaries/de.ts @@ -1114,6 +1114,7 @@ export const de: Dictionary = { "Diese Texte erscheinen auf der Website. Ein leeres Feld bedeutet nicht \"leer auf der Seite\" — dann wird der ursprüngliche Text weiter angezeigt. Trage nur ein, was du ändern möchtest.", groupCompany: "Firmendaten", groupHome: "Homepage", + groupPages: "Weitere Seiten", groupLegal: "Rechtstexte", groupSocial: "Social Media", deLabel: "Deutsch", diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts index 8a9b815..f6728e1 100644 --- a/src/i18n/dictionaries/en.ts +++ b/src/i18n/dictionaries/en.ts @@ -1114,6 +1114,7 @@ export const en: Dictionary = { "These texts appear on the website. An empty field does not mean \"blank on the page\" — the original text keeps showing. Only fill in what you want to change.", groupCompany: "Company details", groupHome: "Homepage", + groupPages: "Other pages", groupLegal: "Legal pages", groupSocial: "Social media", deLabel: "German", diff --git a/src/i18n/dictionary-type.ts b/src/i18n/dictionary-type.ts index 3655514..d679df6 100644 --- a/src/i18n/dictionary-type.ts +++ b/src/i18n/dictionary-type.ts @@ -813,6 +813,7 @@ export interface Dictionary { intro: string; groupCompany: string; groupHome: string; + groupPages: string; groupLegal: string; groupSocial: string; deLabel: string; diff --git a/src/lib/content-registry.ts b/src/lib/content-registry.ts index 3926df7..77c4d66 100644 --- a/src/lib/content-registry.ts +++ b/src/lib/content-registry.ts @@ -14,7 +14,7 @@ export type ContentFieldType = export interface ContentFieldDef { key: string; - group: "company" | "home" | "legal" | "social"; + group: "company" | "home" | "pages" | "legal" | "social"; labelDe: string; labelEn: string; type: ContentFieldType; @@ -83,6 +83,10 @@ export const CONTENT_REGISTRY: ContentFieldDef[] = [ { key: "home.regionalSection.image", group: "home", labelDe: "Regional: Bild", labelEn: "Regional: Image", type: "image", localized: false, getDefault: () => "" }, { key: "home.businessTeaser.image", group: "home", labelDe: "Geschäftskunden-Hinweis: Bild", labelEn: "Business teaser: Image", type: "image", localized: false, getDefault: () => "" }, + // --- Weitere Seiten (nicht lokalisiert) --- + { key: "imkerei.image", group: "pages", labelDe: "Imkerei-Seite: Bild", labelEn: "Imkerei page: Image", type: "image", localized: false, getDefault: () => "" }, + { key: "herkunft.image", group: "pages", labelDe: "Herkunft-Seite: Bild", labelEn: "Herkunft page: Image", type: "image", localized: false, getDefault: () => "" }, + // --- Social Media (nicht lokalisiert, leer = Icon wird im Footer nicht // angezeigt, siehe components/site-footer.tsx Footer()) --- { key: "social.tiktok", group: "social", labelDe: "TikTok-Profil (URL)", labelEn: "TikTok profile (URL)", type: "text", localized: false, getDefault: () => "" },