diff --git a/src/components/MailboxSetting/index.tsx b/src/components/MailboxSetting/index.tsx
index 645225ac..fbe79d69 100644
--- a/src/components/MailboxSetting/index.tsx
+++ b/src/components/MailboxSetting/index.tsx
@@ -1,4 +1,5 @@
import { Button } from '@/components/ui/button'
+import { relayListToMailboxRelay } from '@/lib/relay'
import { normalizeUrl } from '@/lib/url'
import { useNostr } from '@/providers/NostrProvider'
import { TMailboxRelay, TMailboxRelayScope } from '@/types'
@@ -17,16 +18,7 @@ export default function MailboxSetting() {
useEffect(() => {
if (!relayList) return
- const mailboxRelays: TMailboxRelay[] = relayList.read.map((url) => ({ url, scope: 'read' }))
- relayList.write.forEach((url) => {
- const item = mailboxRelays.find((r) => r.url === url)
- if (item) {
- item.scope = 'both'
- } else {
- mailboxRelays.push({ url, scope: 'write' })
- }
- })
- setRelays(mailboxRelays)
+ setRelays(relayListToMailboxRelay(relayList))
}, [relayList])
if (!pubkey) {
diff --git a/src/components/OthersRelayList/index.tsx b/src/components/OthersRelayList/index.tsx
new file mode 100644
index 00000000..a1329dd9
--- /dev/null
+++ b/src/components/OthersRelayList/index.tsx
@@ -0,0 +1,66 @@
+import { useSecondaryPage } from '@/PageManager'
+import { Button } from '@/components/ui/button'
+import { useFetchRelayList } from '@/hooks/useFetchRelayList'
+import { toNoteList } from '@/lib/link'
+import { userIdToPubkey } from '@/lib/pubkey'
+import { relayListToMailboxRelay } from '@/lib/relay'
+import { simplifyUrl } from '@/lib/url'
+import { TMailboxRelay } from '@/types'
+import { ListPlus, Telescope } from 'lucide-react'
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import RelayIcon from '../RelayIcon'
+import SaveRelayDropdownMenu from '../SaveRelayDropdownMenu'
+import { Badge } from '../ui/badge'
+
+export default function OthersRelayList({ userId }: { userId: string }) {
+ const { t } = useTranslation()
+ const pubkey = useMemo(() => userIdToPubkey(userId), [userId])
+ const { relayList, isFetching } = useFetchRelayList(pubkey)
+ const mailboxRelays = useMemo(() => relayListToMailboxRelay(relayList), [relayList])
+
+ if (isFetching) {
+ return
{t('loading...')}
+ }
+
+ return (
+
+ {mailboxRelays.map((relay, index) => (
+
+ ))}
+
+ )
+}
+
+function RelayItem({ relay }: { relay: TMailboxRelay }) {
+ const { t } = useTranslation()
+ const { push } = useSecondaryPage()
+ const { url, scope } = relay
+
+ return (
+
+
push(toNoteList({ relay: url }))}
+ >
+
+
{simplifyUrl(url)}
+
+
+ {scope === 'read' ? (
+ {t('Read')}
+ ) : scope === 'write' ? (
+ {t('Write')}
+ ) : null}
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/RelayIcon/index.tsx b/src/components/RelayIcon/index.tsx
new file mode 100644
index 00000000..86e8cb6c
--- /dev/null
+++ b/src/components/RelayIcon/index.tsx
@@ -0,0 +1,27 @@
+import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
+import { Server } from 'lucide-react'
+import { useMemo } from 'react'
+
+export default function RelayIcon({
+ url,
+ className = 'w-6 h-6',
+ iconSize = 14
+}: {
+ url: string
+ className?: string
+ iconSize?: number
+}) {
+ const icon = useMemo(() => {
+ const u = new URL(url)
+ return `${u.protocol === 'wss:' ? 'https:' : 'http:'}//${u.host}/favicon.ico`
+ }, [url])
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 2a2b3107..99532dab 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -48,6 +48,7 @@ export default {
'switch to system theme': 'switch to system theme',
Note: 'Note',
"username's following": "{{username}}'s following",
+ "username's used relays": "{{username}}'s used relays",
Login: 'Login',
'Follows you': 'Follows you',
'Relay Settings': 'Relay Settings',
diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts
index c9ca2ff7..d191ebcf 100644
--- a/src/i18n/zh.ts
+++ b/src/i18n/zh.ts
@@ -48,6 +48,7 @@ export default {
'switch to system theme': '切换到系统主题',
Note: '笔记',
"username's following": '{{username}} 的关注',
+ "username's used relays": '{{username}} 使用的服务器',
Login: '登录',
'Follows you': '关注了你',
'Relay Settings': '服务器设置',
diff --git a/src/lib/event.ts b/src/lib/event.ts
index 54521e95..e6210ae4 100644
--- a/src/lib/event.ts
+++ b/src/lib/event.ts
@@ -90,10 +90,10 @@ export function getRelayListFromRelayListEvent(event?: Event) {
const normalizedUrl = normalizeUrl(url)
switch (type) {
- case 'w':
+ case 'write':
relayList.write.push(normalizedUrl)
break
- case 'r':
+ case 'read':
relayList.read.push(normalizedUrl)
break
default:
@@ -102,8 +102,8 @@ export function getRelayListFromRelayListEvent(event?: Event) {
}
})
return {
- write: relayList.write.length ? relayList.write.slice(0, 10) : BIG_RELAY_URLS,
- read: relayList.read.length ? relayList.read.slice(0, 10) : BIG_RELAY_URLS
+ write: relayList.write.length ? relayList.write : BIG_RELAY_URLS,
+ read: relayList.read.length ? relayList.read : BIG_RELAY_URLS
}
}
diff --git a/src/lib/link.ts b/src/lib/link.ts
index 9d2b3075..d1194cfb 100644
--- a/src/lib/link.ts
+++ b/src/lib/link.ts
@@ -37,6 +37,10 @@ export const toFollowingList = (pubkey: string) => {
const npub = nip19.npubEncode(pubkey)
return `/users/${npub}/following`
}
+export const toOthersRelaySettings = (pubkey: string) => {
+ const npub = nip19.npubEncode(pubkey)
+ return `/users/${npub}/relays`
+}
export const toRelaySettings = () => '/relay-settings'
export const toSettings = () => '/settings'
export const toProfileEditor = () => '/profile-editor'
diff --git a/src/lib/relay.ts b/src/lib/relay.ts
index 738f1244..24da82bb 100644
--- a/src/lib/relay.ts
+++ b/src/lib/relay.ts
@@ -1,4 +1,4 @@
-import { TRelayInfo } from '@/types'
+import { TMailboxRelay, TRelayInfo, TRelayList } from '@/types'
export function checkAlgoRelay(relayInfo: TRelayInfo | undefined) {
return relayInfo?.software === 'https://github.com/bitvora/algo-relay' // hardcode for now
@@ -7,3 +7,16 @@ export function checkAlgoRelay(relayInfo: TRelayInfo | undefined) {
export function checkSearchRelay(relayInfo: TRelayInfo | undefined) {
return relayInfo?.supported_nips?.includes(50)
}
+
+export function relayListToMailboxRelay(relayList: TRelayList): TMailboxRelay[] {
+ const mailboxRelays: TMailboxRelay[] = relayList.read.map((url) => ({ url, scope: 'read' }))
+ relayList.write.forEach((url) => {
+ const item = mailboxRelays.find((r) => r.url === url)
+ if (item) {
+ item.scope = 'both'
+ } else {
+ mailboxRelays.push({ url, scope: 'write' })
+ }
+ })
+ return mailboxRelays
+}
diff --git a/src/pages/secondary/OthersRelaySettingsPage/index.tsx b/src/pages/secondary/OthersRelaySettingsPage/index.tsx
new file mode 100644
index 00000000..9f95dc7d
--- /dev/null
+++ b/src/pages/secondary/OthersRelaySettingsPage/index.tsx
@@ -0,0 +1,24 @@
+import OthersRelayList from '@/components/OthersRelayList'
+import { useFetchProfile } from '@/hooks'
+import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
+import { useTranslation } from 'react-i18next'
+
+export default function RelaySettingsPage({ id, index }: { id?: string; index?: number }) {
+ const { t } = useTranslation()
+ const { profile } = useFetchProfile(id)
+
+ if (!id || !profile) {
+ return null
+ }
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/pages/secondary/ProfilePage/index.tsx b/src/pages/secondary/ProfilePage/index.tsx
index 6168e115..4f09a934 100644
--- a/src/pages/secondary/ProfilePage/index.tsx
+++ b/src/pages/secondary/ProfilePage/index.tsx
@@ -11,7 +11,12 @@ import { Skeleton } from '@/components/ui/skeleton'
import { useFetchFollowings, useFetchProfile } from '@/hooks'
import { useFetchRelayList } from '@/hooks/useFetchRelayList'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
-import { toFollowingList, toProfileEditor } from '@/lib/link'
+import {
+ toFollowingList,
+ toOthersRelaySettings,
+ toProfileEditor,
+ toRelaySettings
+} from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey'
import { SecondaryPageLink, useSecondaryPage } from '@/PageManager'
import { useFeed } from '@/providers/FeedProvider'
@@ -46,6 +51,10 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
[profile]
)
+ const relayCount = useMemo(
+ () => new Set(relayList.write.concat(relayList.read)).size,
+ [relayList]
+ )
const isSelf = accountPubkey === profile?.pubkey
if (!profile && isFetching) {
@@ -107,13 +116,22 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number