index.tsx raw
1 import { toRelaySettings } from '@/lib/link'
2 import { simplifyUrl } from '@/lib/url'
3 import { cn } from '@/lib/utils'
4 import { SecondaryPageLink } from '@/PageManager'
5 import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
6 import { useFeed } from '@/providers/FeedProvider'
7 import { useNostr } from '@/providers/NostrProvider'
8 import { usePinnedUsers } from '@/providers/PinnedUsersProvider'
9 import { Settings2, Star, UsersRound } from 'lucide-react'
10 import { useMemo } from 'react'
11 import { useTranslation } from 'react-i18next'
12 import RelayIcon from '../RelayIcon'
13 import RelaySetCard from '../RelaySetCard'
14
15 export default function FeedSwitcher({ close }: { close?: () => void }) {
16 const { t } = useTranslation()
17 const { pubkey } = useNostr()
18 const { relaySets, favoriteRelays } = useFavoriteRelays()
19 const { feedInfo, switchFeed } = useFeed()
20 const { pinnedPubkeySet } = usePinnedUsers()
21 const filteredRelaySets = useMemo(
22 () => relaySets.filter((set) => set.relayUrls.length > 0),
23 [relaySets]
24 )
25 const hasRelays = filteredRelaySets.length > 0 || favoriteRelays.length > 0
26
27 return (
28 <div className="space-y-4">
29 {/* Personal Feeds Section */}
30 <div className="space-y-2">
31 <SectionHeader title={t('Personal Feeds')} />
32 <div className="space-y-1.5">
33 <FeedSwitcherItem
34 isActive={feedInfo?.feedType === 'following'}
35 disabled={!pubkey}
36 onClick={() => {
37 if (!pubkey) return
38 switchFeed('following', { pubkey })
39 close?.()
40 }}
41 >
42 <div className="flex gap-3 items-center">
43 <div className="flex justify-center items-center size-6 shrink-0">
44 <UsersRound className="size-5" />
45 </div>
46 <div className="flex-1">{t('Following')}</div>
47 </div>
48 </FeedSwitcherItem>
49
50 <FeedSwitcherItem
51 isActive={feedInfo?.feedType === 'pinned'}
52 disabled={!pubkey || pinnedPubkeySet.size === 0}
53 onClick={() => {
54 if (!pubkey) return
55 switchFeed('pinned', { pubkey })
56 close?.()
57 }}
58 >
59 <div className="flex gap-3 items-center">
60 <div className="flex justify-center items-center size-6 shrink-0">
61 <Star className="size-5" />
62 </div>
63 <div className="flex-1">{t('Special Follow')}</div>
64 </div>
65 </FeedSwitcherItem>
66 </div>
67 </div>
68
69 {/* Relay Feeds Section */}
70 {hasRelays && (
71 <div className="space-y-2">
72 <SectionHeader
73 title={t('Relay Feeds')}
74 action={
75 <SecondaryPageLink
76 to={toRelaySettings()}
77 className="flex items-center gap-1 text-xs text-primary hover:text-primary-hover transition-colors font-medium"
78 onClick={() => close?.()}
79 >
80 <Settings2 className="size-3" />
81 {t('edit')}
82 </SecondaryPageLink>
83 }
84 />
85 <div className="space-y-1.5">
86 {filteredRelaySets.map((set) => (
87 <RelaySetCard
88 key={set.id}
89 relaySet={set}
90 select={feedInfo?.feedType === 'relays' && set.id === feedInfo.id}
91 onSelectChange={(select) => {
92 if (!select) return
93 switchFeed('relays', { activeRelaySetId: set.id })
94 close?.()
95 }}
96 />
97 ))}
98 {favoriteRelays.map((relay) => (
99 <FeedSwitcherItem
100 key={relay}
101 isActive={feedInfo?.feedType === 'relay' && feedInfo.id === relay}
102 onClick={() => {
103 switchFeed('relay', { relay })
104 close?.()
105 }}
106 >
107 <div className="flex gap-3 items-center w-full">
108 <RelayIcon url={relay} className="shrink-0" />
109 <div className="flex-1 w-0 truncate">{simplifyUrl(relay)}</div>
110 </div>
111 </FeedSwitcherItem>
112 ))}
113 </div>
114 </div>
115 )}
116 </div>
117 )
118 }
119
120 function SectionHeader({ title, action }: { title: string; action?: React.ReactNode }) {
121 return (
122 <div className="flex justify-between items-center px-1 py-1">
123 <h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
124 {title}
125 </h3>
126 {action}
127 </div>
128 )
129 }
130
131 function FeedSwitcherItem({
132 children,
133 isActive,
134 disabled,
135 onClick
136 }: {
137 children: React.ReactNode
138 isActive: boolean
139 disabled?: boolean
140 onClick: () => void
141 }) {
142 return (
143 <div
144 className={cn(
145 'group relative w-full border rounded-lg px-3 py-2.5 transition-all duration-200',
146 disabled && 'opacity-50 pointer-events-none',
147 isActive
148 ? 'border-primary bg-primary/5 shadow-sm'
149 : 'border-border hover:border-primary/50 hover:bg-accent/50 clickable'
150 )}
151 onClick={() => {
152 if (disabled) return
153 onClick()
154 }}
155 >
156 <div className="flex justify-between items-center gap-2">
157 <div className="font-medium flex-1 min-w-0">{children}</div>
158 </div>
159 </div>
160 )
161 }
162