import { NDKEvent, NDKKind, NostrEvent } from '@nostr-dev-kit/ndk' import { Dots, Spinner } from 'components/Spinner' import { ZapPopUp } from 'components/Zap' import { formatDate } from 'date-fns' import { useAppSelector, useBodyScrollDisable, useDidMount, useNDKContext, useReactions } from 'hooks' import { useComments } from 'hooks/useComments' import { nip19 } from 'nostr-tools' import React, { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' import { Link, useLoaderData } from 'react-router-dom' import { toast } from 'react-toastify' import { getProfilePageRoute } from 'routes' import { Addressable, BlogPageLoaderResult, CommentEvent, CommentEventStatus, ModPageLoaderResult, UserProfile } from 'types/index.ts' import { abbreviateNumber, hexToNpub, log, LogType } from 'utils' enum SortByEnum { Latest = 'Latest', Oldest = 'Oldest' } enum AuthorFilterEnum { All_Comments = 'All Comments', Creator_Comments = 'Creator Comments' } type FilterOptions = { sort: SortByEnum author: AuthorFilterEnum } type Props = { addressable: Addressable setCommentCount: Dispatch> } export const Comments = ({ addressable, setCommentCount }: Props) => { const { ndk } = useNDKContext() const { commentEvents, setCommentEvents } = useComments( addressable.author, addressable.aTag ) const { event } = useLoaderData() as | ModPageLoaderResult | BlogPageLoaderResult const [filterOptions, setFilterOptions] = useState({ sort: SortByEnum.Latest, author: AuthorFilterEnum.All_Comments }) const [isLoading, setIsLoading] = useState(true) useEffect(() => { // Initial loading to indicate comments fetching (stop after 5 seconds) const t = window.setTimeout(() => setIsLoading(false), 5000) return () => { window.clearTimeout(t) } }, []) useEffect(() => { setCommentCount(commentEvents.length) }, [commentEvents, setCommentCount]) const handleSubmit = async (content: string): Promise => { if (content === '') return false // NDKEvent required if (!event) return false try { const reply = event.reply() reply.content = content setCommentEvents((prev) => [ { event: new NDKEvent(ndk, reply), status: CommentEventStatus.Publishing }, ...prev ]) const relaySet = await reply.publish() if (relaySet.size) { setCommentEvents((prev) => prev.map((ce) => { if (ce.event.id === reply.id) { return { event: ce.event, status: CommentEventStatus.Published } } return ce }) ) // when an event is successfully published remove the status from it after 15 seconds setTimeout(() => { setCommentEvents((prev) => prev.map((ce) => { if (ce.event.id === reply.id) { delete ce.status } return ce }) ) }, 15000) } else { log(true, LogType.Error, 'Publishing comment failed.') setCommentEvents((prev) => prev.map((ce) => { if (ce.event.id === reply.id) { return { event: ce.event, status: CommentEventStatus.Failed } } return ce }) ) } return false } catch (error) { toast.error('An error occurred in publishing comment.') log( true, LogType.Error, 'An error occurred in publishing comment.', error ) return false } } const handleDiscoveredClick = () => { setVisible(commentEvents) } const [visible, setVisible] = useState([]) useEffect(() => { if (isLoading) { setVisible(commentEvents) } }, [commentEvents, isLoading]) const comments = useMemo(() => { let filteredComments = visible if (filterOptions.author === AuthorFilterEnum.Creator_Comments) { filteredComments = filteredComments.filter( (comment) => comment.event.pubkey === addressable.author ) } if (filterOptions.sort === SortByEnum.Latest) { filteredComments.sort((a, b) => a.event.created_at && b.event.created_at ? b.event.created_at - a.event.created_at : 0 ) } else if (filterOptions.sort === SortByEnum.Oldest) { filteredComments.sort((a, b) => a.event.created_at && b.event.created_at ? a.event.created_at - b.event.created_at : 0 ) } return filteredComments }, [visible, filterOptions.author, filterOptions.sort, addressable.author]) const discoveredCount = commentEvents.length - visible.length return (

Comments

{/* Hide comment form if aTag is missing */} {!!addressable.aTag && }
{isLoading ? ( ) : ( )}
{comments.map((comment) => ( ))}
) } type CommentFormProps = { handleSubmit: (content: string) => Promise } const CommentForm = ({ handleSubmit }: CommentFormProps) => { const [isSubmitting, setIsSubmitting] = useState(false) const [commentText, setCommentText] = useState('') const handleComment = async () => { setIsSubmitting(true) const submitted = await handleSubmit(commentText) if (submitted) setCommentText('') setIsSubmitting(false) } return (