feat: branding asset serving + footer email/phone fields
Deploy to VPS / deploy (push) Has been cancelled
Deploy to VPS / deploy (push) Has been cancelled
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
This commit is contained in:
@@ -65,6 +65,7 @@ services:
|
|||||||
- ./public/news:/app/public/news
|
- ./public/news:/app/public/news
|
||||||
- ./public/parts:/app/public/parts
|
- ./public/parts:/app/public/parts
|
||||||
- ./public/operations-inbox:/app/public/operations-inbox
|
- ./public/operations-inbox:/app/public/operations-inbox
|
||||||
|
- ./public/branding:/app/public/branding
|
||||||
networks:
|
networks:
|
||||||
- flux-net
|
- flux-net
|
||||||
expose:
|
expose:
|
||||||
@@ -88,6 +89,7 @@ services:
|
|||||||
- ./public/parts:/srv/parts:ro
|
- ./public/parts:/srv/parts:ro
|
||||||
- ./public/footage:/srv/footage:ro
|
- ./public/footage:/srv/footage:ro
|
||||||
- ./public/operations-inbox:/srv/operations-inbox:ro
|
- ./public/operations-inbox:/srv/operations-inbox:ro
|
||||||
|
- ./public/branding:/srv/branding:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -163,6 +163,12 @@ server {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /branding/ {
|
||||||
|
alias /srv/branding/;
|
||||||
|
add_header Cache-Control "public, max-age=300, must-revalidate" always;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://nextjs;
|
proxy_pass http://nextjs;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -275,6 +275,21 @@ function FooterTab({
|
|||||||
<TextField label="Country" value={value.hqCountry} onChange={(v) => onChange({ ...value, hqCountry: v })} />
|
<TextField label="Country" value={value.hqCountry} onChange={(v) => onChange({ ...value, hqCountry: v })} />
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
|
|
||||||
|
<FieldGroup title="Headquarters contact">
|
||||||
|
<TextField
|
||||||
|
label="Sales email"
|
||||||
|
value={value.hqEmail}
|
||||||
|
onChange={(v) => onChange({ ...value, hqEmail: v })}
|
||||||
|
placeholder="sales@lethepowerflux.com"
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Phone (international format)"
|
||||||
|
value={value.hqPhone}
|
||||||
|
onChange={(v) => onChange({ ...value, hqPhone: v })}
|
||||||
|
placeholder="+39 0424 287 492"
|
||||||
|
/>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
<FieldGroup title="Legal">
|
<FieldGroup title="Legal">
|
||||||
<TextField label="Copyright holder" value={value.copyrightHolder} onChange={(v) => onChange({ ...value, copyrightHolder: v })} />
|
<TextField label="Copyright holder" value={value.copyrightHolder} onChange={(v) => onChange({ ...value, copyrightHolder: v })} />
|
||||||
<TextField label="Privacy policy URL" value={value.privacyUrl} onChange={(v) => onChange({ ...value, privacyUrl: v })} />
|
<TextField label="Privacy policy URL" value={value.privacyUrl} onChange={(v) => onChange({ ...value, privacyUrl: v })} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from "@/i18n/routing";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { getLocalizedData } from "@/lib/i18nHelper";
|
import { getLocalizedData } from "@/lib/i18nHelper";
|
||||||
import { getTranslations, getLocale } from "next-intl/server";
|
import { getTranslations, getLocale } from "next-intl/server";
|
||||||
@@ -97,6 +97,29 @@ export default async function Footer() {
|
|||||||
{footer.hqRegion}, {footer.hqCountry}
|
{footer.hqRegion}, {footer.hqCountry}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{(footer.hqEmail || footer.hqPhone) && (
|
||||||
|
<div className="flex flex-col gap-2 text-[#86868B] font-light">
|
||||||
|
{footer.hqEmail && (
|
||||||
|
<a
|
||||||
|
href={`mailto:${footer.hqEmail}`}
|
||||||
|
className="inline-flex items-center gap-2 hover:text-[#0066CC] dark:hover:text-[#00F0FF] transition-colors group"
|
||||||
|
>
|
||||||
|
<Mail size={14} className="text-[#86868B] group-hover:text-[#0066CC] dark:group-hover:text-[#00F0FF]" />
|
||||||
|
<span className="break-all">{footer.hqEmail}</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{footer.hqPhone && (
|
||||||
|
<a
|
||||||
|
href={`tel:${footer.hqPhone.replace(/\s+/g, "")}`}
|
||||||
|
className="inline-flex items-center gap-2 hover:text-[#0066CC] dark:hover:text-[#00F0FF] transition-colors group"
|
||||||
|
>
|
||||||
|
<Phone size={14} className="text-[#86868B] group-hover:text-[#0066CC] dark:group-hover:text-[#00F0FF]" />
|
||||||
|
<span>{footer.hqPhone}</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{socialLinks.length > 0 && (
|
{socialLinks.length > 0 && (
|
||||||
<div className="flex items-center gap-3 mt-2">
|
<div className="flex items-center gap-3 mt-2">
|
||||||
{socialLinks.map(({ url, icon: Icon, label }) => (
|
{socialLinks.map(({ url, icon: Icon, label }) => (
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export interface FooterSettings {
|
|||||||
hqCity: string;
|
hqCity: string;
|
||||||
hqRegion: string;
|
hqRegion: string;
|
||||||
hqCountry: string;
|
hqCountry: string;
|
||||||
|
hqEmail: string; // e.g. sales@lethepowerflux.com
|
||||||
|
hqPhone: string; // e.g. +39 0424 287 492
|
||||||
copyrightHolder: string;
|
copyrightHolder: string;
|
||||||
privacyUrl: string;
|
privacyUrl: string;
|
||||||
termsUrl: string;
|
termsUrl: string;
|
||||||
@@ -51,6 +53,8 @@ export const DEFAULT_FOOTER: FooterSettings = {
|
|||||||
hqCity: "36060 Romano d'Ezzelino",
|
hqCity: "36060 Romano d'Ezzelino",
|
||||||
hqRegion: "Vicenza",
|
hqRegion: "Vicenza",
|
||||||
hqCountry: "Italy",
|
hqCountry: "Italy",
|
||||||
|
hqEmail: "sales@lethepowerflux.com",
|
||||||
|
hqPhone: "+39 0424 287 492",
|
||||||
copyrightHolder: "FLUX Srl",
|
copyrightHolder: "FLUX Srl",
|
||||||
privacyUrl: "/privacy",
|
privacyUrl: "/privacy",
|
||||||
termsUrl: "/terms",
|
termsUrl: "/terms",
|
||||||
|
|||||||
Reference in New Issue
Block a user