From 2ef843766d97d774072da319dd372e6cae100b6e Mon Sep 17 00:00:00 2001 From: lorentz Date: Sun, 3 May 2026 16:07:47 -0400 Subject: [PATCH] feat(02-01): create MoreDrawer component with Sheet, 3 sections, sign out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shadcn Sheet side=right with controlled open/onOpenChange props - Section 1: Mobile sections (Engagement in-shell route) - Section 2: Full site links with ExternalLink hints (Quotes, Config Items, Backup Status, Ticket Digest, Admin/Sync) - Section 3: Account — user initials display + sign out via Better Auth - Implements DRAWER-01..05 requirements --- components/mobile/MoreDrawer.tsx | 166 +++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 components/mobile/MoreDrawer.tsx diff --git a/components/mobile/MoreDrawer.tsx b/components/mobile/MoreDrawer.tsx new file mode 100644 index 0000000..5bf36b6 --- /dev/null +++ b/components/mobile/MoreDrawer.tsx @@ -0,0 +1,166 @@ +'use client'; + +/* MoreDrawer — phase 02 (DRAWER-01..05). + * + * shadcn Sheet (side="right") with three top-to-bottom sections: + * 1. Mobile sections — Engagement (in-shell route, no ExternalLink hint) + * 2. Full site — desktop-only routes, each with ExternalLink hint + * 3. Account — current user (read-only) + Sign out + * + * Open state is controlled by the parent so the header avatar AND the + * bottom-nav More cell can both trigger this single drawer. */ + +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { + ExternalLink, + FileText, + Server, + HardDrive, + BarChart3, + Settings, + Users, + LogOut, +} from 'lucide-react'; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetDescription, + SheetClose, +} from '@/components/ui/sheet'; +import { useSession, signOut } from '@/lib/auth-client'; +import { toast } from 'sonner'; + +const MOBILE_SECTIONS = [ + { href: '/mobile/engagement', label: 'Engagement', icon: Users }, +]; + +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: '/admin/ticket-digest', label: 'Ticket Digest', icon: BarChart3 }, + { href: '/admin/sync', label: 'Admin / Sync', icon: Settings }, +]; + +interface MoreDrawerProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function MoreDrawer({ open, onOpenChange }: MoreDrawerProps) { + const router = useRouter(); + const { data: session } = useSession(); + const user = session?.user as + | { name?: string; email?: string; image?: string | null } + | undefined; + + const initials = (user?.name ?? user?.email ?? '?') + .split(/[\s@]/) + .filter(Boolean) + .slice(0, 2) + .map((p) => p[0]?.toUpperCase()) + .join(''); + + async function handleSignOut() { + try { + await signOut(); + router.push('/auth/sign-in'); + } catch (e) { + toast.error('Sign out failed'); + console.error('Sign out failed:', e); + } + } + + return ( + + + + Menu + + Navigation, full-site links, and account actions. + + + + {/* Section 1: Mobile sections (DRAWER-03) */} +
+

+ Mobile sections +

+
+ {MOBILE_SECTIONS.map(({ href, label, icon: Icon }) => ( + + + + {label} + + + ))} +
+
+ + {/* Section 2: Full site (DRAWER-04) */} +
+

+ Full site +

+
+ {DESKTOP_LINKS.map(({ href, label, icon: Icon }) => ( + + + + {label} + + + + ))} +
+
+ + {/* Section 3: Account (DRAWER-05) */} +
+

+ Account +

+
+ {user && ( +
+ + {initials} + +
+ {user.name && ( +

+ {user.name} +

+ )} + {user.email && ( +

+ {user.email} +

+ )} +
+
+ )} + +
+
+
+
+ ); +}