93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
import React, { useState } from 'react'
|
|
import LevelBadge from '../xp/LevelBadge'
|
|
import CommentForm from './CommentForm'
|
|
|
|
function CommentItem({ comment, canReply, onReply, onDelete, onReport }) {
|
|
const [replying, setReplying] = useState(false)
|
|
|
|
return (
|
|
<article id={`story-comment-${comment.id}`} className="rounded-2xl border border-white/[0.08] bg-white/[0.04] p-4">
|
|
<div className="flex gap-3">
|
|
<img
|
|
src={comment.user?.avatar_url || 'https://files.skinbase.org/default/avatar_default.webp'}
|
|
alt={comment.user?.display || 'User'}
|
|
className="h-10 w-10 rounded-full object-cover ring-1 ring-white/10"
|
|
/>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{comment.user?.profile_url ? (
|
|
<a href={comment.user.profile_url} className="text-sm font-semibold text-white hover:text-sky-300">
|
|
{comment.user.display}
|
|
</a>
|
|
) : (
|
|
<span className="text-sm font-semibold text-white">{comment.user?.display || 'User'}</span>
|
|
)}
|
|
<LevelBadge level={comment.user?.level} rank={comment.user?.rank} compact />
|
|
<span className="text-xs uppercase tracking-[0.16em] text-white/30">{comment.time_ago}</span>
|
|
</div>
|
|
|
|
<div
|
|
className="prose prose-invert prose-sm mt-2 max-w-none text-white/80 prose-p:my-1.5 prose-a:text-sky-300"
|
|
dangerouslySetInnerHTML={{ __html: comment.rendered_content || '' }}
|
|
/>
|
|
|
|
<div className="mt-3 flex items-center gap-3 text-xs text-white/45">
|
|
{canReply ? (
|
|
<button type="button" onClick={() => setReplying((value) => !value)} className="transition hover:text-white">
|
|
Reply
|
|
</button>
|
|
) : null}
|
|
{comment.can_delete ? (
|
|
<button type="button" onClick={() => onDelete?.(comment.id)} className="transition hover:text-rose-300">
|
|
Delete
|
|
</button>
|
|
) : null}
|
|
{comment.can_report ? (
|
|
<button type="button" onClick={() => onReport?.(comment)} className="transition hover:text-amber-300">
|
|
Report
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
|
|
{replying ? (
|
|
<div className="mt-3">
|
|
<CommentForm
|
|
compact
|
|
placeholder={`Reply to ${comment.user?.display || 'user'}…`}
|
|
submitLabel="Reply"
|
|
onCancel={() => setReplying(false)}
|
|
onSubmit={async (content) => {
|
|
await onReply?.(comment.id, content)
|
|
setReplying(false)
|
|
}}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
{Array.isArray(comment.replies) && comment.replies.length > 0 ? (
|
|
<div className="mt-4 space-y-3 border-l border-white/[0.08] pl-4">
|
|
{comment.replies.map((reply) => (
|
|
<CommentItem key={reply.id} comment={reply} canReply={canReply} onReply={onReply} onDelete={onDelete} onReport={onReport} />
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|
|
|
|
export default function CommentList({ comments = [], canReply = false, onReply, onDelete, onReport, emptyMessage = 'No comments yet.' }) {
|
|
if (!comments.length) {
|
|
return <p className="text-sm text-white/45">{emptyMessage}</p>
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{comments.map((comment) => (
|
|
<CommentItem key={comment.id} comment={comment} canReply={canReply} onReply={onReply} onDelete={onDelete} onReport={onReport} />
|
|
))}
|
|
</div>
|
|
)
|
|
}
|