Skip to content

Commit cf9ad57

Browse files
authored
[masterbots.ai] feat: reorganize navigation menu for mobile view (#298)
* feat: reorganize navigation menu for mobile view * UI: add sideBar style * feat: add link profile and logout icon * Update profile-sidebar.tsx Tailwind class fix
1 parent dd8a3d2 commit cf9ad57

File tree

5 files changed

+182
-15
lines changed

5 files changed

+182
-15
lines changed

apps/masterbots.ai/components/auth/user-login.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UserMenu } from '@/components/layout/header/user-menu'
55
import { Button } from '@/components/ui/button'
66
import Link from 'next/link'
77
import { isTokenExpired } from 'mb-lib'
8+
import { ProfileSidebar } from '@/components/layout/sidebar/profile-sidebar'
89

910
export function UserLogin() {
1011
const { data: session, status } = useSession()
@@ -25,7 +26,7 @@ export function UserLogin() {
2526
return <LoginButton />
2627
}
2728

28-
return <UserMenu user={session.user} />
29+
return <ProfileSidebar user={session.user} />
2930
}
3031

3132
return <LoginButton />

apps/masterbots.ai/components/layout/header/header.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ export function Header() {
1616
<SidebarToggle />
1717
</React.Suspense>
1818
<HeaderLink href="/" text="MB" />
19-
<IconSeparator className="size-6 text-muted-foreground/50" />
20-
<HeaderLink href="/c" text="Chat" />
21-
<HeaderLink href="/" text="Browse" />
22-
23-
{appConfig.devMode && (
24-
<>
25-
<HeaderLink href="/wordware" text="Ww" />
26-
<HeaderLink href="/c/p" text="Pro" />
27-
</>
28-
)}
19+
20+
{/* Navigation links - Hidden on mobile */}
21+
<div className="hidden lg:flex lg:items-center">
22+
<IconSeparator className="size-6 text-muted-foreground/50" />
23+
<HeaderLink href="/c" text="Chat" />
24+
25+
{appConfig.devMode && (
26+
<>
27+
<HeaderLink href="/c/p" text="Pro" />
28+
<HeaderLink href="/wordware" text="Ww" />
29+
</>
30+
)}
31+
</div>
2932
</div>
3033
<div className="flex items-center justify-end space-x-2 gap-2">
3134
<ThemeToggle />
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use client'
2+
3+
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
4+
import { Button } from '@/components/ui/button'
5+
import { LogOut } from 'lucide-react'
6+
import { signOut } from 'next-auth/react'
7+
import Image from 'next/image'
8+
import { appConfig } from 'mb-env'
9+
import type { Session } from 'next-auth'
10+
import { useState, useCallback } from 'react'
11+
import { useRouter } from 'next/navigation'
12+
import { toSlug } from 'mb-lib'
13+
14+
interface ProfileSidebarProps {
15+
user: Session['user'] & {
16+
hasuraJwt?: string
17+
}
18+
}
19+
20+
function getUserInitials(name: string) {
21+
const [firstName, lastName] = name.split(' ')
22+
return lastName ? `${firstName[0]}${lastName[0]}` : firstName.slice(0, 2)
23+
}
24+
25+
export function ProfileSidebar({ user }: ProfileSidebarProps) {
26+
const [isOpen, setIsOpen] = useState(false)
27+
const router = useRouter()
28+
29+
const handleNavigation = (path: string) => {
30+
setIsOpen(false)
31+
router.push(path)
32+
}
33+
34+
const handleLogout = useCallback(async () => {
35+
try {
36+
setIsOpen(false)
37+
await new Promise(resolve => setTimeout(resolve, 100))
38+
await signOut({ callbackUrl: '/' })
39+
} catch (error) {
40+
console.error('Logout error:', error)
41+
window.location.href = '/'
42+
}
43+
}, [])
44+
45+
const goToProfile = useCallback((e: React.MouseEvent) => {
46+
e.preventDefault()
47+
e.stopPropagation()
48+
const userSlug = toSlug(user.name || '')
49+
if (userSlug) {
50+
setIsOpen(false)
51+
router.push(`/u/${userSlug}/t`)
52+
}
53+
}, [router, user.name])
54+
55+
56+
return (
57+
<Sheet open={isOpen} onOpenChange={setIsOpen}>
58+
<SheetTrigger asChild>
59+
<Button variant="ghost" className="pl-0 rounded-full">
60+
{user?.image ? (
61+
<Image
62+
className="transition-opacity duration-300 rounded-full select-none size-8 bg-foreground/10 ring-1 ring-zinc-100/10 hover:opacity-80"
63+
src={user?.image ? user.image : ''}
64+
alt={user.name ?? 'Avatar'}
65+
height={32}
66+
width={32}
67+
priority
68+
/>
69+
) : (
70+
<div className="flex items-center justify-center text-xs font-medium uppercase rounded-full select-none size-7 shrink-0 bg-muted/50 text-muted-foreground">
71+
{user?.name ? getUserInitials(user?.name) : null}
72+
</div>
73+
)}
74+
<span className="ml-2 hidden md:inline-block">{user?.name}</span>
75+
</Button>
76+
</SheetTrigger>
77+
<SheetContent side="right" className="w-[300px] sm:w-[400px] p-0">
78+
<div className="flex flex-col h-full">
79+
{/* Profile Header */}
80+
<div className="p-4 border-b">
81+
<Button
82+
onClick={goToProfile}
83+
variant="sideBarProfile"
84+
size="sideBarProfile"
85+
>
86+
{user?.image ? (
87+
<Image
88+
className="rounded-full size-10"
89+
src={user.image}
90+
alt={user.name ?? 'Avatar'}
91+
height={40}
92+
width={40}
93+
priority
94+
/>
95+
) : (
96+
<div className="flex items-center justify-center text-sm font-medium uppercase rounded-full size-10 bg-muted/50">
97+
{user?.name ? getUserInitials(user?.name) : null}
98+
</div>
99+
)}
100+
<div className="space-y-1">
101+
<p className="text-sm font-medium">{user?.name}</p>
102+
<p className="text-xs text-muted-foreground">{user?.email}</p>
103+
</div>
104+
</Button>
105+
</div>
106+
107+
{/* Navigation Links - Only visible on mobile */}
108+
<nav className="flex flex-col p-4 lg:hidden">
109+
<Button
110+
variant="ghost"
111+
className="w-full justify-start text-sm"
112+
onClick={() => handleNavigation('/c')}
113+
>
114+
Chat
115+
</Button>
116+
117+
{appConfig.devMode && (
118+
<Button
119+
variant="ghost"
120+
className="w-full justify-start text-sm"
121+
onClick={() => handleNavigation('/c/p')}
122+
>
123+
Pro
124+
</Button>
125+
)}
126+
127+
<Button
128+
variant="ghost"
129+
className="w-full justify-start text-sm"
130+
onClick={() => handleNavigation('/')}
131+
>
132+
Browse
133+
</Button>
134+
135+
{appConfig.devMode && (
136+
<Button
137+
variant="ghost"
138+
className="w-full justify-start text-sm"
139+
onClick={() => handleNavigation('/wordware')}
140+
>
141+
Ww
142+
</Button>
143+
)}
144+
</nav>
145+
146+
{/* Logout Button */}
147+
<div className="mt-auto p-4 border-t">
148+
<Button
149+
variant="ghost"
150+
className="w-full justify-start text-sm"
151+
onClick={handleLogout}
152+
>
153+
<LogOut className="size-4 mr-2" />
154+
Log Out
155+
</Button>
156+
</div>
157+
</div>
158+
</SheetContent>
159+
</Sheet>
160+
)
161+
}

apps/masterbots.ai/components/ui/button.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ const buttonVariants = cva(
1919
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
2020
ghost: 'shadow-none hover:bg-accent hover:text-accent-foreground',
2121
link: 'text-primary underline-offset-4 shadow-none hover:underline',
22-
icon: 'flex size-8 shrink-0 select-none items-center justify-center rounded-full border shadow cursor-pointer'
22+
icon: 'flex size-8 shrink-0 select-none items-center justify-center rounded-full border shadow cursor-pointer',
23+
sideBarProfile: 'bg-transparent border-0 shadow-none justify-start',
2324
},
2425
size: {
2526
default: 'h-8 px-4 py-2',
2627
sm: 'h-8 rounded-md px-3',
2728
lg: 'h-11 rounded-md px-8',
28-
icon: 'size-8 p-0'
29+
icon: 'size-8 p-0',
30+
sideBarProfile: 'size-full',
2931
}
3032
},
3133
defaultVariants: {

apps/masterbots.ai/components/ui/dropdown-menu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const DropdownMenuSubContent = React.forwardRef<
2424
<DropdownMenuPrimitive.SubContent
2525
ref={ref}
2626
className={cn(
27-
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
27+
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1',
2828
className
2929
)}
3030
{...props}
@@ -42,7 +42,7 @@ const DropdownMenuContent = React.forwardRef<
4242
ref={ref}
4343
sideOffset={sideOffset}
4444
className={cn(
45-
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
45+
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
4646
className
4747
)}
4848
{...props}

0 commit comments

Comments
 (0)