mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 06:57:07 +02:00
feat(user-menu): make relays and blossom servers collapsible dropdowns (#177)
* feat(user-menu): make relays and blossom servers collapsible dropdowns Convert the relays and blossom servers sections in the user menu from always-expanded lists to collapsible dropdown submenus. Each section now shows a count next to the title and expands on click to show the full list. This saves vertical space in the menu when users have many relays or servers configured. * refactor(user-menu): reorganize menu items for better UX Reorder menu sections by logical grouping: - Identity (user profile) at top - Account config (wallet, relays, blossom) grouped together - App preferences (theme) in consistent location - Promotional (support grimoire) separated - Session actions (login/logout) at bottom This provides a more intuitive flow and consistent placement regardless of login state. * refactor(user-menu): revert dropdowns, add login/logout icons - Revert relays and blossom servers back to flat lists (not dropdowns) - Move login to first option for logged out users - Add LogIn/LogOut icons from lucide-react - Keep the reorganized menu structure * style(user-menu): clean up icons and font consistency - Make theme icon muted like other icons - Remove HardDrive icons from blossom servers section - Make blossom label consistent with relays (plain text) - Remove unused HardDrive import * fix(user-menu): close menu when clicking relays, blossom, or donate Wrap RelayLink items in DropdownMenuItem with asChild to properly trigger menu close on click. Convert Support Grimoire section from a raw div to DropdownMenuItem so it also closes the menu. * fix(user-menu): properly close menu when clicking relay items Use pointer-events-none on RelayLink to make it purely presentational, and handle the click on DropdownMenuItem instead. This ensures the menu closes properly when clicking a relay. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
User,
|
||||
HardDrive,
|
||||
Palette,
|
||||
Wallet,
|
||||
X,
|
||||
@@ -8,6 +7,8 @@ import {
|
||||
Eye,
|
||||
EyeOff,
|
||||
Zap,
|
||||
LogIn,
|
||||
LogOut,
|
||||
} from "lucide-react";
|
||||
import accounts from "@/services/accounts";
|
||||
import { useProfile } from "@/hooks/useProfile";
|
||||
@@ -370,6 +371,18 @@ export default function UserMenu() {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-80" align="start">
|
||||
{/* Login first for logged out users */}
|
||||
{!account && (
|
||||
<>
|
||||
<DropdownMenuItem onClick={() => setShowLogin(true)}>
|
||||
<LogIn className="size-4 text-muted-foreground mr-2" />
|
||||
<span className="text-sm">Log in</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* User Profile - Identity section */}
|
||||
{account && (
|
||||
<>
|
||||
<DropdownMenuGroup>
|
||||
@@ -385,7 +398,7 @@ export default function UserMenu() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Wallet Section - Always show */}
|
||||
{/* Wallet Section */}
|
||||
{nwcConnection ? (
|
||||
<DropdownMenuItem
|
||||
className="cursor-crosshair flex items-center justify-between"
|
||||
@@ -423,106 +436,63 @@ export default function UserMenu() {
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
{/* Support Grimoire Section */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<div
|
||||
className="px-2 py-2 cursor-crosshair hover:bg-accent/50 transition-colors"
|
||||
onClick={openDonate}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Zap className="size-4 text-yellow-500" />
|
||||
<span className="text-sm font-medium">Support Grimoire</span>
|
||||
</div>
|
||||
<Progress value={goalProgress} className="h-1.5 mb-1" />
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-foreground font-medium">
|
||||
{formatSats(monthlyDonations)}
|
||||
</span>
|
||||
{" / "}
|
||||
{formatSats(MONTHLY_GOAL_SATS)}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{goalProgress.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
{/* Account Configuration - Relays & Blossom */}
|
||||
{account && (
|
||||
<>
|
||||
{relays && relays.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
|
||||
Relays
|
||||
</DropdownMenuLabel>
|
||||
{relays.map((relay) => (
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
|
||||
Relays
|
||||
</DropdownMenuLabel>
|
||||
{relays.map((relay) => (
|
||||
<DropdownMenuItem
|
||||
key={relay.url}
|
||||
className="p-0 cursor-crosshair"
|
||||
onClick={() => addWindow("relay", { url: relay.url })}
|
||||
>
|
||||
<RelayLink
|
||||
className="px-2 py-1"
|
||||
className="px-2 py-1.5 w-full pointer-events-none"
|
||||
urlClassname="text-sm"
|
||||
iconClassname="size-4"
|
||||
key={relay.url}
|
||||
url={relay.url}
|
||||
read={relay.read}
|
||||
write={relay.write}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
|
||||
{blossomServers && blossomServers.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
|
||||
<HardDrive className="size-3.5" />
|
||||
<span>Blossom Servers</span>
|
||||
</DropdownMenuLabel>
|
||||
{blossomServers.map((server) => (
|
||||
<DropdownMenuItem
|
||||
key={server}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => {
|
||||
addWindow(
|
||||
"blossom",
|
||||
{ subcommand: "list", serverUrl: server },
|
||||
`Files on ${server}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<HardDrive className="size-4 text-muted-foreground mr-2" />
|
||||
<span className="text-sm truncate">{server}</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
</>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal">
|
||||
Blossom Servers
|
||||
</DropdownMenuLabel>
|
||||
{blossomServers.map((server) => (
|
||||
<DropdownMenuItem
|
||||
key={server}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => {
|
||||
addWindow(
|
||||
"blossom",
|
||||
{ subcommand: "list", serverUrl: server },
|
||||
`Files on ${server}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className="text-sm truncate">{server}</span>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!account && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => setShowLogin(true)}>
|
||||
Log in
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Theme Section - Always show */}
|
||||
{/* App Preferences - Theme */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="cursor-crosshair">
|
||||
<Palette className="size-4 mr-2" />
|
||||
<Palette className="size-4 text-muted-foreground mr-2" />
|
||||
Theme
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
@@ -544,6 +514,42 @@ export default function UserMenu() {
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
|
||||
{/* Support Grimoire */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
className="cursor-crosshair flex-col items-stretch p-2"
|
||||
onClick={openDonate}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Zap className="size-4 text-yellow-500" />
|
||||
<span className="text-sm font-medium">Support Grimoire</span>
|
||||
</div>
|
||||
<Progress value={goalProgress} className="h-1.5 mb-1" />
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span className="text-muted-foreground">
|
||||
<span className="text-foreground font-medium">
|
||||
{formatSats(monthlyDonations)}
|
||||
</span>
|
||||
{" / "}
|
||||
{formatSats(MONTHLY_GOAL_SATS)}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{goalProgress.toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Logout at bottom for logged in users */}
|
||||
{account && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
|
||||
<LogOut className="size-4 text-muted-foreground mr-2" />
|
||||
<span className="text-sm">Log out</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user