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) && (
+
+ )}
+
{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",