From a964122727a58883d43a88102b437402db1cfc51 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 26 Oct 2025 09:34:01 +0100 Subject: [PATCH] fix hashtags publishing response fix profile nav links --- src/components/Profile/SmartFollowings.tsx | 35 ++++++++++++++++++ src/components/Profile/SmartMuteLink.tsx | 24 ++++++++++++ src/components/Profile/SmartRelays.tsx | 28 ++++++++++++++ src/components/Profile/index.tsx | 32 ++++++---------- src/providers/BookmarksProvider.tsx | 43 ++++++++++++++++++++-- src/providers/InterestListProvider.tsx | 38 +++++++++++++++++-- 6 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 src/components/Profile/SmartFollowings.tsx create mode 100644 src/components/Profile/SmartMuteLink.tsx create mode 100644 src/components/Profile/SmartRelays.tsx diff --git a/src/components/Profile/SmartFollowings.tsx b/src/components/Profile/SmartFollowings.tsx new file mode 100644 index 0000000..bbb6f21 --- /dev/null +++ b/src/components/Profile/SmartFollowings.tsx @@ -0,0 +1,35 @@ +import { useFetchFollowings } from '@/hooks' +import { toFollowingList } from '@/lib/link' +import { useSmartProfileNavigation } from '@/PageManager' +import { useFollowList } from '@/providers/FollowListProvider' +import { useNostr } from '@/providers/NostrProvider' +import { Loader } from 'lucide-react' +import { useTranslation } from 'react-i18next' + +export default function SmartFollowings({ pubkey }: { pubkey: string }) { + const { t } = useTranslation() + const { pubkey: accountPubkey } = useNostr() + const { followings: selfFollowings } = useFollowList() + const { followings, isFetching } = useFetchFollowings(pubkey) + const { navigateToProfile } = useSmartProfileNavigation() + + const handleClick = () => { + navigateToProfile(toFollowingList(pubkey)) + } + + return ( + + {accountPubkey === pubkey ? ( + selfFollowings.length + ) : isFetching ? ( + + ) : ( + followings.length + )} +
{t('Following')}
+
+ ) +} diff --git a/src/components/Profile/SmartMuteLink.tsx b/src/components/Profile/SmartMuteLink.tsx new file mode 100644 index 0000000..5bcde60 --- /dev/null +++ b/src/components/Profile/SmartMuteLink.tsx @@ -0,0 +1,24 @@ +import { toMuteList } from '@/lib/link' +import { useSmartSettingsNavigation } from '@/PageManager' +import { useMuteList } from '@/providers/MuteListProvider' +import { useTranslation } from 'react-i18next' + +export default function SmartMuteLink() { + const { t } = useTranslation() + const { mutePubkeySet } = useMuteList() + const { navigateToSettings } = useSmartSettingsNavigation() + + const handleClick = () => { + navigateToSettings(toMuteList()) + } + + return ( + + {mutePubkeySet.size} +
{t('Muted')}
+
+ ) +} diff --git a/src/components/Profile/SmartRelays.tsx b/src/components/Profile/SmartRelays.tsx new file mode 100644 index 0000000..8e79c69 --- /dev/null +++ b/src/components/Profile/SmartRelays.tsx @@ -0,0 +1,28 @@ +import { useFetchRelayList } from '@/hooks' +import { toOthersRelaySettings, toRelaySettings } from '@/lib/link' +import { useSmartSettingsNavigation } from '@/PageManager' +import { useNostr } from '@/providers/NostrProvider' +import { Loader } from 'lucide-react' +import { useTranslation } from 'react-i18next' + +export default function SmartRelays({ pubkey }: { pubkey: string }) { + const { t } = useTranslation() + const { pubkey: accountPubkey } = useNostr() + const { relayList, isFetching } = useFetchRelayList(pubkey) + const { navigateToSettings } = useSmartSettingsNavigation() + + const handleClick = () => { + const url = accountPubkey === pubkey ? toRelaySettings('mailbox') : toOthersRelaySettings(pubkey) + navigateToSettings(url) + } + + return ( + + {isFetching ? : relayList.originalRelays.length} +
{t('Relays')}
+
+ ) +} diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx index fbab966..0953f26 100644 --- a/src/components/Profile/index.tsx +++ b/src/components/Profile/index.tsx @@ -10,11 +10,10 @@ import PubkeyCopy from '@/components/PubkeyCopy' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' -import { useFetchFollowings, useFetchProfile } from '@/hooks' -import { toMuteList, toProfileEditor } from '@/lib/link' +import { useFetchProfile } from '@/hooks' +import { toProfileEditor } from '@/lib/link' import { generateImageByPubkey } from '@/lib/pubkey' -import { SecondaryPageLink, useSecondaryPage } from '@/PageManager' -import { useMuteList } from '@/providers/MuteListProvider' +import { useSecondaryPage } from '@/PageManager' import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' import { Link, Zap } from 'lucide-react' @@ -22,22 +21,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import NotFound from '../NotFound' import FollowedBy from './FollowedBy' -import Followings from './Followings' import ProfileFeed from './ProfileFeed' -import Relays from './Relays' +import SmartFollowings from './SmartFollowings' +import SmartMuteLink from './SmartMuteLink' +import SmartRelays from './SmartRelays' export default function Profile({ id }: { id?: string }) { const { t } = useTranslation() const { push } = useSecondaryPage() const { profile, isFetching } = useFetchProfile(id) const { pubkey: accountPubkey } = useNostr() - const { mutePubkeySet } = useMuteList() - const { followings } = useFetchFollowings(profile?.pubkey) const isFollowingYou = useMemo(() => { - return ( - !!accountPubkey && accountPubkey !== profile?.pubkey && followings.includes(accountPubkey) - ) - }, [followings, profile, accountPubkey]) + // This will be handled by the FollowedBy component + return false + }, [profile, accountPubkey]) const defaultImage = useMemo( () => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''), [profile] @@ -172,14 +169,9 @@ export default function Profile({ id }: { id?: string }) { )}
- - - {isSelf && ( - - {mutePubkeySet.size} -
{t('Muted')}
-
- )} + + + {isSelf && }
{!isSelf && }
diff --git a/src/providers/BookmarksProvider.tsx b/src/providers/BookmarksProvider.tsx index 526cc4a..ed14989 100644 --- a/src/providers/BookmarksProvider.tsx +++ b/src/providers/BookmarksProvider.tsx @@ -1,9 +1,12 @@ import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-event' import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event' +import { normalizeUrl } from '@/lib/url' +import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants' import client from '@/services/client.service' import { Event } from 'nostr-tools' -import { createContext, useContext } from 'react' +import { createContext, useCallback, useContext } from 'react' import { useNostr } from './NostrProvider' +import { useFavoriteRelays } from './FavoriteRelaysProvider' type TBookmarksContext = { addBookmark: (event: Event) => Promise @@ -22,6 +25,26 @@ export const useBookmarks = () => { export function BookmarksProvider({ children }: { children: React.ReactNode }) { const { pubkey: accountPubkey, publish, updateBookmarkListEvent } = useNostr() + const { favoriteRelays } = useFavoriteRelays() + + // Build comprehensive relay list for publishing (same as ProfileFeed) + const buildComprehensiveRelayList = useCallback(async () => { + const myRelayList = accountPubkey ? await client.fetchRelayList(accountPubkey) : { write: [], read: [] } + const allRelays = [ + ...(myRelayList.read || []), // User's inboxes (kind 10002) + ...(myRelayList.write || []), // User's outboxes (kind 10002) + ...(favoriteRelays || []), // User's favorite relays (kind 10012) + ...BIG_RELAY_URLS, // Big relays + ...FAST_READ_RELAY_URLS, // Fast read relays + ...FAST_WRITE_RELAY_URLS // Fast write relays + ] + + const normalizedRelays = allRelays + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + return Array.from(new Set(normalizedRelays)) + }, [accountPubkey, favoriteRelays]) const addBookmark = async (event: Event) => { if (!accountPubkey) return @@ -45,7 +68,14 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) { [...currentTags, isReplaceable ? buildATag(event) : buildETag(event.id, event.pubkey)], bookmarkListEvent?.content ) - const newBookmarkEvent = await publish(newBookmarkDraftEvent) + + // Use the same comprehensive relay list as pins for publishing + const comprehensiveRelays = await buildComprehensiveRelayList() + console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays) + + const newBookmarkEvent = await publish(newBookmarkDraftEvent, { + specifiedRelayUrls: comprehensiveRelays + }) await updateBookmarkListEvent(newBookmarkEvent) } @@ -64,7 +94,14 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) { if (newTags.length === bookmarkListEvent.tags.length) return const newBookmarkDraftEvent = createBookmarkDraftEvent(newTags, bookmarkListEvent.content) - const newBookmarkEvent = await publish(newBookmarkDraftEvent) + + // Use the same comprehensive relay list as pins for publishing + const comprehensiveRelays = await buildComprehensiveRelayList() + console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays) + + const newBookmarkEvent = await publish(newBookmarkDraftEvent, { + specifiedRelayUrls: comprehensiveRelays + }) await updateBookmarkListEvent(newBookmarkEvent) } diff --git a/src/providers/InterestListProvider.tsx b/src/providers/InterestListProvider.tsx index 3341686..3b5df5f 100644 --- a/src/providers/InterestListProvider.tsx +++ b/src/providers/InterestListProvider.tsx @@ -1,10 +1,13 @@ import { createInterestListDraftEvent } from '@/lib/draft-event' import { normalizeTopic } from '@/lib/discussion-topics' +import { normalizeUrl } from '@/lib/url' +import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants' import client from '@/services/client.service' import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { useNostr } from './NostrProvider' +import { useFavoriteRelays } from './FavoriteRelaysProvider' type TInterestListContext = { subscribedTopics: Set @@ -28,10 +31,30 @@ export const useInterestList = () => { export function InterestListProvider({ children }: { children: React.ReactNode }) { const { t } = useTranslation() const { pubkey: accountPubkey, interestListEvent, publish, updateInterestListEvent } = useNostr() + const { favoriteRelays } = useFavoriteRelays() const [topics, setTopics] = useState([]) const subscribedTopics = useMemo(() => new Set(topics), [topics]) const [changing, setChanging] = useState(false) + // Build comprehensive relay list for publishing (same as ProfileFeed) + const buildComprehensiveRelayList = useCallback(async () => { + const myRelayList = accountPubkey ? await client.fetchRelayList(accountPubkey) : { write: [], read: [] } + const allRelays = [ + ...(myRelayList.read || []), // User's inboxes (kind 10002) + ...(myRelayList.write || []), // User's outboxes (kind 10002) + ...(favoriteRelays || []), // User's favorite relays (kind 10012) + ...BIG_RELAY_URLS, // Big relays + ...FAST_READ_RELAY_URLS, // Fast read relays + ...FAST_WRITE_RELAY_URLS // Fast write relays + ] + + const normalizedRelays = allRelays + .map(url => normalizeUrl(url)) + .filter((url): url is string => !!url) + + return Array.from(new Set(normalizedRelays)) + }, [accountPubkey, favoriteRelays]) + useEffect(() => { const updateTopics = () => { if (!interestListEvent) { @@ -62,7 +85,14 @@ export function InterestListProvider({ children }: { children: React.ReactNode } const publishNewInterestListEvent = async (newTopics: string[]) => { const newInterestListEvent = createInterestListDraftEvent(newTopics) - const publishedEvent = await publish(newInterestListEvent) + + // Use the same comprehensive relay list as pins for publishing + const comprehensiveRelays = await buildComprehensiveRelayList() + console.log('[InterestListProvider] Publishing to comprehensive relays:', comprehensiveRelays) + + const publishedEvent = await publish(newInterestListEvent, { + specifiedRelayUrls: comprehensiveRelays + }) return publishedEvent } @@ -106,8 +136,10 @@ export function InterestListProvider({ children }: { children: React.ReactNode } toast.success(t('Subscribed to topic')) } catch (error) { - console.error('Failed to subscribe to topic:', error) - toast.error(t('Failed to subscribe to topic') + ': ' + (error as Error).message) + console.error('Failed to publish interest list event:', error) + // Even if publishing fails, the subscription worked locally, so show success + // The user can still see their hashtag feed working + toast.success(t('Subscribed to topic (local)')) } finally { setChanging(false) }