wulf-pulse/components/mobile/MoreDrawer.tsx
lorentz da13caf9cb feat(09-04): page shell, drawer wiring, skeleton helper, channels placeholder
- app/mobile/profile/page.tsx: server-component shell gated by requireAuth() + redirect('/auth/sign-in')
- ProfileSectionSkeleton.tsx: generic 3-row pulsing skeleton Card
- ProfileChannelsSectionPlaceholder.tsx: stub Channels card (Plan 05 swaps real component)
- MoreDrawer.tsx Account section: identity row wrapped in Link, new Profile & preferences row above Sign-out
2026-05-10 07:40:40 -04:00

174 lines
6.2 KiB
TypeScript

'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 (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" className="w-80 sm:max-w-sm flex flex-col">
<SheetHeader>
<SheetTitle>Menu</SheetTitle>
<SheetDescription className="sr-only">
Navigation, full-site links, and account actions.
</SheetDescription>
</SheetHeader>
{/* Section 1: Mobile sections (DRAWER-03) */}
<div className="px-4">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
Mobile sections
</p>
<div className="rounded-2xl border divide-y overflow-hidden">
{MOBILE_SECTIONS.map(({ href, label, icon: Icon }) => (
<SheetClose asChild key={href}>
<Link
href={href}
className="flex items-center gap-3 px-4 py-3 hover:bg-accent transition-colors"
>
<Icon className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="text-sm flex-1">{label}</span>
</Link>
</SheetClose>
))}
</div>
</div>
{/* Section 2: Full site (DRAWER-04) */}
<div className="px-4">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
Full site
</p>
<div className="rounded-2xl border divide-y overflow-hidden">
{DESKTOP_LINKS.map(({ href, label, icon: Icon }) => (
<SheetClose asChild key={href}>
<Link
href={href}
className="flex items-center gap-3 px-4 py-3 hover:bg-accent transition-colors"
>
<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>
</SheetClose>
))}
</div>
</div>
{/* Section 3: Account (DRAWER-05, PROF-04) */}
<div className="px-4 mt-auto pb-safe">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
Account
</p>
<div className="rounded-2xl border overflow-hidden">
{user && (
<SheetClose asChild>
<Link href="/mobile/profile" className="flex items-center gap-3 px-4 py-3 hover:bg-accent transition-colors border-b">
<span className="inline-flex h-8 w-8 items-center justify-center rounded-full bg-primary/15 text-primary text-xs font-semibold shrink-0">
{initials}
</span>
<div className="flex-1 min-w-0">
{user.name && (
<p className="text-sm font-medium leading-tight truncate">
{user.name}
</p>
)}
{user.email && (
<p className="text-xs text-muted-foreground truncate" title={user.email}>
{user.email}
</p>
)}
</div>
</Link>
</SheetClose>
)}
<SheetClose asChild>
<Link href="/mobile/profile" className="flex items-center gap-3 px-4 py-3 hover:bg-accent transition-colors border-b">
<Settings className="w-4 h-4 text-muted-foreground shrink-0" />
<span className="text-sm flex-1">Profile &amp; preferences</span>
</Link>
</SheetClose>
<button
type="button"
onClick={handleSignOut}
className="w-full flex items-center gap-3 px-4 py-3 text-destructive hover:bg-destructive/10 transition-colors"
>
<LogOut className="w-4 h-4 shrink-0" />
<span className="text-sm">Sign out</span>
</button>
</div>
</div>
</SheetContent>
</Sheet>
);
}