import { dateLocaleCode } from '@/lib/dateFnsI18n'; import { Link } from 'react-router-dom'; import type { NostrEvent } from '@nostrify/nostrify'; import { Star, ThumbsUp, ArrowUpRight } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Skeleton } from '@/components/ui/skeleton'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { TradeReputationCard } from '@/components/TradeReputationCard'; import { TradeRecommendationsCard } from '@/components/TradeRecommendationsCard'; import { DeckRating } from '@/components/DeckRating'; import { useTradeReputation, TRADE_METHODS, type TradeRole } from '@/hooks/useTradeAttestations'; import { useAuthor } from '@/hooks/useAuthor'; import { genUserName } from '@/lib/genUserName'; import { cn } from '@/lib/utils'; import { useTranslation } from '@/contexts/language'; /** One trade record / review row. */ function ReviewRow({ ev, selfPubkey }: { ev: NostrEvent; selfPubkey: string }) { const { t } = useTranslation(); const role = ev.tags.find(([t]) => t === 'd')?.[1]?.split(':')[2] as TradeRole | undefined; const counterparty = ev.tags.find(([t]) => t === 'p')?.[1] ?? ''; const method = ev.tags.find(([t]) => t === 'method')?.[1]; const amount = ev.tags.find(([t]) => t === 'amount')?.[1]; const rating = Number(ev.tags.find(([t]) => t === 'rating')?.[1] ?? 0); const coord = ev.tags.find(([t]) => t === 'a')?.[1] ?? ''; const [, listingPubkey, listingId] = coord.split(':'); const author = useAuthor(counterparty); const name = author.data?.metadata?.name ?? genUserName(counterparty); const picture = author.data?.metadata?.picture; const given = ev.pubkey === selfPubkey; const date = new Date(ev.created_at * 1000).toLocaleDateString(dateLocaleCode()); return (
{name.slice(0, 2).toUpperCase()}
{name} {role === 'sold' ? t('marketUi.trades.roleSold') : t('marketUi.trades.roleBought')} {!given && ★ {t('marketUi.reputationPanel.reviewOfYou')}} {given && {t('marketUi.reputationPanel.yourRecord')}} {rating > 0 && }
{ev.content &&

{ev.content}

}
{method && {TRADE_METHODS[method as keyof typeof TRADE_METHODS] ?? method}} {amount && {Number(amount).toLocaleString()} {t('common.sats')}} {date} {listingPubkey && listingId && ( {t('marketUi.reputationPanel.listing')} )}
); } /** Loading placeholder shaped like a review row. */ function ReviewSkeleton() { return (
); } /** * Marketplace → Reputation tab: this trader's reputation, peer recommendations, * and their trade records / reviews (from kind-31871 attestations). */ export function TradeReputationPanel({ pubkey }: { pubkey: string }) { const { t } = useTranslation(); const { data, isLoading } = useTradeReputation(pubkey); const attestations = [...(data?.attestations ?? [])].sort((a, b) => b.created_at - a.created_at); const reviews = attestations.filter((a) => a.pubkey !== pubkey); const ratings = attestations .map((a) => Number(a.tags.find(([t]) => t === 'rating')?.[1] ?? 0)) .filter((n) => n >= 1 && n <= 10); const avg = ratings.length ? ratings.reduce((s, n) => s + n, 0) / ratings.length : 0; return (
{avg > 0 && (
{avg.toFixed(1)}/10 {ratings.length === 1 ? t('marketUi.reputationPanel.averageOne', { count: ratings.length }) : t('marketUi.reputationPanel.averageMany', { count: ratings.length })}
)}

{t('marketUi.reputationPanel.reviewsTitle')} {reviews.length > 0 && ( {t('marketUi.reputationPanel.fromOthers', { count: reviews.length })} )}

{isLoading ? (
) : attestations.length === 0 ? (

{t('marketUi.reputationPanel.emptyBefore')} {t('marketUi.trades.title')} {t('marketUi.reputationPanel.emptyAfter')}

) : (
{attestations.map((ev) => ( ))}
)}
); }