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