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)
}