diff --git a/next.config.ts b/next.config.ts index d2ab3a9..4a2c0d9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -72,6 +72,14 @@ const nextConfig: NextConfig = { turbopackFileSystemCacheForBuild: false, }, + // Das Favicon ist im Admin austauschbar und wird von app/icon.tsx erzeugt; + // eine statische app/favicon.ico gibt es deshalb nicht mehr. Browser und + // Crawler, die ohne direkt /favicon.ico abfragen, + // bekommen dasselbe Bild. + async rewrites() { + return [{ source: "/favicon.ico", destination: "/icon" }]; + }, + async headers() { // Content-Security-Policy wird bewusst NICHT hier gesetzt: sie braucht // eine pro Request neue Nonce (siehe proxy.ts), was ein statischer diff --git a/src/app/admin/inhalte/page.tsx b/src/app/admin/inhalte/page.tsx index ecafba6..f78f826 100644 --- a/src/app/admin/inhalte/page.tsx +++ b/src/app/admin/inhalte/page.tsx @@ -204,6 +204,7 @@ export default async function AdminInhaltePage() { const current = await loadCurrentValues(); const groups: { key: ContentFieldDef["group"]; heading: string }[] = [ + { key: "brand", heading: t.adminContentPage.groupBrand }, { key: "company", heading: t.adminContentPage.groupCompany }, { key: "shipping", heading: t.adminContentPage.groupShipping }, { key: "home", heading: t.adminContentPage.groupHome }, diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index 9577eda..5525f3f 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -7,6 +7,7 @@ import { logoutAction } from "@/app/konto/actions"; import { AdminShell } from "@/components/admin-shell"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; +import { getBrandAssets } from "@/lib/brand"; // Admin-Daten (Bestellungen, Bestand) muessen immer aktuell sein; zudem // entkoppelt das den Docker-Build-Schritt von einer DB-Verbindung. @@ -47,6 +48,7 @@ export default async function AdminLayout({ children }: LayoutProps<"/admin">) { // Seitenleiste. Nur echte Arbeit: bezahlte, noch nicht versendete oder // abgeholte Bestellungen (keine offenen Zahlungen), neue Retouren, // unfreigegebene Bewertungen, neue Firmenanfragen. Reine COUNT-Abfragen. + const brand = await getBrandAssets(); const [openOrders, openReturns, pendingReviews, newInquiries] = await Promise.all([ prisma.order.count({ where: { status: { in: ["BEZAHLT", "IN_VORBEREITUNG", "VERSANDBEREIT", "ABHOLBEREIT"] }, archivedAt: null }, @@ -59,6 +61,7 @@ export default async function AdminLayout({ children }: LayoutProps<"/admin">) { return ( - - - - - - - - - - - - - - - - - - - - - ), - { ...size } - ); +export default async function AppleIcon(): Promise { + const { favicon } = await getBrandAssets(); + const png = await renderBrandIcon(favicon, size.width, "#FAF8F3"); + return new Response(new Uint8Array(png), { + headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }, + }); } diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/icon.svg b/src/app/icon.svg deleted file mode 100644 index 821d152..0000000 --- a/src/app/icon.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/icon.tsx b/src/app/icon.tsx new file mode 100644 index 0000000..bb85361 --- /dev/null +++ b/src/app/icon.tsx @@ -0,0 +1,24 @@ +import { getBrandAssets } from "@/lib/brand"; +import { renderBrandIcon } from "@/lib/brand-icon"; + +export const size = { width: 512, height: 512 }; +export const contentType = "image/png"; + +// Das Favicon ist im Admin austauschbar (Inhalte > Logo und Favicon) — nicht +// zur Build-Zeit einfrieren. "no-cache" statt langem Caching: Browser halten +// Favicons sonst tagelang fest, ein neues Bild waere nicht zu sehen. +export const dynamic = "force-dynamic"; + +/** + * Favicon und Manifest-Icon. 512 px, weil dieselbe Datei auch als Android- + * Startbildschirm-Icon dient (app/manifest.ts); Browser skalieren fuer den + * Tab selbst herunter. /favicon.ico wird in next.config.ts hierher + * umgeleitet. + */ +export default async function Icon(): Promise { + const { favicon } = await getBrandAssets(); + const png = await renderBrandIcon(favicon, size.width); + return new Response(new Uint8Array(png), { + headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }, + }); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b0d6abf..2718f16 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -14,6 +14,7 @@ import { getSocialLinks } from "@/lib/social-links"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; import { I18nProvider } from "@/i18n/i18n-provider"; +import { getBrandAssets } from "@/lib/brand"; const cormorant = Cormorant_Garamond({ variable: "--font-cormorant", @@ -47,7 +48,12 @@ export async function generateMetadata(): Promise { export default async function RootLayout({ children }: LayoutProps<"/">) { const locale = await getLocale(); const dict = getDictionary(locale); - const [company, socialLinks, flash] = await Promise.all([getCompanyInfo(), getSocialLinks(), readFlash()]); + const [company, socialLinks, flash, brand] = await Promise.all([ + getCompanyInfo(), + getSocialLinks(), + readFlash(), + getBrandAssets(), + ]); const base = getSiteUrl(); // LocalBusiness-Schema fuer die gesamte Seite (einmal im Root-Layout statt @@ -131,13 +137,13 @@ export default async function RootLayout({ children }: LayoutProps<"/">) { {dict.nav.skipToContent} -
+
{children}
-