update
This commit is contained in:
161
resources/js/components/social/StorySocialPanel.jsx
Normal file
161
resources/js/components/social/StorySocialPanel.jsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import React, { useState } from 'react'
|
||||
import FollowButton from './FollowButton'
|
||||
import LikeButton from './LikeButton'
|
||||
import BookmarkButton from './BookmarkButton'
|
||||
import CommentForm from './CommentForm'
|
||||
import CommentList from './CommentList'
|
||||
|
||||
export default function StorySocialPanel({ story, creator, initialState, initialComments, isAuthenticated = false }) {
|
||||
const [state, setState] = useState({
|
||||
liked: Boolean(initialState?.liked),
|
||||
bookmarked: Boolean(initialState?.bookmarked),
|
||||
likesCount: Number(initialState?.likes_count || 0),
|
||||
commentsCount: Number(initialState?.comments_count || 0),
|
||||
bookmarksCount: Number(initialState?.bookmarks_count || 0),
|
||||
})
|
||||
const [comments, setComments] = useState(Array.isArray(initialComments) ? initialComments : [])
|
||||
const csrfToken = typeof document !== 'undefined'
|
||||
? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
||||
: null
|
||||
|
||||
const postJson = async (url, method = 'POST', body = null) => {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': csrfToken || '',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: body ? JSON.stringify(body) : null,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => ({}))
|
||||
throw new Error(payload?.message || 'Request failed.')
|
||||
}
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
const refreshCounts = (nextComments) => {
|
||||
const countReplies = (items) => items.reduce((sum, item) => sum + 1 + countReplies(item.replies || []), 0)
|
||||
return countReplies(nextComments)
|
||||
}
|
||||
|
||||
const insertReply = (items, parentId, newComment) => items.map((item) => {
|
||||
if (item.id === parentId) {
|
||||
return { ...item, replies: [...(item.replies || []), newComment] }
|
||||
}
|
||||
|
||||
if (Array.isArray(item.replies) && item.replies.length > 0) {
|
||||
return { ...item, replies: insertReply(item.replies, parentId, newComment) }
|
||||
}
|
||||
|
||||
return item
|
||||
})
|
||||
|
||||
const deleteCommentRecursive = (items, commentId) => items
|
||||
.filter((item) => item.id !== commentId)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
replies: Array.isArray(item.replies) ? deleteCommentRecursive(item.replies, commentId) : [],
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<LikeButton
|
||||
active={state.liked}
|
||||
count={state.likesCount}
|
||||
onToggle={async () => {
|
||||
if (!isAuthenticated) {
|
||||
window.location.href = '/login'
|
||||
return
|
||||
}
|
||||
|
||||
const payload = await postJson(`/api/stories/${story.id}/like`, 'POST', { state: !state.liked })
|
||||
setState((current) => ({
|
||||
...current,
|
||||
liked: Boolean(payload?.liked),
|
||||
likesCount: Number(payload?.likes_count || 0),
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
|
||||
<BookmarkButton
|
||||
active={state.bookmarked}
|
||||
count={state.bookmarksCount}
|
||||
onToggle={async () => {
|
||||
if (!isAuthenticated) {
|
||||
window.location.href = '/login'
|
||||
return
|
||||
}
|
||||
|
||||
const payload = await postJson(`/api/stories/${story.id}/bookmark`, 'POST', { state: !state.bookmarked })
|
||||
setState((current) => ({
|
||||
...current,
|
||||
bookmarked: Boolean(payload?.bookmarked),
|
||||
bookmarksCount: Number(payload?.bookmarks_count || 0),
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
|
||||
{creator?.username ? (
|
||||
<FollowButton
|
||||
username={creator.username}
|
||||
initialFollowing={Boolean(initialState?.is_following_creator)}
|
||||
initialCount={Number(creator.followers_count || 0)}
|
||||
className="min-w-[11rem]"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<section className="rounded-2xl border border-white/[0.08] bg-white/[0.04] p-5">
|
||||
<div className="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-white">Discussion</h2>
|
||||
<p className="text-sm text-white/40">{state.commentsCount.toLocaleString()} comments on this story</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isAuthenticated ? (
|
||||
<div className="mb-5">
|
||||
<CommentForm
|
||||
placeholder="Add to the story discussion…"
|
||||
submitLabel="Post Comment"
|
||||
onSubmit={async (content) => {
|
||||
const payload = await postJson(`/api/stories/${story.id}/comments`, 'POST', { content })
|
||||
const nextComments = [payload.data, ...comments]
|
||||
setComments(nextComments)
|
||||
setState((current) => ({ ...current, commentsCount: refreshCounts(nextComments) }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<p className="mb-5 text-sm text-white/45">
|
||||
<a href="/login" className="text-sky-300 hover:text-sky-200">Sign in</a> to join the discussion.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<CommentList
|
||||
comments={comments}
|
||||
canReply={isAuthenticated}
|
||||
emptyMessage="No comments yet. Start the discussion."
|
||||
onReply={async (parentId, content) => {
|
||||
const payload = await postJson(`/api/stories/${story.id}/comments`, 'POST', { content, parent_id: parentId })
|
||||
const nextComments = insertReply(comments, parentId, payload.data)
|
||||
setComments(nextComments)
|
||||
setState((current) => ({ ...current, commentsCount: refreshCounts(nextComments) }))
|
||||
}}
|
||||
onDelete={async (commentId) => {
|
||||
await postJson(`/api/stories/${story.id}/comments/${commentId}`, 'DELETE')
|
||||
const nextComments = deleteCommentRecursive(comments, commentId)
|
||||
setComments(nextComments)
|
||||
setState((current) => ({ ...current, commentsCount: refreshCounts(nextComments) }))
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user