156 lines
5.9 KiB
JavaScript
156 lines
5.9 KiB
JavaScript
import React, { useRef, useState } from 'react'
|
|
import LevelBadge from '../../xp/LevelBadge'
|
|
|
|
const DEFAULT_AVATAR = 'https://files.skinbase.org/default/avatar_default.webp'
|
|
|
|
function CommentItem({ comment }) {
|
|
return (
|
|
<div className="flex gap-3 py-4 border-b border-white/5 last:border-0">
|
|
<a href={comment.author_profile_url} className="shrink-0 mt-0.5">
|
|
<img
|
|
src={comment.author_avatar || DEFAULT_AVATAR}
|
|
alt={comment.author_name}
|
|
className="w-9 h-9 rounded-xl object-cover ring-1 ring-white/10"
|
|
onError={(e) => { e.target.src = DEFAULT_AVATAR }}
|
|
loading="lazy"
|
|
/>
|
|
</a>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<a
|
|
href={comment.author_profile_url}
|
|
className="text-sm font-semibold text-slate-200 hover:text-white transition-colors"
|
|
>
|
|
{comment.author_name}
|
|
</a>
|
|
<LevelBadge level={comment.author_level} rank={comment.author_rank} compact />
|
|
<span className="text-slate-600 text-xs ml-auto whitespace-nowrap">
|
|
{(() => {
|
|
try {
|
|
const d = new Date(comment.created_at)
|
|
const diff = Date.now() - d.getTime()
|
|
const mins = Math.floor(diff / 60000)
|
|
if (mins < 1) return 'just now'
|
|
if (mins < 60) return `${mins}m ago`
|
|
const hrs = Math.floor(mins / 60)
|
|
if (hrs < 24) return `${hrs}h ago`
|
|
const days = Math.floor(hrs / 24)
|
|
if (days < 30) return `${days}d ago`
|
|
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
|
|
} catch { return '' }
|
|
})()}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-slate-400 leading-relaxed break-words whitespace-pre-line">
|
|
{comment.body}
|
|
</p>
|
|
{comment.author_signature && (
|
|
<p className="text-xs text-slate-600 mt-2 italic border-t border-white/5 pt-1 truncate">
|
|
{comment.author_signature}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* TabActivity
|
|
* Profile comments list + comment form for authenticated visitors.
|
|
* Also acts as "Activity" tab.
|
|
*/
|
|
export default function TabActivity({ profileComments, user, isOwner, isLoggedIn }) {
|
|
const uname = user.username || user.name
|
|
const formRef = useRef(null)
|
|
const [submitted, setSubmitted] = useState(false)
|
|
|
|
return (
|
|
<div
|
|
id="tabpanel-activity"
|
|
role="tabpanel"
|
|
aria-labelledby="tab-activity"
|
|
className="pt-6 max-w-2xl"
|
|
>
|
|
<h2 className="text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2">
|
|
<i className="fa-solid fa-comments text-orange-400 fa-fw" />
|
|
Comments
|
|
{profileComments?.length > 0 && (
|
|
<span className="ml-1 px-1.5 py-0.5 rounded bg-white/5 text-slate-400 font-normal text-[11px]">
|
|
{profileComments.length}
|
|
</span>
|
|
)}
|
|
</h2>
|
|
|
|
{/* Comments list */}
|
|
<div className="bg-white/4 ring-1 ring-white/10 rounded-2xl p-5 shadow-xl shadow-black/20 mb-5">
|
|
{!profileComments?.length ? (
|
|
<p className="text-slate-500 text-sm text-center py-8">
|
|
No comments yet. Be the first to leave one!
|
|
</p>
|
|
) : (
|
|
<div>
|
|
{profileComments.map((comment) => (
|
|
<CommentItem key={comment.id} comment={comment} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Comment form */}
|
|
{!isOwner && (
|
|
<div className="bg-white/4 ring-1 ring-white/10 rounded-2xl p-5 shadow-xl shadow-black/20">
|
|
<h3 className="text-xs font-semibold uppercase tracking-widest text-slate-500 mb-4 flex items-center gap-2">
|
|
<i className="fa-solid fa-pen text-sky-400 fa-fw" />
|
|
Write a Comment
|
|
</h3>
|
|
|
|
{isLoggedIn ? (
|
|
submitted ? (
|
|
<div className="flex items-center gap-2 text-green-400 text-sm p-3 rounded-xl bg-green-500/10 ring-1 ring-green-500/20">
|
|
<i className="fa-solid fa-check fa-fw" />
|
|
Your comment has been posted!
|
|
</div>
|
|
) : (
|
|
<form
|
|
ref={formRef}
|
|
method="POST"
|
|
action={`/@${uname.toLowerCase()}/comment`}
|
|
onSubmit={() => setSubmitted(false)}
|
|
>
|
|
<input type="hidden" name="_token" value={
|
|
(() => document.querySelector('meta[name="csrf-token"]')?.content ?? '')()
|
|
} />
|
|
<textarea
|
|
name="body"
|
|
rows={4}
|
|
required
|
|
minLength={2}
|
|
maxLength={2000}
|
|
placeholder={`Write a comment for ${uname}…`}
|
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2.5 text-sm text-slate-200 placeholder:text-slate-600 resize-none focus:outline-none focus:ring-2 focus:ring-sky-400/40 focus:border-sky-400/30 transition-all"
|
|
/>
|
|
<div className="mt-3 flex justify-end">
|
|
<button
|
|
type="submit"
|
|
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-sky-600 hover:bg-sky-500 text-white text-sm font-semibold transition-all shadow-lg shadow-sky-900/30"
|
|
>
|
|
<i className="fa-solid fa-paper-plane fa-fw" />
|
|
Post Comment
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)
|
|
) : (
|
|
<p className="text-sm text-slate-400 text-center py-4">
|
|
<a href="/login" className="text-sky-400 hover:text-sky-300 hover:underline transition-colors">
|
|
Log in
|
|
</a>
|
|
{' '}to leave a comment.
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|