From f8606a45ff44750dbb16432adadd9739ea0fb716 Mon Sep 17 00:00:00 2001 From: DavidHerran Date: Mon, 4 May 2026 15:24:06 -0500 Subject: [PATCH] feat: branding asset serving + footer email/phone fields Two changes that together make Site Settings actually work end-to-end. BRANDING ASSET SERVING (the broken thumbnails fix) The favicon/logo previews were broken because uploaded files in /public/branding had no path to reach the browser: 1. The folder wasn't mounted into the app container, so uploads vanished on next deploy 2. Nginx had no location block, so /branding/foo.png returned 404 (everything not in cases/applications/news/parts/footage was a proxy_pass to Next.js, which doesn't serve from /public/branding in standalone mode) Fix: - docker-compose.yml: ./public/branding mounted to /app/public/branding (write side) AND /srv/branding (read-only side for Nginx) - nginx/conf.d/flux.conf: new "location /branding/" block, same cache strategy as the other asset locations (max-age=300, must-revalidate) FOOTER EMAIL + PHONE (David's request) - siteSettingsTypes.ts: hqEmail and hqPhone fields added to FooterSettings, pre-filled with sales@lethepowerflux.com and +39 0424 287 492 - Footer.tsx: clickable mailto: and tel: links with Mail / Phone icons shown right under the HQ address. Hidden when fields are empty so the layout stays clean for editors who want to suppress contact info. - /hq-command/dashboard/settings: new "Headquarters contact" group in the Footer tab with the two fields (auto-translate ignores them, since emails and phone numbers don't need translation). DEPLOY (David) cd /opt/flux-srl mkdir -p public/branding # one-time, creates the folder if missing git pull docker compose up -d --build app docker compose exec nginx nginx -t docker compose exec nginx nginx -s reload --- docker-compose.yml | 2 ++ nginx/conf.d/flux.conf | 6 +++++ .../hq-command/dashboard/settings/page.tsx | 15 +++++++++++ src/components/layout/Footer.tsx | 25 ++++++++++++++++++- src/lib/siteSettingsTypes.ts | 4 +++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index b7ed86c..58d905e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,6 +65,7 @@ services: - ./public/news:/app/public/news - ./public/parts:/app/public/parts - ./public/operations-inbox:/app/public/operations-inbox + - ./public/branding:/app/public/branding networks: - flux-net expose: @@ -88,6 +89,7 @@ services: - ./public/parts:/srv/parts:ro - ./public/footage:/srv/footage:ro - ./public/operations-inbox:/srv/operations-inbox:ro + - ./public/branding:/srv/branding:ro depends_on: - app networks: diff --git a/nginx/conf.d/flux.conf b/nginx/conf.d/flux.conf index 83bf6c7..a5ad8fd 100644 --- a/nginx/conf.d/flux.conf +++ b/nginx/conf.d/flux.conf @@ -163,6 +163,12 @@ server { access_log off; } + location /branding/ { + alias /srv/branding/; + add_header Cache-Control "public, max-age=300, must-revalidate" always; + access_log off; + } + location / { proxy_pass http://nextjs; proxy_set_header Host $host; diff --git a/src/app/hq-command/dashboard/settings/page.tsx b/src/app/hq-command/dashboard/settings/page.tsx index ea79ac8..478a391 100644 --- a/src/app/hq-command/dashboard/settings/page.tsx +++ b/src/app/hq-command/dashboard/settings/page.tsx @@ -275,6 +275,21 @@ function FooterTab({ onChange({ ...value, hqCountry: v })} /> + + onChange({ ...value, hqEmail: v })} + placeholder="sales@lethepowerflux.com" + /> + onChange({ ...value, hqPhone: v })} + placeholder="+39 0424 287 492" + /> + + onChange({ ...value, copyrightHolder: v })} /> onChange({ ...value, privacyUrl: v })} /> diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 2442f68..02f15bc 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,5 +1,5 @@ import { Link } from "@/i18n/routing"; -import { Linkedin, Instagram, Youtube, Mail } from "lucide-react"; +import { Linkedin, Instagram, Youtube, Mail, Phone } from "lucide-react"; import { prisma } from "@/lib/prisma"; import { getLocalizedData } from "@/lib/i18nHelper"; import { getTranslations, getLocale } from "next-intl/server"; @@ -97,6 +97,29 @@ export default async function Footer() { {footer.hqRegion}, {footer.hqCountry}

+ {(footer.hqEmail || footer.hqPhone) && ( +
+ {footer.hqEmail && ( + + + {footer.hqEmail} + + )} + {footer.hqPhone && ( + + + {footer.hqPhone} + + )} +
+ )} + {socialLinks.length > 0 && (
{socialLinks.map(({ url, icon: Icon, label }) => ( diff --git a/src/lib/siteSettingsTypes.ts b/src/lib/siteSettingsTypes.ts index b2afab3..81ba65a 100644 --- a/src/lib/siteSettingsTypes.ts +++ b/src/lib/siteSettingsTypes.ts @@ -21,6 +21,8 @@ export interface FooterSettings { hqCity: string; hqRegion: string; hqCountry: string; + hqEmail: string; // e.g. sales@lethepowerflux.com + hqPhone: string; // e.g. +39 0424 287 492 copyrightHolder: string; privacyUrl: string; termsUrl: string; @@ -51,6 +53,8 @@ export const DEFAULT_FOOTER: FooterSettings = { hqCity: "36060 Romano d'Ezzelino", hqRegion: "Vicenza", hqCountry: "Italy", + hqEmail: "sales@lethepowerflux.com", + hqPhone: "+39 0424 287 492", copyrightHolder: "FLUX Srl", privacyUrl: "/privacy", termsUrl: "/terms",