140 lines
4.6 KiB
JavaScript
140 lines
4.6 KiB
JavaScript
import React, { useState } from 'react'
|
|
import axios from 'axios'
|
|
|
|
/**
|
|
* PostActions: Like toggle, Comment toggle, Share menu, Report
|
|
*/
|
|
export default function PostActions({
|
|
post,
|
|
isLoggedIn,
|
|
onCommentToggle,
|
|
onReactionChange,
|
|
}) {
|
|
const [liked, setLiked] = useState(post.viewer_liked ?? false)
|
|
const [likeCount, setLikeCount] = useState(post.reactions_count ?? 0)
|
|
const [busy, setBusy] = useState(false)
|
|
const [menuOpen, setMenuOpen] = useState(false)
|
|
const [shareMsg, setShareMsg] = useState(null)
|
|
|
|
const handleLike = async () => {
|
|
if (!isLoggedIn) {
|
|
window.location.href = '/login'
|
|
return
|
|
}
|
|
if (busy) return
|
|
setBusy(true)
|
|
try {
|
|
if (liked) {
|
|
await axios.delete(`/api/posts/${post.id}/reactions/like`)
|
|
setLiked(false)
|
|
setLikeCount((c) => Math.max(0, c - 1))
|
|
onReactionChange?.({ liked: false, count: Math.max(0, likeCount - 1) })
|
|
} else {
|
|
await axios.post(`/api/posts/${post.id}/reactions`, { reaction: 'like' })
|
|
setLiked(true)
|
|
setLikeCount((c) => c + 1)
|
|
onReactionChange?.({ liked: true, count: likeCount + 1 })
|
|
}
|
|
} catch {
|
|
// ignore
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
const handleCopyLink = () => {
|
|
const url = `${window.location.origin}/@${post.author.username}/posts?post=${post.id}`
|
|
navigator.clipboard?.writeText(url)
|
|
setShareMsg('Link copied!')
|
|
setTimeout(() => setShareMsg(null), 2000)
|
|
setMenuOpen(false)
|
|
}
|
|
|
|
const handleReport = async () => {
|
|
setMenuOpen(false)
|
|
const reason = window.prompt('Why are you reporting this post? (required)')
|
|
if (!reason?.trim()) return
|
|
try {
|
|
await axios.post(`/api/posts/${post.id}/report`, { reason: reason.trim() })
|
|
alert('Report submitted. Thank you!')
|
|
} catch (err) {
|
|
if (err.response?.data?.message) {
|
|
alert(err.response.data.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-1 text-slate-400 relative">
|
|
{/* Like */}
|
|
<button
|
|
onClick={handleLike}
|
|
disabled={busy}
|
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors ${
|
|
liked
|
|
? 'text-sky-400 bg-sky-500/10 hover:bg-sky-500/20'
|
|
: 'hover:bg-white/5 hover:text-slate-200'
|
|
}`}
|
|
title={liked ? 'Unlike' : 'Like'}
|
|
aria-label={liked ? 'Unlike this post' : 'Like this post'}
|
|
>
|
|
<i className={`fa-${liked ? 'solid' : 'regular'} fa-heart fa-fw text-xs`} />
|
|
<span className="tabular-nums">{likeCount > 0 && likeCount}</span>
|
|
</button>
|
|
|
|
{/* Comment toggle */}
|
|
<button
|
|
onClick={onCommentToggle}
|
|
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm hover:bg-white/5 hover:text-slate-200 transition-colors"
|
|
title="Comment"
|
|
aria-label="Show comments"
|
|
>
|
|
<i className="fa-regular fa-comment fa-fw text-xs" />
|
|
<span className="tabular-nums">{post.comments_count > 0 && post.comments_count}</span>
|
|
</button>
|
|
|
|
{/* Share / More menu */}
|
|
<div className="relative ml-auto">
|
|
<button
|
|
onClick={() => setMenuOpen((v) => !v)}
|
|
className="flex items-center gap-1 px-3 py-1.5 rounded-lg text-sm hover:bg-white/5 hover:text-slate-200 transition-colors"
|
|
aria-label="More options"
|
|
>
|
|
<i className="fa-solid fa-ellipsis-h fa-fw text-xs" />
|
|
</button>
|
|
|
|
{menuOpen && (
|
|
<div
|
|
className="absolute right-0 bottom-full mb-1 w-44 rounded-xl border border-white/10 bg-[#10192e] shadow-2xl z-50 overflow-hidden"
|
|
onBlur={() => setMenuOpen(false)}
|
|
>
|
|
<button
|
|
onClick={handleCopyLink}
|
|
className="w-full text-left px-4 py-2.5 text-sm text-slate-300 hover:bg-white/5 flex items-center gap-2"
|
|
>
|
|
<i className="fa-solid fa-link fa-fw opacity-60" />
|
|
Copy link
|
|
</button>
|
|
{isLoggedIn && (
|
|
<button
|
|
onClick={handleReport}
|
|
className="w-full text-left px-4 py-2.5 text-sm text-rose-400 hover:bg-white/5 flex items-center gap-2"
|
|
>
|
|
<i className="fa-solid fa-flag fa-fw opacity-60" />
|
|
Report post
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Share feedback toast */}
|
|
{shareMsg && (
|
|
<span className="absolute -top-8 right-0 text-xs bg-slate-800 text-white px-2 py-1 rounded shadow">
|
|
{shareMsg}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|