141 lines
4.2 KiB
TypeScript
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>
|
|
)
|
|
}
|