--- import Image from 'astro/components/Image.astro' import { Icon } from 'astro-icon/components' import { Markdown } from 'astro-remote' import { Schema } from 'astro-seo-schema' import { actions } from 'astro:actions' import { karmaUnlocksById } from '../constants/karmaUnlocks' import { getServiceUserRoleInfo } from '../constants/serviceUserRoles' import { cn } from '../lib/cn' import { makeCommentUrl, MAX_COMMENT_DEPTH, type CommentWithRepliesPopulated, } from '../lib/commentsWithReplies' import { computeKarmaUnlocks } from '../lib/karmaUnlocks' import { formatDateShort } from '../lib/timeAgo' import BadgeSmall from './BadgeSmall.astro' import CommentModeration from './CommentModeration.astro' import CommentReply from './CommentReply.astro' import TimeFormatted from './TimeFormatted.astro' import Tooltip from './Tooltip.astro' import type { HTMLAttributes } from 'astro/types' type Props = HTMLAttributes<'div'> & { comment: CommentWithRepliesPopulated depth?: number showPending?: boolean highlightedCommentId: number | null serviceSlug: string itemReviewedId: string } const { comment, depth = 0, showPending = false, highlightedCommentId = null, serviceSlug, itemReviewedId, class: className, ...htmlProps } = Astro.props const user = Astro.locals.user const userCommentsDisabled = user ? user.karmaUnlocks.commentsDisabled : false const authorUnlocks = computeKarmaUnlocks(comment.author.totalKarma) function checkIsHighlightParent(c: CommentWithRepliesPopulated, highlight: number | null): boolean { if (!highlight) return false if (c.id === highlight) return true if (!c.replies?.length) return false return c.replies.some((r) => checkIsHighlightParent(r, highlight)) } const isHighlightParent = checkIsHighlightParent(comment, highlightedCommentId) const isHighlighted = comment.id === highlightedCommentId // Get user's current vote if any const userVote = user ? comment.votes.find((v) => v.userId === user.id) : null const isAuthor = user?.id === comment.author.id const isAdminOrVerifier = !!user && (user.admin || user.verifier) const isAuthorOrPrivileged = isAuthor || isAdminOrVerifier // Check if user is new (less than 1 week old) const isNewUser = new Date().getTime() - new Date(comment.author.createdAt).getTime() < 7 * 24 * 60 * 60 * 1000 const isRatingActive = comment.rating !== null && !comment.parentId && comment.ratingActive && !comment.suspicious && (comment.status === 'APPROVED' || comment.status === 'VERIFIED') // Skip rendering if comment is not approved/verified and user is not the author or admin/verifier const shouldShow = comment.status === 'APPROVED' || comment.status === 'VERIFIED' || ((showPending || isHighlightParent || isHighlighted) && comment.status === 'PENDING') || ((showPending || isHighlightParent || isHighlighted) && comment.status === 'HUMAN_PENDING') || ((isHighlightParent || isHighlighted) && comment.status === 'REJECTED') || isAuthorOrPrivileged if (!shouldShow) return null const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin: Astro.url.origin }) ---
0 && 'ml-3 border-b-0 border-l border-zinc-800 pt-2 pl-2 sm:ml-4', comment.author.serviceAffiliations.some((affiliation) => affiliation.service.slug === serviceSlug) && 'bg-[#182a1f]', (comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING') && 'bg-[#292815]', comment.status === 'REJECTED' && 'bg-[#2f1f1f]', isHighlighted && 'bg-[#192633]', comment.suspicious && 'opacity-25 transition-opacity not-has-[[data-collapse-toggle]:checked]:opacity-100! focus-within:opacity-100 hover:opacity-100 focus:opacity-100', className, ])} > { isRatingActive && comment.rating !== null && ( ) }
{ comment.author.picture && ( {`Profile ) } {comment.author.displayName ?? comment.author.name} { (comment.author.verified || comment.author.admin || comment.author.verifier) && ( ) } {/* User badges - more compact but still with text */}
{ comment.author.admin && ( ) } { comment.author.verifier && !comment.author.admin && ( ) } { isNewUser && !comment.author.admin && !comment.author.verifier && ( ) } { authorUnlocks.highKarmaBadge && !comment.author.admin && !comment.author.verifier && ( ) } { authorUnlocks.negativeKarmaBadge && !authorUnlocks.untrustedBadge && ( ) } { (authorUnlocks.untrustedBadge || comment.author.spammer) && ( ) } { comment.author.serviceAffiliations.map((affiliation) => { const roleInfo = getServiceUserRoleInfo(affiliation.role) return ( ) }) }
{comment.upvotes} {comment.suspicious && } { comment.requiresAdminReview && isAuthorOrPrivileged && ( ) } { comment.rating !== null && !comment.parentId && ( {comment.rating.toLocaleString()}/5 ) } { comment.status === 'VERIFIED' && ( ) } { (comment.status === 'PENDING' || comment.status === 'HUMAN_PENDING') && (showPending || isHighlightParent || isAuthorOrPrivileged) && ( ) } { comment.status === 'REJECTED' && isAuthorOrPrivileged && ( ) } {/* Service usage verification indicators */} { comment.orderId && comment.orderIdStatus === 'APPROVED' && ( ) } { comment.orderId && comment.orderIdStatus === 'REJECTED' && ( ) } { comment.kycRequested && ( ) } { comment.fundsBlocked && ( ) }
{ isAuthor && comment.status === 'REJECTED' && (
This comment has been rejected and is only visible to you
) }
{ !!comment.content && (
) }
{ comment.communityNote && (
Added context: {comment.communityNote}
) } { user && (user.admin || user.verifier) && comment.internalNote && (
Internal note: {comment.internalNote}
) } { user && (user.admin || user.verifier) && comment.privateContext && (
Private context: {comment.privateContext}
) }
= 20 ? 'Upvote' : 'Need 20+ karma to vote'} position="right" aria-label="Upvote" >
= 20 ? 'Downvote' : 'Need 20+ karma to vote'} position="right" disabled={!user?.totalKarma || user.totalKarma < 20} class={cn([ 'rounded-sm p-1 hover:bg-zinc-800', userVote?.downvote === true ? 'text-red-500' : 'text-zinc-500', (!user?.totalKarma || user.totalKarma < 20) && 'cursor-not-allowed text-zinc-700', ])} aria-label="Downvote" >
{ user && userCommentsDisabled ? ( You cannot reply due to low karma. ) : ( ) } { user && (
) }
{ user && userCommentsDisabled ? null : ( <>