feat: mobile nav page + suppress desktop header on /mobile/* routes

- AppNavigation returns null on /mobile/* (no more horizontal scroll)
- Mobile header: 'Pulse' title (links home) + Menu icon (links to /mobile/nav)
- /mobile/nav: full-screen nav page with touch-friendly cards
  - Mobile Views: Dashboard, Tickets, Finance (large icon cards)
  - Full Site: Quotes, Config Items, Backup, Engagement, Ticket Digest, Admin
  - Sign out button
- Bottom tab bar unchanged (Dashboard / Tickets / Finance)
This commit is contained in:
lorentz 2026-03-23 12:58:09 -04:00
parent 44847ccf21
commit fea62382de
3 changed files with 99 additions and 3 deletions

View file

@ -2,7 +2,7 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { LayoutDashboard, Ticket, DollarSign } from 'lucide-react';
import { LayoutDashboard, Ticket, DollarSign, Menu } from 'lucide-react';
const NAV = [
{ href: '/mobile/dashboard', label: 'Dashboard', icon: LayoutDashboard },
@ -17,8 +17,10 @@ export default function MobileLayout({ children }: { children: React.ReactNode }
<div className="flex flex-col min-h-screen bg-background max-w-lg mx-auto">
{/* Top bar */}
<header className="sticky top-0 z-20 bg-background border-b px-4 py-3 flex items-center justify-between">
<span className="font-bold text-lg tracking-tight">Pulse</span>
<span className="text-xs text-muted-foreground">Wulf Consulting</span>
<Link href="/mobile" className="font-bold text-lg tracking-tight">Pulse</Link>
<Link href="/mobile/nav" className="p-1.5 rounded-lg hover:bg-accent transition-colors" aria-label="Navigation menu">
<Menu className="w-5 h-5" />
</Link>
</header>
{/* Page content */}

91
app/mobile/nav/page.tsx Normal file
View file

@ -0,0 +1,91 @@
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import {
LayoutDashboard, Ticket, DollarSign, ArrowLeft,
ExternalLink, Settings, LogOut, ChevronRight,
FileText, Server, HardDrive, Users, BarChart3,
} from 'lucide-react';
import { signOut } from '@/lib/auth-client';
const MAIN_SECTIONS = [
{ href: '/mobile/dashboard', label: 'Dashboard', icon: LayoutDashboard, description: 'Overview & stats', color: 'bg-blue-500/10 text-blue-600 dark:text-blue-400' },
{ href: '/mobile/tickets', label: 'Tickets', icon: Ticket, description: 'Open service tickets', color: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400' },
{ href: '/mobile/finance', label: 'Finance', icon: DollarSign, description: 'AR, invoices & payments', color: 'bg-violet-500/10 text-violet-600 dark:text-violet-400' },
];
const DESKTOP_LINKS = [
{ href: '/quotes', label: 'Quotes', icon: FileText },
{ href: '/configuration-items', label: 'Configuration Items', icon: Server },
{ href: '/backup-status', label: 'Backup Status', icon: HardDrive },
{ href: '/engagement', label: 'Engagement', icon: Users },
{ href: '/admin/ticket-digest', label: 'Ticket Digest', icon: BarChart3 },
{ href: '/admin/sync', label: 'Admin / Sync', icon: Settings },
];
export default function MobileNav() {
const router = useRouter();
return (
<div className="p-4 space-y-6 pb-24">
{/* Header */}
<div className="flex items-center gap-3">
<button
onClick={() => router.back()}
className="p-2 rounded-xl hover:bg-accent transition-colors"
aria-label="Go back"
>
<ArrowLeft className="w-5 h-5" />
</button>
<h1 className="text-lg font-bold">Navigation</h1>
</div>
{/* Main mobile sections */}
<div>
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Mobile Views</p>
<div className="grid grid-cols-1 gap-2">
{MAIN_SECTIONS.map(({ href, label, icon: Icon, description, color }) => (
<Link key={href} href={href}
className="flex items-center gap-4 p-4 rounded-2xl border bg-card hover:bg-accent transition-colors active:scale-[0.98]">
<div className={`w-11 h-11 rounded-xl flex items-center justify-center shrink-0 ${color.split(' ')[0]}`}>
<Icon className={`w-5 h-5 ${color.split(' ').slice(1).join(' ')}`} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold">{label}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
<ChevronRight className="w-4 h-4 text-muted-foreground shrink-0" />
</Link>
))}
</div>
</div>
{/* Desktop links */}
<div>
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Full Site</p>
<div className="rounded-2xl border divide-y overflow-hidden">
{DESKTOP_LINKS.map(({ href, label, icon: Icon }) => (
<Link key={href} href={href}
className="flex items-center gap-3 px-4 py-3.5 hover:bg-accent transition-colors active:bg-accent">
<Icon className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="text-sm flex-1">{label}</span>
<ExternalLink className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
</Link>
))}
</div>
</div>
{/* Sign out */}
<div className="rounded-2xl border overflow-hidden">
<button
onClick={() => signOut().then(() => router.push('/auth/sign-in'))}
className="w-full flex items-center gap-3 px-4 py-3.5 hover:bg-destructive/10 text-destructive transition-colors"
>
<LogOut className="w-4 h-4 shrink-0" />
<span className="text-sm">Sign out</span>
</button>
</div>
</div>
);
}

View file

@ -195,6 +195,9 @@ export function AppNavigation() {
const userRole = (session?.user as any)?.role || 'user';
const isSuperAdmin = userRole === 'super-admin';
// Mobile routes have their own nav — suppress desktop header
if (pathname.startsWith('/mobile')) return null;
const isActive = (href?: string) => {
if (!href) return false;
return pathname === href || pathname.startsWith(href + '/');