index.tsx raw

   1  import { useStuff } from '@/hooks/useStuff'
   2  import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
   3  import { useBookmarks } from '@/providers/BookmarksProvider'
   4  import { useNostr } from '@/providers/NostrProvider'
   5  import { BookmarkIcon, Loader } from 'lucide-react'
   6  import { Event } from 'nostr-tools'
   7  import { useMemo, useState } from 'react'
   8  import { useTranslation } from 'react-i18next'
   9  import { toast } from 'sonner'
  10  
  11  export default function BookmarkButton({ stuff }: { stuff: Event | string }) {
  12    const { t } = useTranslation()
  13    const { pubkey: accountPubkey, bookmarkListEvent, checkLogin } = useNostr()
  14    const { addBookmark, removeBookmark } = useBookmarks()
  15    const [updating, setUpdating] = useState(false)
  16    const { event } = useStuff(stuff)
  17    const isBookmarked = useMemo(() => {
  18      if (!event) return false
  19  
  20      const isReplaceable = isReplaceableEvent(event.kind)
  21      const eventKey = isReplaceable ? getReplaceableCoordinateFromEvent(event) : event.id
  22  
  23      return bookmarkListEvent?.tags.some((tag) =>
  24        isReplaceable ? tag[0] === 'a' && tag[1] === eventKey : tag[0] === 'e' && tag[1] === eventKey
  25      )
  26    }, [bookmarkListEvent, event])
  27  
  28    if (!accountPubkey) return null
  29  
  30    const handleBookmark = async (e: React.MouseEvent) => {
  31      e.stopPropagation()
  32      checkLogin(async () => {
  33        if (isBookmarked || !event) return
  34  
  35        setUpdating(true)
  36        try {
  37          await addBookmark(event)
  38        } catch (error) {
  39          toast.error(t('Bookmark failed') + ': ' + (error as Error).message)
  40        } finally {
  41          setUpdating(false)
  42        }
  43      })
  44    }
  45  
  46    const handleRemoveBookmark = async (e: React.MouseEvent) => {
  47      e.stopPropagation()
  48      checkLogin(async () => {
  49        if (!isBookmarked || !event) return
  50  
  51        setUpdating(true)
  52        try {
  53          await removeBookmark(event)
  54        } catch (error) {
  55          toast.error(t('Remove bookmark failed') + ': ' + (error as Error).message)
  56        } finally {
  57          setUpdating(false)
  58        }
  59      })
  60    }
  61  
  62    return (
  63      <button
  64        className={`flex items-center gap-1 ${
  65          isBookmarked ? 'text-rose-400' : 'text-muted-foreground'
  66        } enabled:hover:text-rose-400 px-3 h-full disabled:text-muted-foreground/40 disabled:cursor-default`}
  67        onClick={isBookmarked ? handleRemoveBookmark : handleBookmark}
  68        disabled={!event || updating}
  69        title={isBookmarked ? t('Remove bookmark') : t('Bookmark')}
  70      >
  71        {updating ? (
  72          <Loader className="animate-spin" />
  73        ) : (
  74          <BookmarkIcon className={isBookmarked ? 'fill-rose-400' : ''} />
  75        )}
  76      </button>
  77    )
  78  }
  79