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:
@@ -66,22 +66,31 @@ export function MobileBottomTabs({ onMoreClick }: { onMoreClick: () => void }) {
|
||||
href={`/${portSlug}/${tab.segment}` as any}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 h-14 text-xs',
|
||||
'relative flex flex-col items-center justify-center gap-0.5 h-14 text-xs transition-colors',
|
||||
active ? 'text-primary' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<Icon className="size-5" aria-hidden />
|
||||
<span className="font-medium">{tab.label}</span>
|
||||
{/* Subtle pill background behind the icon when active. Keeps the
|
||||
tab grid alignment intact while giving the eye an anchor. */}
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'absolute top-1.5 h-7 w-12 rounded-full transition-all',
|
||||
active ? 'bg-primary/10' : 'bg-transparent',
|
||||
)}
|
||||
/>
|
||||
<Icon className="relative size-5" aria-hidden />
|
||||
<span className="relative font-medium">{tab.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onMoreClick}
|
||||
className="flex flex-col items-center justify-center gap-0.5 h-14 text-xs text-muted-foreground"
|
||||
className="relative flex flex-col items-center justify-center gap-0.5 h-14 text-xs text-muted-foreground transition-colors"
|
||||
>
|
||||
<Menu className="size-5" aria-hidden />
|
||||
<span className="font-medium">More</span>
|
||||
<Menu className="relative size-5" aria-hidden />
|
||||
<span className="relative font-medium">More</span>
|
||||
</button>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user