diff --git a/src/components/AccountList/index.tsx b/src/components/AccountList/index.tsx index 83dbcc74..79fc7970 100644 --- a/src/components/AccountList/index.tsx +++ b/src/components/AccountList/index.tsx @@ -30,7 +30,6 @@ export default function AccountList({ accounts, account, switchAccount, - viewAccountAsReadOnly, removeAccount, retryNip07SignerForPreferredAccount } = useNostr() @@ -58,9 +57,14 @@ export default function AccountList({ if (isRedundantAccountPick(act, account)) { if (account?.signerType === 'npub' && act.signerType === 'nip-07') { setSwitchingAccount(act) - const ok = await retryNip07SignerForPreferredAccount() - if (ok) toast.success(t('accountSwitch.extensionConnected')) - else toast.error(t('accountSwitch.extensionRetryFailed')) + const switched = await switchAccount(act) + if (switched) { + afterSwitch() + } else { + const ok = await retryNip07SignerForPreferredAccount() + if (ok) toast.success(t('accountSwitch.extensionConnected')) + else toast.error(t('accountSwitch.extensionRetryFailed')) + } setSwitchingAccount(null) } return @@ -70,13 +74,7 @@ export default function AccountList({ closeDialog?.() } try { - const needsWriteSigner = - act.signerType === 'nsec' || - act.signerType === 'ncryptsec' || - act.signerType === 'bunker' - const switched = needsWriteSigner - ? await switchAccount(act) - : await viewAccountAsReadOnly(act) + const switched = await switchAccount(act) if (!switched) { toast.error(t('notificationsSwitchAccountFailed')) return diff --git a/src/components/AccountQuickSwitchMenuItems.tsx b/src/components/AccountQuickSwitchMenuItems.tsx index 41b1a18d..8243575f 100644 --- a/src/components/AccountQuickSwitchMenuItems.tsx +++ b/src/components/AccountQuickSwitchMenuItems.tsx @@ -25,7 +25,6 @@ export function AccountQuickSwitchMenuItems({ onAfterSwitch }: { onAfterSwitch?: accounts, account, switchAccount, - viewAccountAsReadOnly, retryNip07SignerForPreferredAccount } = useNostr() const rows = listSwitchableAccounts(accounts) @@ -35,23 +34,22 @@ export function AccountQuickSwitchMenuItems({ onAfterSwitch }: { onAfterSwitch?: const handleSwitch = async (act: TAccountPointer) => { if (isRedundantAccountPick(act, account)) { if (account?.signerType === 'npub' && act.signerType === 'nip-07') { + const switched = await switchAccount(act) + if (switched) { + onAfterSwitch?.() + return + } const ok = await retryNip07SignerForPreferredAccount() if (ok) { toast.success(t('accountSwitch.extensionConnected')) onAfterSwitch?.() } else { - toast.error(t('accountSwitch.extensionRetryFailed')) + toast.error(t('accountSwitch.extensionUnavailable')) } } return } - const needsWriteSigner = - act.signerType === 'nsec' || - act.signerType === 'ncryptsec' || - act.signerType === 'bunker' - const switched = needsWriteSigner - ? await switchAccount(act) - : await viewAccountAsReadOnly(act) + const switched = await switchAccount(act) if (!switched) { toast.error(t('notificationsSwitchAccountFailed')) return diff --git a/src/components/FollowButton/index.tsx b/src/components/FollowButton/index.tsx index 6f7c22ea..48763118 100644 --- a/src/components/FollowButton/index.tsx +++ b/src/components/FollowButton/index.tsx @@ -14,6 +14,7 @@ import { Skeleton } from '@/components/ui/skeleton' import { useFollowListOptional } from '@/providers/follow-list-context' import { useMuteList } from '@/contexts/mute-list-context' import { muteSetHas } from '@/lib/mute-set' +import { useSignGatedControl } from '@/hooks/useSignGatedControl' import { useNostr } from '@/providers/NostrProvider' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -22,6 +23,7 @@ import { toast } from 'sonner' export default function FollowButton({ pubkey }: { pubkey: string }) { const { t } = useTranslation() const { pubkey: accountPubkey, checkLogin } = useNostr() + const { canSignEvents, signControlProps } = useSignGatedControl() const followList = useFollowListOptional() const { mutePubkeySet, unmutePubkey } = useMuteList() const [updating, setUpdating] = useState(false) @@ -31,7 +33,9 @@ export default function FollowButton({ pubkey }: { pubkey: string }) { const isFollowing = useMemo(() => followings.includes(pubkey), [followings, pubkey]) const isMuted = useMemo(() => muteSetHas(mutePubkeySet, pubkey), [mutePubkeySet, pubkey]) - if (!followList || !accountPubkey || (pubkey && pubkey === accountPubkey)) return null + if (!followList || !accountPubkey || !canSignEvents || (pubkey && pubkey === accountPubkey)) { + return null + } const { follow, unfollow } = followList @@ -92,7 +96,7 @@ export default function FollowButton({ pubkey }: { pubkey: string }) { ) diff --git a/src/components/HelpAndAccountMenu.tsx b/src/components/HelpAndAccountMenu.tsx index 3295fa28..3f02bd5e 100644 --- a/src/components/HelpAndAccountMenu.tsx +++ b/src/components/HelpAndAccountMenu.tsx @@ -11,7 +11,14 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { Skeleton } from '@/components/ui/skeleton' -import { formatPubkey, formatNpub, generateImageByPubkey, pubkeyToNpub } from '@/lib/pubkey' +import { + accountPubkeyToHex, + formatPubkey, + formatNpub, + generateImageByPubkey, + hexPubkeysEqual, + pubkeyToNpub +} from '@/lib/pubkey' import { isVideo } from '@/lib/url' import { cn } from '@/lib/utils' import { openBrowseCacheFromRegistry } from '@/contexts/cache-browser-context' @@ -27,6 +34,20 @@ import { useRelayConnectionRows } from '@/hooks/useRelayConnectionRows' import { ArrowDownUp, Database, LogIn, LogOut, Settings, User, UserRound } from 'lucide-react' import { useCallback, useMemo, useState, type ReactNode } from 'react' import { useTranslation } from 'react-i18next' +import type { TProfile } from '@/types' + +/** Profile for the badge only when it belongs to the active session pubkey (avoids stale name/avatar). */ +function profileForActivePubkey( + pubkey: string | undefined, + nostrProfile: TProfile | null, + fetchedProfile: TProfile | null +): TProfile | null { + const pk = pubkey ? accountPubkeyToHex(pubkey) : null + if (!pk) return null + if (fetchedProfile && hexPubkeysEqual(fetchedProfile.pubkey, pk)) return fetchedProfile + if (nostrProfile && hexPubkeysEqual(nostrProfile.pubkey, pk)) return nostrProfile + return null +} const titlebarAccountMenuContentClassName = 'z-[220] w-[min(18rem,calc(100vw-1.5rem))] overflow-y-auto overscroll-contain' @@ -92,6 +113,10 @@ function SidebarAccountMenu({ const [menuOpen, setMenuOpen] = useState(false) const pubkey = account?.pubkey const { profile: fetchedProfile } = useFetchProfile(pubkey) + const resolvedProfile = useMemo( + () => profileForActivePubkey(pubkey, profile, fetchedProfile), + [pubkey, profile, fetchedProfile] + ) const active = useMemo(() => current === 'profile' && display, [display, current]) if (!pubkey) return null @@ -99,8 +124,9 @@ function SidebarAccountMenu({ const defaultAvatar = generateImageByPubkey(pubkey) const npub = pubkeyToNpub(pubkey) const fallbackUsername = npub ? formatNpub(npub) : formatPubkey(pubkey) - const resolvedProfile = fetchedProfile ?? profile - const { username, avatar } = resolvedProfile || { username: fallbackUsername, avatar: defaultAvatar } + const { username, avatar } = resolvedProfile + ? { username: resolvedProfile.username, avatar: resolvedProfile.avatar ?? defaultAvatar } + : { username: fallbackUsername, avatar: defaultAvatar } return ( @@ -121,7 +147,7 @@ function SidebarAccountMenu({