index.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import {
   3    Drawer,
   4    DrawerContent,
   5    DrawerHeader,
   6    DrawerOverlay,
   7    DrawerTitle
   8  } from '@/components/ui/drawer'
   9  import {
  10    DropdownMenu,
  11    DropdownMenuContent,
  12    DropdownMenuItem,
  13    DropdownMenuLabel,
  14    DropdownMenuSeparator,
  15    DropdownMenuTrigger
  16  } from '@/components/ui/dropdown-menu'
  17  import { Separator } from '@/components/ui/separator'
  18  import { normalizeRelayUrl } from '@/domain/relay/adapters'
  19  import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
  20  import { useNostr } from '@/providers/NostrProvider'
  21  import { useScreenSize } from '@/providers/ScreenSizeProvider'
  22  import { TRelaySet } from '@/types'
  23  import { Check, FolderPlus, Plus, Star } from 'lucide-react'
  24  import { useMemo, useState } from 'react'
  25  import { useTranslation } from 'react-i18next'
  26  import DrawerMenuItem from '../DrawerMenuItem'
  27  
  28  export default function SaveRelayDropdownMenu({
  29    urls,
  30    bigButton = false
  31  }: {
  32    urls: string[]
  33    bigButton?: boolean
  34  }) {
  35    const { t } = useTranslation()
  36    const { isSmallScreen } = useScreenSize()
  37    const { favoriteRelays, relaySets } = useFavoriteRelays()
  38    const normalizedUrls = useMemo(() => urls.map((url) => normalizeRelayUrl(url)).filter((url): url is string => url !== null), [urls])
  39    const alreadySaved = useMemo(() => {
  40      return (
  41        normalizedUrls.every((url) => favoriteRelays.includes(url)) ||
  42        relaySets.some((set) => normalizedUrls.every((url) => set.relayUrls.includes(url)))
  43      )
  44    }, [relaySets, normalizedUrls])
  45    const [isDrawerOpen, setIsDrawerOpen] = useState(false)
  46  
  47    const trigger = bigButton ? (
  48      <Button variant="ghost" size="titlebar-icon" onClick={() => setIsDrawerOpen(true)}>
  49        <Star className={alreadySaved ? 'fill-primary stroke-primary' : ''} />
  50      </Button>
  51    ) : (
  52      <button
  53        className="enabled:hover:text-primary [&_svg]:size-5 pr-0 pt-0.5"
  54        onClick={(e) => {
  55          e.stopPropagation()
  56          setIsDrawerOpen(true)
  57        }}
  58      >
  59        <Star className={alreadySaved ? 'fill-primary stroke-primary' : ''} />
  60      </button>
  61    )
  62  
  63    if (isSmallScreen) {
  64      return (
  65        <div>
  66          {trigger}
  67          <div onClick={(e) => e.stopPropagation()}>
  68            <Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
  69              <DrawerOverlay onClick={() => setIsDrawerOpen(false)} />
  70              <DrawerContent hideOverlay>
  71                <DrawerHeader>
  72                  <DrawerTitle>{t('Save to')} ...</DrawerTitle>
  73                </DrawerHeader>
  74                <div className="py-2">
  75                  <RelayItem urls={normalizedUrls} />
  76                  {relaySets.map((set) => (
  77                    <RelaySetItem key={set.id} set={set} urls={normalizedUrls} />
  78                  ))}
  79                  <Separator />
  80                  <SaveToNewSet urls={normalizedUrls} />
  81                </div>
  82              </DrawerContent>
  83            </Drawer>
  84          </div>
  85        </div>
  86      )
  87    }
  88  
  89    return (
  90      <DropdownMenu>
  91        <DropdownMenuTrigger asChild className="px-2">
  92          {trigger}
  93        </DropdownMenuTrigger>
  94        <DropdownMenuContent onClick={(e) => e.stopPropagation()}>
  95          <DropdownMenuLabel>{t('Save to')} ...</DropdownMenuLabel>
  96          <DropdownMenuSeparator />
  97          <RelayItem urls={normalizedUrls} />
  98          {relaySets.map((set) => (
  99            <RelaySetItem key={set.id} set={set} urls={normalizedUrls} />
 100          ))}
 101          <DropdownMenuSeparator />
 102          <SaveToNewSet urls={normalizedUrls} />
 103        </DropdownMenuContent>
 104      </DropdownMenu>
 105    )
 106  }
 107  
 108  function RelayItem({ urls }: { urls: string[] }) {
 109    const { t } = useTranslation()
 110    const { isSmallScreen } = useScreenSize()
 111    const { favoriteRelays, addFavoriteRelays, deleteFavoriteRelays } = useFavoriteRelays()
 112    const saved = useMemo(
 113      () => urls.every((url) => favoriteRelays.includes(url)),
 114      [favoriteRelays, urls]
 115    )
 116  
 117    const handleClick = async () => {
 118      if (saved) {
 119        await deleteFavoriteRelays(urls)
 120      } else {
 121        await addFavoriteRelays(urls)
 122      }
 123    }
 124  
 125    if (isSmallScreen) {
 126      return (
 127        <DrawerMenuItem onClick={handleClick}>
 128          {saved ? <Check /> : <Plus />}
 129          {saved ? t('Unfavorite') : t('Favorite')}
 130        </DrawerMenuItem>
 131      )
 132    }
 133  
 134    return (
 135      <DropdownMenuItem className="flex gap-2" onClick={handleClick}>
 136        {saved ? <Check /> : <Plus />}
 137        {saved ? t('Unfavorite') : t('Favorite')}
 138      </DropdownMenuItem>
 139    )
 140  }
 141  
 142  function RelaySetItem({ set, urls }: { set: TRelaySet; urls: string[] }) {
 143    const { isSmallScreen } = useScreenSize()
 144    const { pubkey, startLogin } = useNostr()
 145    const { updateRelaySet } = useFavoriteRelays()
 146    const saved = urls.every((url) => set.relayUrls.includes(url))
 147  
 148    const handleClick = () => {
 149      if (!pubkey) {
 150        startLogin()
 151        return
 152      }
 153      if (saved) {
 154        updateRelaySet({
 155          ...set,
 156          relayUrls: set.relayUrls.filter((u) => !urls.includes(u))
 157        })
 158      } else {
 159        updateRelaySet({
 160          ...set,
 161          relayUrls: Array.from(new Set([...set.relayUrls, ...urls]))
 162        })
 163      }
 164    }
 165  
 166    if (isSmallScreen) {
 167      return (
 168        <DrawerMenuItem onClick={handleClick}>
 169          {saved ? <Check /> : <Plus />}
 170          {set.name}
 171        </DrawerMenuItem>
 172      )
 173    }
 174  
 175    return (
 176      <DropdownMenuItem key={set.id} className="flex gap-2" onClick={handleClick}>
 177        {saved ? <Check /> : <Plus />}
 178        {set.name}
 179      </DropdownMenuItem>
 180    )
 181  }
 182  
 183  function SaveToNewSet({ urls }: { urls: string[] }) {
 184    const { t } = useTranslation()
 185    const { isSmallScreen } = useScreenSize()
 186    const { pubkey, startLogin } = useNostr()
 187    const { createRelaySet } = useFavoriteRelays()
 188  
 189    const handleSave = () => {
 190      if (!pubkey) {
 191        startLogin()
 192        return
 193      }
 194      const newSetName = prompt(t('Enter a name for the new relay set'))
 195      if (newSetName) {
 196        createRelaySet(newSetName, urls)
 197      }
 198    }
 199  
 200    if (isSmallScreen) {
 201      return (
 202        <DrawerMenuItem onClick={handleSave}>
 203          <FolderPlus />
 204          {t('Save to a new relay set')}
 205        </DrawerMenuItem>
 206      )
 207    }
 208  
 209    return (
 210      <DropdownMenuItem onClick={handleSave}>
 211        <FolderPlus />
 212        {t('Save to a new relay set')}
 213      </DropdownMenuItem>
 214    )
 215  }
 216