EmojiPack.tsx raw

   1  import { Button } from '@/components/ui/button'
   2  import { getReplaceableCoordinateFromEvent } from '@/lib/event'
   3  import { getEmojiPackInfoFromEvent } from '@/lib/event-metadata'
   4  import { useEmojiPack } from '@/providers/EmojiPackProvider'
   5  import { useNostr } from '@/providers/NostrProvider'
   6  import { CheckIcon, Loader, PlusIcon } from 'lucide-react'
   7  import { Event } from 'nostr-tools'
   8  import { useMemo, useState } from 'react'
   9  import { useTranslation } from 'react-i18next'
  10  import { toast } from 'sonner'
  11  import Image from '../Image'
  12  
  13  export default function EmojiPack({ event, className }: { event: Event; className?: string }) {
  14    const { t } = useTranslation()
  15    const { pubkey: accountPubkey, checkLogin } = useNostr()
  16    const { emojiPackCoordinateSet, addEmojiPack, removeEmojiPack } = useEmojiPack()
  17    const [updating, setUpdating] = useState(false)
  18    const { title, emojis } = useMemo(() => getEmojiPackInfoFromEvent(event), [event])
  19    const coordinate = useMemo(() => getReplaceableCoordinateFromEvent(event), [event])
  20    const isCollected = useMemo(() => {
  21      return emojiPackCoordinateSet.has(coordinate)
  22    }, [emojiPackCoordinateSet, coordinate])
  23  
  24    const handleCollect = async (e: React.MouseEvent) => {
  25      e.stopPropagation()
  26      checkLogin(async () => {
  27        if (isCollected) return
  28  
  29        setUpdating(true)
  30        try {
  31          await addEmojiPack(event)
  32          toast.success(t('Emoji pack added'))
  33        } catch (error) {
  34          toast.error(t('Add emoji pack failed') + ': ' + (error as Error).message)
  35        } finally {
  36          setUpdating(false)
  37        }
  38      })
  39    }
  40  
  41    const handleRemoveCollect = async (e: React.MouseEvent) => {
  42      e.stopPropagation()
  43      checkLogin(async () => {
  44        if (!isCollected) return
  45  
  46        setUpdating(true)
  47        try {
  48          await removeEmojiPack(event)
  49          toast.success(t('Emoji pack removed'))
  50        } catch (error) {
  51          toast.error(t('Remove emoji pack failed') + ': ' + (error as Error).message)
  52        } finally {
  53          setUpdating(false)
  54        }
  55      })
  56    }
  57  
  58    return (
  59      <div className={className}>
  60        <div className="flex items-center justify-between mb-2">
  61          <h3 className="text-2xl font-semibold">{title}</h3>
  62          {accountPubkey && (
  63            <Button
  64              variant={isCollected ? 'secondary' : 'outline'}
  65              size="sm"
  66              onClick={isCollected ? handleRemoveCollect : handleCollect}
  67              disabled={updating}
  68              className="shrink-0"
  69            >
  70              {updating ? (
  71                <Loader className="animate-spin mr-1" />
  72              ) : isCollected ? (
  73                <CheckIcon />
  74              ) : (
  75                <PlusIcon />
  76              )}
  77              {updating
  78                ? isCollected
  79                  ? t('Removing...')
  80                  : t('Adding...')
  81                : isCollected
  82                  ? t('Added')
  83                  : t('Add')}
  84            </Button>
  85          )}
  86        </div>
  87        <div className="flex flex-wrap gap-1">
  88          {emojis.map((emoji, index) => (
  89            <Image
  90              key={`emoji-${index}`}
  91              image={{ url: emoji.url, pubkey: event.pubkey }}
  92              className="size-14 object-contain"
  93              classNames={{
  94                wrapper: 'size-14 flex items-center justify-center p-1',
  95                errorPlaceholder: 'size-14'
  96              }}
  97              hideIfError
  98            />
  99          ))}
 100        </div>
 101      </div>
 102    )
 103  }
 104