import { resolveHttpMediaUrl } from '@/lib/badge-definition-media' import { getImetaInfosFromEvent } from '@/lib/event' import { getPubkeysFromPTags } from '@/lib/tag' import logger from '@/lib/logger' import { cn } from '@/lib/utils' import { useFollowListOptional } from '@/providers/follow-list-context' import { useMuteList } from '@/contexts/mute-list-context' import { muteSetHas } from '@/lib/mute-set' import { useNostr } from '@/providers/NostrProvider' import { Event } from 'nostr-tools' import { Users } from 'lucide-react' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import UserAvatar, { SimpleUserAvatar } from '@/components/UserAvatar' import Username from '@/components/Username' import { Button } from '@/components/ui/button' /** NIP-style `image` tags on kind 39089; falls back to first NIP-94 `imeta` URL. */ function followPackBannerUrlFromEvent(event: Event): string | undefined { for (const t of event.tags) { if (t[0] === 'image' && t[1]) { const u = resolveHttpMediaUrl(t[1]) if (u) return u } } for (const im of getImetaInfosFromEvent(event)) { const u = resolveHttpMediaUrl(im.url) if (u) return u } return undefined } export default function FollowPackPreview({ event, className }: { event: Event className?: string }) { const { t } = useTranslation() const { pubkey, canManageIdentity } = useNostr() const followList = useFollowListOptional() const followings = followList?.followings ?? [] const { mutePubkeySet } = useMuteList() const [busy, setBusy] = useState(false) const [bannerFailed, setBannerFailed] = useState(false) const packPubkeys = useMemo(() => getPubkeysFromPTags(event.tags), [event.tags]) const bannerUrl = useMemo(() => followPackBannerUrlFromEvent(event), [event]) useEffect(() => { setBannerFailed(false) }, [event.id]) const getPackTitle = (pack: Event): string => { const titleTag = pack.tags.find((tag) => tag[0] === 'title' || tag[0] === 'name') return titleTag?.[1] || t('Follow Pack') } const getPackDescription = (pack: Event): string => { const descTag = pack.tags.find((tag) => tag[0] === 'description' || tag[0] === 'd') return descTag?.[1] || '' } const title = getPackTitle(event) const description = getPackDescription(event) const followingSet = useMemo(() => new Set(followings), [followings]) const availablePubkeys = useMemo( () => packPubkeys.filter((p) => !muteSetHas(mutePubkeySet, p)), [packPubkeys, mutePubkeySet] ) const alreadyFollowingAll = availablePubkeys.length > 0 && availablePubkeys.every((p) => followingSet.has(p)) const toFollowCount = availablePubkeys.filter((p) => !followingSet.has(p)).length const handleFollowPack = useCallback( async (e: React.MouseEvent) => { e.stopPropagation() if (!pubkey) { toast.error(t('Please log in to follow')) return } if (!followList) return const { followMany } = followList const toFollow = packPubkeys.filter((p) => !followingSet.has(p) && !muteSetHas(mutePubkeySet, p)) if (toFollow.length === 0) { const mutedCount = packPubkeys.filter((p) => muteSetHas(mutePubkeySet, p) && !followingSet.has(p)).length if (mutedCount > 0) { toast.info(t('All available members are already followed or muted')) } else { toast.info(t('You are already following all members of this pack')) } return } setBusy(true) try { await followMany(toFollow) toast.success(t('Followed {{count}} users', { count: toFollow.length })) } catch (error) { logger.error('Failed to follow pack', { error }) toast.error(t('Failed to follow pack') + ': ' + (error as Error).message) } finally { setBusy(false) } }, [pubkey, followList, packPubkeys, followingSet, mutePubkeySet, t] ) return (
{bannerUrl && !bannerFailed ? (
{title} setBannerFailed(true)} />
) : null}
[{t('Follow Pack')}] {title}
{t('Follow pack by')}:
{description ? (
{description}
) : null}
{t('{{count}} profiles', { count: availablePubkeys.length })}
{availablePubkeys.length > 0 ? (
{availablePubkeys.slice(0, 5).map((pk) => ( ))} {availablePubkeys.length > 5 ? (
+{availablePubkeys.length - 5}
) : null}
) : null}
{!canManageIdentity ? (

{t('Please log in to follow')}

) : !followList ? null : ( )}
) }