fix: QR menu branding fields — display name, header image, empty-to-hide

- menu_display_name is now separate from the site's internal name (was
  incorrectly showing the sysadmin-only site name on the public menu).
- Header image now resolves against the cloud API origin instead of
  the menu-app's own origin, fixing the broken-image icon.
- Tagline/blurb/hours now genuinely hide when cleared: the sysadmin
  save handler was converting empty fields to null, which the PUT
  endpoint's exclude_none silently drops instead of clearing.
- Added a settable blurb (EN/GR) below the tagline, previously
  hardcoded ("Fresh seasonal plates, served with care.").
- Collapsed hours from two fields (EN/GR) to one — opening hours don't
  vary by language.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 10:44:28 +03:00
parent f5736b85cb
commit a73f081ca1
7 changed files with 101 additions and 59 deletions

View File

@@ -35,10 +35,12 @@ export default function SiteDetailPage() {
// Menu settings form state
const [menuMode, setMenuMode] = useState('order')
const [displayName, setDisplayName] = useState('')
const [taglineEn, setTaglineEn] = useState('')
const [taglineGr, setTaglineGr] = useState('')
const [hoursEn, setHoursEn] = useState('')
const [hoursGr, setHoursGr] = useState('')
const [blurbEn, setBlurbEn] = useState('')
const [blurbGr, setBlurbGr] = useState('')
const [hours, setHours] = useState('')
const [headerImageFile, setHeaderImageFile] = useState(null)
const [uploadingHeaderImage, setUploadingHeaderImage] = useState(false)
@@ -177,10 +179,12 @@ export default function SiteDetailPage() {
try {
const { data } = await client.put(`/api/sites/${siteId}`, {
menu_mode: menuMode,
menu_tagline_en: taglineEn.trim() || null,
menu_tagline_gr: taglineGr.trim() || null,
menu_hours_en: hoursEn.trim() || null,
menu_hours_gr: hoursGr.trim() || null,
menu_display_name: displayName.trim(),
menu_tagline_en: taglineEn.trim(),
menu_tagline_gr: taglineGr.trim(),
menu_blurb_en: blurbEn.trim(),
menu_blurb_gr: blurbGr.trim(),
menu_hours: hours.trim(),
})
setSite(data)
setModal(null)
@@ -347,10 +351,12 @@ export default function SiteDetailPage() {
<button
onClick={() => {
setMenuMode(site.menu_mode || 'order')
setDisplayName(site.menu_display_name || '')
setTaglineEn(site.menu_tagline_en || '')
setTaglineGr(site.menu_tagline_gr || '')
setHoursEn(site.menu_hours_en || '')
setHoursGr(site.menu_hours_gr || '')
setBlurbEn(site.menu_blurb_en || '')
setBlurbGr(site.menu_blurb_gr || '')
setHours(site.menu_hours || '')
setModal('menu_settings')
}}
className="text-xs text-cyan-400 hover:text-cyan-300 transition-colors"
@@ -365,6 +371,10 @@ export default function SiteDetailPage() {
{site.menu_mode === 'view_only' ? 'View Menu Only' : 'Ordering Enabled'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Display name</span>
<span className="text-gray-300">{site.menu_display_name || <span className="text-gray-600 italic">Not set</span>}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-500">Header image</span>
<span className="text-gray-300">{site.menu_header_image_url ? 'Set' : '—'}</span>
@@ -655,6 +665,20 @@ export default function SiteDetailPage() {
</p>
</div>
<div>
<label className="block text-xs text-gray-400 mb-1.5">Display name</label>
<input
type="text"
value={displayName}
onChange={e => setDisplayName(e.target.value)}
placeholder="Olive & Thyme"
className="w-full bg-gray-800 border border-gray-600 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-cyan-500 placeholder-gray-600"
/>
<p className="text-xs text-gray-500 mt-1.5">
Shown on the public menu header. Separate from the internal site name above leave empty to show a generic "Our Menu" title (or hide it entirely once a header image is set).
</p>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-gray-400 mb-1.5">Tagline (EN)</label>
@@ -680,27 +704,38 @@ export default function SiteDetailPage() {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-gray-400 mb-1.5">Hours (EN)</label>
<label className="block text-xs text-gray-400 mb-1.5">Blurb (EN)</label>
<input
type="text"
value={hoursEn}
onChange={e => setHoursEn(e.target.value)}
placeholder="Open today · 12:00 23:30"
value={blurbEn}
onChange={e => setBlurbEn(e.target.value)}
placeholder="Fresh seasonal plates, served with care."
className="w-full bg-gray-800 border border-gray-600 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-cyan-500 placeholder-gray-600"
/>
</div>
<div>
<label className="block text-xs text-gray-400 mb-1.5">Hours (GR)</label>
<label className="block text-xs text-gray-400 mb-1.5">Blurb (GR)</label>
<input
type="text"
value={hoursGr}
onChange={e => setHoursGr(e.target.value)}
placeholder="Ανοιχτά σήμερα · 12:00 23:30"
value={blurbGr}
onChange={e => setBlurbGr(e.target.value)}
placeholder="Εποχιακά πιάτα, με αγάπη."
className="w-full bg-gray-800 border border-gray-600 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-cyan-500 placeholder-gray-600"
/>
</div>
</div>
<p className="text-xs text-gray-500">Leave a field empty to clear it from the menu header.</p>
<div>
<label className="block text-xs text-gray-400 mb-1.5">Hours</label>
<input
type="text"
value={hours}
onChange={e => setHours(e.target.value)}
placeholder="Open today · 12:00 23:30"
className="w-full bg-gray-800 border border-gray-600 text-white text-sm rounded-lg px-3 py-2 focus:outline-none focus:ring-1 focus:ring-cyan-500 placeholder-gray-600"
/>
</div>
<p className="text-xs text-gray-500">Leave a field empty to hide it from the menu header.</p>
</div>
</ConfirmModal>
)}