feat: improve wallet UX with profile-based naming and better layout

Improvements to NWC wallet UI:
- Add separator between user info and wallet section in menu
- Show wallet icon instead of zap icon for better clarity
- Display connection status indicator (green/red dot) in both menu and dialog
- Make wallet service username clickable in wallet info dialog to open profile
- Use wallet relays as hints when fetching service profile for better resolution
- Enhanced useProfile hook to accept optional relay hints parameter

The wallet now properly resolves service profiles using the NWC relay
and shows visual connection status at a glance.
This commit is contained in:
Claude
2026-01-18 09:33:01 +00:00
parent 8e6e989ca5
commit 54cfad784b
2 changed files with 68 additions and 21 deletions

View File

@@ -1,12 +1,4 @@
import {
User,
HardDrive,
Palette,
Wallet,
Zap,
X,
RefreshCw,
} from "lucide-react";
import { User, HardDrive, Palette, Wallet, X, RefreshCw } from "lucide-react";
import accounts from "@/services/accounts";
import { useProfile } from "@/hooks/useProfile";
import { use$ } from "applesauce-react/hooks";
@@ -84,11 +76,19 @@ export default function UserMenu() {
const [showWalletInfo, setShowWalletInfo] = useState(false);
const { themeId, setTheme, availableThemes } = useTheme();
// Get wallet service profile for display name
const walletServiceProfile = useProfile(nwcConnection?.service);
// Get wallet service profile for display name, using wallet relays as hints
const walletServiceProfile = useProfile(
nwcConnection?.service,
nwcConnection?.relays,
);
// Use wallet hook for real-time balance and methods
const { disconnect: disconnectWallet, refreshBalance, balance } = useWallet();
const {
disconnect: disconnectWallet,
refreshBalance,
balance,
wallet,
} = useWallet();
function openProfile() {
if (!account?.pubkey) return;
@@ -138,6 +138,16 @@ export default function UserMenu() {
);
}
function openWalletServiceProfile() {
if (!nwcConnection?.service) return;
addWindow(
"profile",
{ pubkey: nwcConnection.service },
`Profile ${nwcConnection.service.slice(0, 8)}...`,
);
setShowWalletInfo(false);
}
return (
<>
<SettingsDialog open={showSettings} onOpenChange={setShowSettings} />
@@ -185,7 +195,27 @@ export default function UserMenu() {
{/* Wallet Name */}
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Wallet:</span>
<span className="text-sm font-medium">{getWalletName()}</span>
<button
onClick={openWalletServiceProfile}
className="text-sm font-medium hover:underline cursor-crosshair text-primary"
>
{getWalletName()}
</button>
</div>
{/* Connection Status */}
<div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Status:</span>
<div className="flex items-center gap-2">
<span
className={`size-2 rounded-full ${
wallet ? "bg-green-500" : "bg-red-500"
}`}
/>
<span className="text-sm font-medium">
{wallet ? "Connected" : "Disconnected"}
</span>
</div>
</div>
{/* Lightning Address */}
@@ -276,6 +306,8 @@ export default function UserMenu() {
</DropdownMenuLabel>
</DropdownMenuGroup>
<DropdownMenuSeparator />
{/* Wallet Section */}
{nwcConnection ? (
<DropdownMenuItem
@@ -283,7 +315,7 @@ export default function UserMenu() {
onClick={() => setShowWalletInfo(true)}
>
<div className="flex items-center gap-2">
<Zap className="size-4 text-yellow-500" />
<Wallet className="size-4 text-muted-foreground" />
{balance !== undefined ||
nwcConnection.balance !== undefined ? (
<span className="text-sm">
@@ -291,9 +323,16 @@ export default function UserMenu() {
</span>
) : null}
</div>
<span className="text-xs text-muted-foreground">
{getWalletName()}
</span>
<div className="flex items-center gap-1.5">
<span
className={`size-1.5 rounded-full ${
wallet ? "bg-green-500" : "bg-red-500"
}`}
/>
<span className="text-xs text-muted-foreground">
{getWalletName()}
</span>
</div>
</DropdownMenuItem>
) : (
<DropdownMenuItem

View File

@@ -12,9 +12,13 @@ import db from "@/services/db";
* - Pubkey changes while a fetch is in progress
*
* @param pubkey - The user's public key (hex)
* @param relayHints - Optional relay URLs to try fetching from
* @returns ProfileContent or undefined if loading/not found
*/
export function useProfile(pubkey?: string): ProfileContent | undefined {
export function useProfile(
pubkey?: string,
relayHints?: string[],
): ProfileContent | undefined {
const [profile, setProfile] = useState<ProfileContent | undefined>();
const abortControllerRef = useRef<AbortController | null>(null);
@@ -37,8 +41,12 @@ export function useProfile(pubkey?: string): ProfileContent | undefined {
}
});
// Fetch from network
const sub = profileLoader({ kind: kinds.Metadata, pubkey }).subscribe({
// Fetch from network with optional relay hints
const sub = profileLoader({
kind: kinds.Metadata,
pubkey,
...(relayHints && relayHints.length > 0 && { relays: relayHints }),
}).subscribe({
next: async (fetchedEvent) => {
if (controller.signal.aborted) return;
if (!fetchedEvent || !fetchedEvent.content) return;
@@ -77,7 +85,7 @@ export function useProfile(pubkey?: string): ProfileContent | undefined {
controller.abort();
sub.unsubscribe();
};
}, [pubkey]);
}, [pubkey, relayHints]);
return profile;
}