From e13d3cfadae5b4aee7eb10f032b1650fbd869a92 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Wed, 3 Jun 2026 10:12:22 +0200 Subject: [PATCH] refactor refresh --- src/components/AccountList/index.tsx | 20 +- .../AccountQuickSwitchMenuItems.tsx | 16 +- src/components/FollowButton/index.tsx | 16 +- src/components/HelpAndAccountMenu.tsx | 41 ++- src/components/MuteButton/index.tsx | 12 +- .../Nip07ExtensionKeyMismatchToast/index.tsx | 51 --- src/components/NoteStats/LikeButton.tsx | 12 +- src/components/NoteStats/ReplyButton.tsx | 5 +- src/components/NoteStats/RepostButton.tsx | 9 +- src/components/PostEditor/PostContent.tsx | 4 +- src/components/StoredAccountSwitchSelect.tsx | 86 +++-- src/hooks/index.tsx | 1 + src/hooks/useFetchProfile.tsx | 6 +- src/hooks/useSignGatedControl.ts | 20 ++ src/i18n/locales/de.ts | 2 + src/i18n/locales/en.ts | 2 + src/lib/account.test.ts | 24 +- src/lib/account.ts | 35 +- .../nip07-extension-key-mismatch-toast.tsx | 28 -- src/lib/pubkey-nip07.test.ts | 12 +- src/lib/pubkey.ts | 17 +- src/pages/primary/SpellsPage/index.tsx | 10 - src/providers/NostrProvider/index.tsx | 318 ++++++++++++------ src/providers/NostrProvider/nip-07.signer.ts | 20 ++ src/types/index.d.ts | 2 + 25 files changed, 492 insertions(+), 277 deletions(-) delete mode 100644 src/components/Nip07ExtensionKeyMismatchToast/index.tsx create mode 100644 src/hooks/useSignGatedControl.ts delete mode 100644 src/lib/nip07-extension-key-mismatch-toast.tsx 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({