1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 08:35:42 +02:00

Improve sidebar button ui

This commit is contained in:
Kamran Ahmed
2025-06-02 19:53:29 +01:00
parent 1fdf64fb5a
commit aec83617bf
4 changed files with 84 additions and 87 deletions

1
.astro/types.d.ts vendored
View File

@@ -1,2 +1 @@
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -1,21 +1,10 @@
import { import { BookOpen, Compass, Plus, Star, X, Zap } from 'lucide-react';
BookOpen,
Compass,
CreditCardIcon,
LogOut,
LogOutIcon,
Plus,
Star,
X,
Zap,
} from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import { useIsPaidUser } from '../../queries/billing'; import { useIsPaidUser } from '../../queries/billing';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { AITutorLogo } from '../ReactIcons/AITutorLogo'; import { AITutorLogo } from '../ReactIcons/AITutorLogo';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { logout } from '../../lib/auth';
import { UserDropdown } from './UserDropdown'; import { UserDropdown } from './UserDropdown';
type AITutorSidebarProps = { type AITutorSidebarProps = {
@@ -145,36 +134,9 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
</li> </li>
)} )}
</ul> </ul>
{/* {isLoggedIn() && (
<div className="mt-auto mb-2 space-y-1">
<AITutorSidebarItem
item={{
key: 'billing',
label: 'Billing',
href: '/account/billing',
icon: CreditCardIcon,
}}
/>
<AITutorSidebarItem
item={{
key: 'logout',
label: 'Logout',
icon: LogOutIcon,
href: '/logout',
}}
as="button"
isActive={false}
onClick={logout}
className="hover:text-red-500"
/>
</div>
)} */}
{isLoggedIn() && (
<div className="mx-2 mt-auto mb-2"> <div className="mx-2 mt-auto mb-2">
<UserDropdown /> <UserDropdown />
</div> </div>
)}
</aside> </aside>
{isFloating && ( {isFloating && (
<div className="fixed inset-0 z-40 bg-black/50" onClick={onClose} /> <div className="fixed inset-0 z-40 bg-black/50" onClick={onClose} />

View File

@@ -1,10 +1,16 @@
import { import {
ChevronsUpDownIcon, ChevronDown,
CogIcon,
CreditCardIcon, CreditCardIcon,
LogInIcon,
LogOutIcon, LogOutIcon,
UserIcon, Settings,
User2,
} from 'lucide-react'; } from 'lucide-react';
import { useAuth } from '../../hooks/use-auth';
import { useClientMount } from '../../hooks/use-client-mount';
import { logout } from '../../lib/auth';
import { showLoginPopup } from '../../lib/popup';
import { useIsPaidUser } from '../../queries/billing';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -12,16 +18,28 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '../DropdownMenu'; } from '../DropdownMenu';
import { useAuth } from '../../hooks/use-auth';
import { logout } from '../../lib/auth';
type UserDropdownProps = {}; type UserDropdownProps = {};
export function UserDropdown(props: UserDropdownProps) { export function UserDropdown(props: UserDropdownProps) {
const currentUser = useAuth(); const currentUser = useAuth();
const { isPaidUser, isLoading } = useIsPaidUser();
const isMounted = useClientMount();
if (!isMounted || isLoading) {
return null;
}
if (!currentUser) { if (!currentUser) {
return null; return (
<button
onClick={showLoginPopup}
className="animate-fade-in inline-flex h-auto w-full items-center justify-center gap-2 rounded-lg border border-gray-700 bg-black px-4 py-2.5 text-sm font-medium text-white transition-all duration-200 outline-none hover:!opacity-80 disabled:cursor-not-allowed disabled:opacity-60"
>
<LogInIcon className="size-4" />
Free Signup or Login
</button>
);
} }
const userAvatar = currentUser?.avatar const userAvatar = currentUser?.avatar
@@ -31,58 +49,65 @@ export function UserDropdown(props: UserDropdownProps) {
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button className="inline-flex h-auto w-full items-center justify-between gap-2 rounded-lg border border-transparent px-4 py-2 text-sm font-medium outline-none hover:bg-gray-100 hover:text-black focus-visible:border-gray-300 focus-visible:bg-gray-100 focus-visible:text-black disabled:cursor-not-allowed disabled:opacity-60 [&_svg]:pointer-events-none [&_svg]:shrink-0"> <button className="group flex w-full items-center gap-3 rounded-lg border border-transparent px-4 py-2.5 text-sm font-medium transition-colors hover:bg-gray-100 hover:text-black focus:outline-none data-[state=open]:bg-gray-100 data-[state=open]:text-black">
<div className="flex min-w-0 items-center gap-2.5">
<div className="relative size-7 shrink-0 overflow-hidden rounded-full"> <div className="relative size-7 shrink-0 overflow-hidden rounded-full">
<img src={userAvatar} className="absolute inset-0 object-cover" /> <img
src={userAvatar}
alt={currentUser.name}
className="absolute inset-0 h-full w-full object-cover"
/>
</div> </div>
<div className="flex min-w-0 flex-col gap-0.5 text-left"> <div className="flex min-w-0 flex-1 flex-col text-left">
<h3 className="truncate text-sm font-medium"> <span className="truncate font-medium text-gray-900">
{currentUser.name} {currentUser.name}
</h3> </span>
<p className="truncate text-xs text-gray-500"> <span className="truncate text-xs text-gray-500">
{currentUser.email} {isPaidUser ? 'Pro Member' : 'Free User'}
</p> </span>
</div>
</div> </div>
<ChevronsUpDownIcon className="ml-auto size-3.5" /> <ChevronDown className="size-4 text-gray-400 transition-transform duration-200 group-data-[state=open]:rotate-180" />
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-[var(--radix-dropdown-menu-trigger-width)] min-w-full p-0.5">
<div className="flex h-auto flex-col justify-between gap-0.5 p-2">
<div className="flex min-w-0 items-center gap-2.5">
<div className="relative size-8 shrink-0 overflow-hidden rounded-full">
<img src={userAvatar} className="absolute inset-0 object-cover" />
</div>
<div className="flex min-w-0 flex-col gap-0.5 text-left"> <DropdownMenuContent className="w-[var(--radix-dropdown-menu-trigger-width)] min-w-52 rounded-lg border border-gray-200 bg-white p-1">
<h3 className="truncate text-sm font-medium"> <div className="space-y-1">
{currentUser.name}
</h3>
<p className="truncate text-xs text-gray-500">
{currentUser.email}
</p>
</div>
</div>
</div>
<DropdownMenuSeparator />
<div className="space-y-0.5">
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<a href="/account/billing"> <a
href="/account"
className="flex w-full items-center gap-3 rounded px-3 py-2 text-sm font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-black"
>
<User2 className="size-4" />
Account
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href="/account/billing"
className="flex w-full items-center gap-3 rounded px-3 py-2 text-sm font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-black"
>
<CreditCardIcon className="size-4" /> <CreditCardIcon className="size-4" />
Billing Billing
</a> </a>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href="/account/settings"
className="flex w-full items-center gap-3 rounded px-3 py-2 text-sm font-medium text-gray-500 transition-colors hover:bg-gray-100 hover:text-black"
>
<Settings className="size-4" />
Settings
</a>
</DropdownMenuItem>
</div> </div>
<DropdownMenuSeparator /> <DropdownMenuSeparator className="my-1" />
<DropdownMenuItem <DropdownMenuItem
className="text-red-600 focus:bg-red-100 focus:text-red-600" className="flex w-full items-center gap-3 rounded px-3 py-2 text-sm font-medium text-red-600 transition-colors hover:bg-red-50 hover:text-red-700"
onSelect={() => { onSelect={() => {
logout(); logout();
}} }}

View File

@@ -0,0 +1,11 @@
import { useEffect, useState } from 'react';
export function useClientMount() {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
return isMounted;
}