import { useSecondaryPage } from '@/PageManager' import { Button } from '@/components/ui/button' import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel' import { ExtendedKind } from '@/constants' import { compareEvents } from '@/lib/event' import { getStarsFromRelayReviewEvent } from '@/lib/event-metadata' import { toRelayReviews } from '@/lib/link' import { cn, isTouchDevice } from '@/lib/utils' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import client from '@/services/client.service' import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures' import { Filter, NostrEvent } from 'nostr-tools' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Stars from '../Stars' import RelayReviewCard from './RelayReviewCard' import ReviewEditor from './ReviewEditor' export default function RelayReviewsPreview({ relayUrl }: { relayUrl: string }) { const { t } = useTranslation() const { push } = useSecondaryPage() const { pubkey, checkLogin } = useNostr() const { hideUntrustedNotes, isUserTrusted, isSpammer } = useUserTrust() const { mutePubkeySet } = useMuteList() const [showEditor, setShowEditor] = useState(false) const [myReview, setMyReview] = useState(null) const [reviews, setReviews] = useState([]) const [initialized, setInitialized] = useState(false) const { stars, count } = useMemo(() => { let totalStars = 0 let totalCount = 0 ;[myReview, ...reviews].forEach((evt) => { if (!evt) return const stars = getStarsFromRelayReviewEvent(evt) if (stars) { totalStars += stars totalCount += 1 } }) return { stars: totalCount > 0 ? +(totalStars / totalCount).toFixed(1) : 0, count: totalCount } }, [myReview, reviews]) useEffect(() => { const init = async () => { const filters: Filter[] = [ { kinds: [ExtendedKind.RELAY_REVIEW], '#d': [relayUrl], limit: 100 } ] if (pubkey) { filters.push({ kinds: [ExtendedKind.RELAY_REVIEW], authors: [pubkey], '#d': [relayUrl] }) } const events = await client.fetchEvents([relayUrl, ...client.currentRelays], filters, { cache: true }) const pubkeySet = new Set() const reviews: NostrEvent[] = [] let myReview: NostrEvent | null = null events.sort((a, b) => compareEvents(b, a)) for (const evt of events) { if (mutePubkeySet.has(evt.pubkey) || pubkeySet.has(evt.pubkey)) { continue } const stars = getStarsFromRelayReviewEvent(evt) if (!stars) { continue } pubkeySet.add(evt.pubkey) if (evt.pubkey === pubkey) { myReview = evt } else { reviews.push(evt) } } const filteredReviews = ( await Promise.all( reviews.map(async (evt) => { if (await isSpammer(evt.pubkey)) { return null } return evt }) ) ).filter(Boolean) as NostrEvent[] setMyReview(myReview) setReviews(filteredReviews) setInitialized(true) } init() }, [relayUrl, pubkey, mutePubkeySet, hideUntrustedNotes, isUserTrusted]) const handleReviewed = (evt: NostrEvent) => { setMyReview(evt) setShowEditor(false) } return (
{stars}
0 && 'underline cursor-pointer hover:text-foreground' )} onClick={() => { if (count > 0) { push(toRelayReviews(relayUrl)) } }} > {t('{{count}} reviews', { count })}
{!showEditor && !myReview && ( )}
{showEditor && } {myReview || reviews.length > 0 ? ( ) : !showEditor ? (
{initialized ? t('No reviews yet. Be the first to write one!') : t('Loading...')}
) : null}
) } function ReviewCarousel({ relayUrl, myReview, reviews }: { relayUrl: string myReview: NostrEvent | null reviews: NostrEvent[] }) { const { t } = useTranslation() const { push } = useSecondaryPage() const showPreviousAndNext = useMemo(() => !isTouchDevice(), []) return ( {myReview && ( )} {reviews.slice(0, 10).map((evt) => ( ))} {reviews.length > 10 && (
push(toRelayReviews(relayUrl))} >
{t('View more reviews')}
)}
{showPreviousAndNext && } {showPreviousAndNext && }
) } function Item({ children }: { children: React.ReactNode }) { const { enableSingleColumnLayout } = useUserPreferences() return ( {children} ) }