fix(ui): mobile + dashboard polish + dev CSRF relaxation

- filter-bar: hide select / multi-select fields when the options list is
  empty (was rendering bare "Tags" / "Status" labels above empty inputs)
- berth-detail-header: show "Berth A1" title on mobile (was hidden via
  `hidden sm:block`)
- dashboard-shell: time-aware greeting (Good morning/afternoon/evening,
  firstName) using the existing ['me'] cache; falls back to
  "Welcome back" when firstName isn't set yet
- mobile-topbar: hide UUID-segment fallback title flash on detail-page
  navigation — when the URL last segment is a UUID, walk up to the
  parent collection name ("Clients", "Yachts") until the page sets the
  real entity title via useMobileChrome
- mobile-bottom-tabs: subtle bg-primary/10 pill behind icon on active
  tab for a clear "you are here" cue
- branded-auth-shell: lock to viewport via fixed/inset-0 so the iOS
  Safari rubber-band bounce doesn't scroll the centered login card
- middleware: skip CSRF origin check in development. LAN testing
  (real iPhone on 192.168.x.x hitting the Mac dev server while a Mac
  browser tab is on localhost) trips the cross-origin defense; prod
  keeps it as-is.
- package.json dev script: -H 0.0.0.0 so the dev server is reachable
  from devices on the LAN

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:58:42 +02:00
parent de8726a9b9
commit 979eadae48
6 changed files with 79 additions and 18 deletions

View File

@@ -21,13 +21,18 @@ export function MobileTopbar() {
const router = useRouter();
const pathname = usePathname();
// UUID detection — the URL's last segment on detail pages is the
// entity's UUID, and title-casing it produces an ugly "Abc 123 Uuid"
// flash before the page calls `useMobileChrome.setChrome({title: ...})`
// with the real entity name. When the segment matches the UUID shape,
// walk back to the parent collection segment ("clients", "yachts",
// "documents", …) which IS a clean, human-readable label.
const segments = pathname.split('/').filter(Boolean);
const last = segments[segments.length - 1] ?? '';
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(last);
const fallbackSegment = isUuid ? segments[segments.length - 2] : last;
const fallbackTitle =
pathname
.split('/')
.filter(Boolean)
.pop()
?.replace(/-/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase()) ?? 'Port Nimara';
fallbackSegment?.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) ?? 'Port Nimara';
return (
<header