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