feat: make /imkerei and /herkunft hero images admin-editable
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 <[email protected]>
This commit is contained in:
1 parent
3627f4e5c1
commit
8d988515f3
7 files changed
+29
-7
No files matched your search
@@ -33,6 +33,7 @@ export default async function AdminInhaltePage(props: PageProps<"/admin/inhalte"
|
|||||||
const groups: { key: ContentFieldDef["group"]; heading: string }[] = [
|
const groups: { key: ContentFieldDef["group"]; heading: string }[] = [
|
||||||
{ key: "company", heading: t.adminContentPage.groupCompany },
|
{ key: "company", heading: t.adminContentPage.groupCompany },
|
||||||
{ key: "home", heading: t.adminContentPage.groupHome },
|
{ key: "home", heading: t.adminContentPage.groupHome },
|
||||||
|
{ key: "pages", heading: t.adminContentPage.groupPages },
|
||||||
{ key: "social", heading: t.adminContentPage.groupSocial },
|
{ key: "social", heading: t.adminContentPage.groupSocial },
|
||||||
{ key: "legal", heading: t.adminContentPage.groupLegal },
|
{ key: "legal", heading: t.adminContentPage.groupLegal },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Container, SectionHeading } from "@/components/ui";
|
import { Container, SectionHeading } from "@/components/ui";
|
||||||
import { PlaceholderImage } from "@/components/placeholder-image";
|
import { ContentImage } from "@/components/content-image";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
import { getContentValue } from "@/lib/content";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
@@ -14,10 +15,14 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function HerkunftPage() {
|
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 (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<PlaceholderImage
|
<ContentImage
|
||||||
|
src={image || null}
|
||||||
|
alt={t.herkunftPage.eyebrow}
|
||||||
tone="forest"
|
tone="forest"
|
||||||
className="h-[50vh] min-h-[360px] w-full mb-20"
|
className="h-[50vh] min-h-[360px] w-full mb-20"
|
||||||
label={t.herkunftPage.eyebrow}
|
label={t.herkunftPage.eyebrow}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Container, SectionHeading } from "@/components/ui";
|
import { Container, SectionHeading } from "@/components/ui";
|
||||||
import { PlaceholderImage } from "@/components/placeholder-image";
|
import { ContentImage } from "@/components/content-image";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
import { getContentValue } from "@/lib/content";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
@@ -14,7 +15,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function ImkereiPage() {
|
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 (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="grid md:grid-cols-2 gap-14 items-center">
|
<Container className="grid md:grid-cols-2 gap-14 items-center">
|
||||||
@@ -30,7 +33,13 @@ export default async function ImkereiPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PlaceholderImage tone="honey" className="aspect-[4/5] w-full" label={t.imkereiPage.imageLabel} />
|
<ContentImage
|
||||||
|
src={image || null}
|
||||||
|
alt={t.imkereiPage.imageLabel}
|
||||||
|
tone="honey"
|
||||||
|
className="aspect-[4/5] w-full"
|
||||||
|
label={t.imkereiPage.imageLabel}
|
||||||
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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.",
|
"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",
|
groupCompany: "Firmendaten",
|
||||||
groupHome: "Homepage",
|
groupHome: "Homepage",
|
||||||
|
groupPages: "Weitere Seiten",
|
||||||
groupLegal: "Rechtstexte",
|
groupLegal: "Rechtstexte",
|
||||||
groupSocial: "Social Media",
|
groupSocial: "Social Media",
|
||||||
deLabel: "Deutsch",
|
deLabel: "Deutsch",
|
||||||
|
|||||||
@@ -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.",
|
"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",
|
groupCompany: "Company details",
|
||||||
groupHome: "Homepage",
|
groupHome: "Homepage",
|
||||||
|
groupPages: "Other pages",
|
||||||
groupLegal: "Legal pages",
|
groupLegal: "Legal pages",
|
||||||
groupSocial: "Social media",
|
groupSocial: "Social media",
|
||||||
deLabel: "German",
|
deLabel: "German",
|
||||||
|
|||||||
@@ -813,6 +813,7 @@ export interface Dictionary {
|
|||||||
intro: string;
|
intro: string;
|
||||||
groupCompany: string;
|
groupCompany: string;
|
||||||
groupHome: string;
|
groupHome: string;
|
||||||
|
groupPages: string;
|
||||||
groupLegal: string;
|
groupLegal: string;
|
||||||
groupSocial: string;
|
groupSocial: string;
|
||||||
deLabel: string;
|
deLabel: string;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type ContentFieldType =
|
|||||||
|
|
||||||
export interface ContentFieldDef {
|
export interface ContentFieldDef {
|
||||||
key: string;
|
key: string;
|
||||||
group: "company" | "home" | "legal" | "social";
|
group: "company" | "home" | "pages" | "legal" | "social";
|
||||||
labelDe: string;
|
labelDe: string;
|
||||||
labelEn: string;
|
labelEn: string;
|
||||||
type: ContentFieldType;
|
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.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: () => "" },
|
{ 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
|
// --- Social Media (nicht lokalisiert, leer = Icon wird im Footer nicht
|
||||||
// angezeigt, siehe components/site-footer.tsx Footer()) ---
|
// 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: () => "" },
|
{ key: "social.tiktok", group: "social", labelDe: "TikTok-Profil (URL)", labelEn: "TikTok profile (URL)", type: "text", localized: false, getDefault: () => "" },
|
||||||
|
|||||||
Reference in new issue
Block a user