mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 16:07:15 +02:00
refactor: move wallet functionality to user menu
Move wallet connection and info from separate header button into the user menu dropdown for better organization. Changes: - Remove standalone WalletButton component - Add wallet section to user menu dropdown - Show "Connect Wallet" option when no wallet is connected - Display wallet balance and alias when connected - Clicking wallet info opens detailed dialog with: - Balance (without suffix) - Wallet name/alias - Lightning address (lud16) - Supported NWC methods - Connected relay URLs - Disconnect button This consolidates all user-related settings (account, relays, blossom servers, wallet) in one consistent location.
This commit is contained in:
@@ -1,169 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { Wallet, Zap, X } from "lucide-react";
|
||||
import { useGrimoire } from "@/core/state";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import ConnectWalletDialog from "@/components/ConnectWalletDialog";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function WalletButton() {
|
||||
const { state, disconnectNWC } = useGrimoire();
|
||||
const nwcConnection = state.nwcConnection;
|
||||
const [showConnectWallet, setShowConnectWallet] = useState(false);
|
||||
const [showWalletInfo, setShowWalletInfo] = useState(false);
|
||||
|
||||
function formatBalance(millisats?: number): string {
|
||||
if (millisats === undefined) return "—";
|
||||
const sats = Math.floor(millisats / 1000);
|
||||
return sats.toLocaleString();
|
||||
}
|
||||
|
||||
function handleDisconnect() {
|
||||
disconnectNWC();
|
||||
setShowWalletInfo(false);
|
||||
toast.success("Wallet disconnected");
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
if (nwcConnection) {
|
||||
setShowWalletInfo(true);
|
||||
} else {
|
||||
setShowConnectWallet(true);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectWalletDialog
|
||||
open={showConnectWallet}
|
||||
onOpenChange={setShowConnectWallet}
|
||||
/>
|
||||
|
||||
{/* Wallet Info Dialog */}
|
||||
{nwcConnection && (
|
||||
<Dialog open={showWalletInfo} onOpenChange={setShowWalletInfo}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Wallet Info</DialogTitle>
|
||||
<DialogDescription>
|
||||
Connected Lightning wallet details
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Balance */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Balance:</span>
|
||||
<span className="text-lg font-semibold">
|
||||
{formatBalance(nwcConnection.balance)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Wallet Alias */}
|
||||
{nwcConnection.info?.alias && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Wallet:</span>
|
||||
<span className="text-sm font-medium">
|
||||
{nwcConnection.info.alias}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lightning Address */}
|
||||
{nwcConnection.lud16 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Address:
|
||||
</span>
|
||||
<span className="text-sm font-mono">
|
||||
{nwcConnection.lud16}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Supported Methods */}
|
||||
{nwcConnection.info?.methods &&
|
||||
nwcConnection.info.methods.length > 0 && (
|
||||
<div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Supported Methods:
|
||||
</span>
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{nwcConnection.info.methods.map((method) => (
|
||||
<span
|
||||
key={method}
|
||||
className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium"
|
||||
>
|
||||
{method}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Relays */}
|
||||
<div>
|
||||
<span className="text-sm text-muted-foreground">Relays:</span>
|
||||
<div className="mt-2 space-y-1">
|
||||
{nwcConnection.relays.map((relay) => (
|
||||
<div
|
||||
key={relay}
|
||||
className="text-xs font-mono text-muted-foreground"
|
||||
>
|
||||
{relay}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Disconnect Button */}
|
||||
<Button
|
||||
onClick={handleDisconnect}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
>
|
||||
<X className="mr-2 size-4" />
|
||||
Disconnect Wallet
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{/* Wallet Button */}
|
||||
{nwcConnection ? (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="gap-2"
|
||||
onClick={handleClick}
|
||||
title={
|
||||
nwcConnection.info?.alias
|
||||
? `${nwcConnection.info.alias} - ${formatBalance(nwcConnection.balance)} sats`
|
||||
: `${formatBalance(nwcConnection.balance)} sats`
|
||||
}
|
||||
>
|
||||
<Zap className="size-4 text-yellow-500" />
|
||||
<span className="text-sm font-medium">
|
||||
{formatBalance(nwcConnection.balance)}
|
||||
</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={handleClick}
|
||||
title="Connect Wallet"
|
||||
>
|
||||
<Wallet className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import CommandLauncher from "../CommandLauncher";
|
||||
import { GlobalAuthPrompt } from "../GlobalAuthPrompt";
|
||||
import { SpellbookDropdown } from "../SpellbookDropdown";
|
||||
import UserMenu from "../nostr/user-menu";
|
||||
import WalletButton from "../WalletButton";
|
||||
import { AppShellContext } from "./AppShellContext";
|
||||
|
||||
interface AppShellProps {
|
||||
@@ -77,10 +76,7 @@ export function AppShell({ children, hideBottomBar = false }: AppShellProps) {
|
||||
<SpellbookDropdown />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<WalletButton />
|
||||
<UserMenu />
|
||||
</div>
|
||||
<UserMenu />
|
||||
</header>
|
||||
<section className="flex-1 relative overflow-hidden">
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { User, HardDrive, Palette } from "lucide-react";
|
||||
import { User, HardDrive, Palette, Wallet, Zap, X } from "lucide-react";
|
||||
import accounts from "@/services/accounts";
|
||||
import { useProfile } from "@/hooks/useProfile";
|
||||
import { use$ } from "applesauce-react/hooks";
|
||||
@@ -17,13 +17,22 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import Nip05 from "./nip05";
|
||||
import { RelayLink } from "./RelayLink";
|
||||
import SettingsDialog from "@/components/SettingsDialog";
|
||||
import LoginDialog from "./LoginDialog";
|
||||
import ConnectWalletDialog from "@/components/ConnectWalletDialog";
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@/lib/themes";
|
||||
import { toast } from "sonner";
|
||||
|
||||
function UserAvatar({ pubkey }: { pubkey: string }) {
|
||||
const profile = useProfile(pubkey);
|
||||
@@ -56,11 +65,14 @@ function UserLabel({ pubkey }: { pubkey: string }) {
|
||||
|
||||
export default function UserMenu() {
|
||||
const account = use$(accounts.active$);
|
||||
const { state, addWindow } = useGrimoire();
|
||||
const { state, addWindow, disconnectNWC } = useGrimoire();
|
||||
const relays = state.activeAccount?.relays;
|
||||
const blossomServers = state.activeAccount?.blossomServers;
|
||||
const nwcConnection = state.nwcConnection;
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showLogin, setShowLogin] = useState(false);
|
||||
const [showConnectWallet, setShowConnectWallet] = useState(false);
|
||||
const [showWalletInfo, setShowWalletInfo] = useState(false);
|
||||
const { themeId, setTheme, availableThemes } = useTheme();
|
||||
|
||||
function openProfile() {
|
||||
@@ -77,10 +89,118 @@ export default function UserMenu() {
|
||||
accounts.removeAccount(account);
|
||||
}
|
||||
|
||||
function formatBalance(millisats?: number): string {
|
||||
if (millisats === undefined) return "—";
|
||||
const sats = Math.floor(millisats / 1000);
|
||||
return sats.toLocaleString();
|
||||
}
|
||||
|
||||
function handleDisconnectWallet() {
|
||||
disconnectNWC();
|
||||
setShowWalletInfo(false);
|
||||
toast.success("Wallet disconnected");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsDialog open={showSettings} onOpenChange={setShowSettings} />
|
||||
<LoginDialog open={showLogin} onOpenChange={setShowLogin} />
|
||||
<ConnectWalletDialog
|
||||
open={showConnectWallet}
|
||||
onOpenChange={setShowConnectWallet}
|
||||
/>
|
||||
|
||||
{/* Wallet Info Dialog */}
|
||||
{nwcConnection && (
|
||||
<Dialog open={showWalletInfo} onOpenChange={setShowWalletInfo}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Wallet Info</DialogTitle>
|
||||
<DialogDescription>
|
||||
Connected Lightning wallet details
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Balance */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Balance:</span>
|
||||
<span className="text-lg font-semibold">
|
||||
{formatBalance(nwcConnection.balance)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Wallet Alias */}
|
||||
{nwcConnection.info?.alias && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Wallet:</span>
|
||||
<span className="text-sm font-medium">
|
||||
{nwcConnection.info.alias}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Lightning Address */}
|
||||
{nwcConnection.lud16 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Address:
|
||||
</span>
|
||||
<span className="text-sm font-mono">
|
||||
{nwcConnection.lud16}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Supported Methods */}
|
||||
{nwcConnection.info?.methods &&
|
||||
nwcConnection.info.methods.length > 0 && (
|
||||
<div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Supported Methods:
|
||||
</span>
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{nwcConnection.info.methods.map((method) => (
|
||||
<span
|
||||
key={method}
|
||||
className="inline-flex items-center rounded-md bg-muted px-2 py-1 text-xs font-medium"
|
||||
>
|
||||
{method}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Relays */}
|
||||
<div>
|
||||
<span className="text-sm text-muted-foreground">Relays:</span>
|
||||
<div className="mt-2 space-y-1">
|
||||
{nwcConnection.relays.map((relay) => (
|
||||
<div
|
||||
key={relay}
|
||||
className="text-xs font-mono text-muted-foreground"
|
||||
>
|
||||
{relay}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Disconnect Button */}
|
||||
<Button
|
||||
onClick={handleDisconnectWallet}
|
||||
variant="destructive"
|
||||
className="w-full"
|
||||
>
|
||||
<X className="mr-2 size-4" />
|
||||
Disconnect Wallet
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
@@ -157,6 +277,43 @@ export default function UserMenu() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Wallet Section */}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
|
||||
<Wallet className="size-3.5" />
|
||||
<span>Lightning Wallet</span>
|
||||
</DropdownMenuLabel>
|
||||
{nwcConnection ? (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
className="cursor-crosshair flex items-center justify-between"
|
||||
onClick={() => setShowWalletInfo(true)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="size-4 text-yellow-500" />
|
||||
<span className="text-sm">
|
||||
{formatBalance(nwcConnection.balance)}
|
||||
</span>
|
||||
</div>
|
||||
{nwcConnection.info?.alias && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{nwcConnection.info.alias}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
className="cursor-crosshair"
|
||||
onClick={() => setShowConnectWallet(true)}
|
||||
>
|
||||
<Wallet className="size-4 text-muted-foreground mr-2" />
|
||||
<span className="text-sm">Connect Wallet</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
|
||||
Log out
|
||||
|
||||
Reference in New Issue
Block a user