Files
smesh/src/components/NoteStats/LikeButton.tsx

141 lines
4.2 KiB
TypeScript

import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { useNoteStatsById } from '@/hooks/useNoteStatsById'
import { createReactionDraftEvent } from '@/lib/draft-event'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import noteStatsService from '@/services/note-stats.service'
import { Loader, SmilePlus } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Emoji from '../Emoji'
import EmojiPicker from '../EmojiPicker'
import SuggestedEmojis from '../SuggestedEmojis'
import { formatCount } from './utils'
export default function LikeButton({ event }: { event: Event }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { pubkey, publish, checkLogin } = useNostr()
const [liking, setLiking] = useState(false)
const [isEmojiReactionsOpen, setIsEmojiReactionsOpen] = useState(false)
const [isPickerOpen, setIsPickerOpen] = useState(false)
const noteStats = useNoteStatsById(event.id)
const { myLastEmoji, likeCount } = useMemo(() => {
const stats = noteStats || {}
const like = stats.likes?.find((like) => like.pubkey === pubkey)
return { myLastEmoji: like?.emoji, likeCount: stats.likes?.length }
}, [noteStats, pubkey])
const like = async (emoji: string) => {
checkLogin(async () => {
if (liking || !pubkey) return
setLiking(true)
const timer = setTimeout(() => setLiking(false), 10_000)
try {
if (!noteStats?.updatedAt) {
await noteStatsService.fetchNoteStats(event, pubkey)
}
const reaction = createReactionDraftEvent(event, emoji)
const evt = await publish(reaction)
noteStatsService.updateNoteStatsByEvents([evt])
} catch (error) {
console.error('like failed', error)
} finally {
setLiking(false)
clearTimeout(timer)
}
})
}
const trigger = (
<button
className="flex items-center enabled:hover:text-primary gap-1 px-3 h-full text-muted-foreground"
title={t('Like')}
onClick={() => {
if (isSmallScreen) {
setIsEmojiReactionsOpen(true)
}
}}
>
{liking ? (
<Loader className="animate-spin" />
) : myLastEmoji ? (
<>
<div className="h-5 w-5 flex items-center justify-center">
<Emoji emoji={myLastEmoji} />
</div>
{!!likeCount && <div className="text-sm">{formatCount(likeCount)}</div>}
</>
) : (
<>
<SmilePlus />
{!!likeCount && <div className="text-sm">{formatCount(likeCount)}</div>}
</>
)}
</button>
)
if (isSmallScreen) {
return (
<>
{trigger}
<Drawer open={isEmojiReactionsOpen} onOpenChange={setIsEmojiReactionsOpen}>
<DrawerOverlay onClick={() => setIsEmojiReactionsOpen(false)} />
<DrawerContent hideOverlay>
<EmojiPicker
onEmojiClick={(data) => {
setIsEmojiReactionsOpen(false)
like(data.emoji)
}}
/>
</DrawerContent>
</Drawer>
</>
)
}
return (
<DropdownMenu
open={isEmojiReactionsOpen}
onOpenChange={(open) => {
setIsEmojiReactionsOpen(open)
if (open) {
setIsPickerOpen(false)
}
}}
>
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
<DropdownMenuContent side="top" className="p-0 w-fit">
{isPickerOpen ? (
<EmojiPicker
onEmojiClick={(data, e) => {
e.stopPropagation()
setIsEmojiReactionsOpen(false)
like(data.emoji)
}}
/>
) : (
<SuggestedEmojis
onEmojiClick={(emoji) => {
setIsEmojiReactionsOpen(false)
like(emoji)
}}
onMoreButtonClick={() => {
setIsPickerOpen(true)
}}
/>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}