159 lines
5.0 KiB
JavaScript
159 lines
5.0 KiB
JavaScript
import React, { useState, useCallback } from 'react'
|
|
import axios from 'axios'
|
|
import PostCard from '../../Feed/PostCard'
|
|
import PostComposer from '../../Feed/PostComposer'
|
|
import PostCardSkeleton from '../../Feed/PostCardSkeleton'
|
|
import FeedSidebar from '../../Feed/FeedSidebar'
|
|
|
|
function EmptyPostsState({ isOwner, username }) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center py-20 text-center">
|
|
<div className="w-16 h-16 rounded-2xl bg-white/5 flex items-center justify-center mb-4 text-slate-600">
|
|
<i className="fa-regular fa-newspaper text-2xl" />
|
|
</div>
|
|
<p className="text-slate-400 font-medium mb-1">No posts yet</p>
|
|
{isOwner ? (
|
|
<p className="text-slate-600 text-sm max-w-xs">
|
|
Share updates or showcase your artworks.
|
|
</p>
|
|
) : (
|
|
<p className="text-slate-600 text-sm">@{username} has not posted anything yet.</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* TabPosts
|
|
* Profile Posts tab — shows the user's post feed with optional composer (for owner).
|
|
*
|
|
* Props:
|
|
* username string
|
|
* isOwner boolean
|
|
* authUser object|null { id, username, name, avatar }
|
|
* user object full user from ProfileController
|
|
* profile object
|
|
* stats object|null
|
|
* followerCount number
|
|
* recentFollowers array
|
|
* socialLinks object
|
|
* countryName string|null
|
|
* onTabChange function(tab)
|
|
*/
|
|
export default function TabPosts({
|
|
username,
|
|
isOwner,
|
|
authUser,
|
|
user,
|
|
profile,
|
|
stats,
|
|
followerCount,
|
|
recentFollowers,
|
|
socialLinks,
|
|
countryName,
|
|
onTabChange,
|
|
}) {
|
|
const [posts, setPosts] = useState([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [page, setPage] = useState(1)
|
|
const [hasMore, setHasMore] = useState(false)
|
|
const [loaded, setLoaded] = useState(false)
|
|
|
|
// Fetch on mount
|
|
React.useEffect(() => {
|
|
fetchFeed(1)
|
|
}, [username])
|
|
|
|
const fetchFeed = async (p = 1) => {
|
|
setLoading(true)
|
|
try {
|
|
const { data } = await axios.get(`/api/posts/profile/${username}`, { params: { page: p } })
|
|
setPosts((prev) => p === 1 ? data.data : [...prev, ...data.data])
|
|
setHasMore(data.meta.current_page < data.meta.last_page)
|
|
setPage(p)
|
|
} catch {
|
|
//
|
|
} finally {
|
|
setLoading(false)
|
|
setLoaded(true)
|
|
}
|
|
}
|
|
|
|
const handlePosted = useCallback((newPost) => {
|
|
setPosts((prev) => [newPost, ...prev])
|
|
}, [])
|
|
|
|
const handleDeleted = useCallback((postId) => {
|
|
setPosts((prev) => prev.filter((p) => p.id !== postId))
|
|
}, [])
|
|
|
|
return (
|
|
<div className="flex gap-6 py-4 items-start">
|
|
{/* ── Main feed column ──────────────────────────────────────────────── */}
|
|
<div className="flex-1 min-w-0 space-y-4">
|
|
{/* Composer (owner only) */}
|
|
{isOwner && authUser && (
|
|
<PostComposer user={authUser} onPosted={handlePosted} />
|
|
)}
|
|
|
|
{/* Skeletons while loading */}
|
|
{!loaded && loading && (
|
|
<div className="space-y-4">
|
|
{[1, 2, 3].map((i) => <PostCardSkeleton key={i} />)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty state */}
|
|
{loaded && !loading && posts.length === 0 && (
|
|
<EmptyPostsState isOwner={isOwner} username={username} />
|
|
)}
|
|
|
|
{/* Post list */}
|
|
{posts.length > 0 && (
|
|
<div className="space-y-4">
|
|
{posts.map((post) => (
|
|
<PostCard
|
|
key={post.id}
|
|
post={post}
|
|
isLoggedIn={!!authUser}
|
|
viewerUsername={authUser?.username ?? null}
|
|
onDelete={handleDeleted}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Load more */}
|
|
{loaded && hasMore && (
|
|
<div className="flex justify-center py-4">
|
|
<button
|
|
onClick={() => fetchFeed(page + 1)}
|
|
disabled={loading}
|
|
className="px-6 py-2.5 rounded-xl bg-white/5 hover:bg-white/10 text-slate-300 text-sm transition-colors disabled:opacity-50"
|
|
>
|
|
{loading ? (
|
|
<><i className="fa-solid fa-spinner fa-spin mr-2" />Loading…</>
|
|
) : 'Load more posts'}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* ── Sidebar ───────────────────────────────────────────────────────── */}
|
|
<aside className="w-72 xl:w-80 shrink-0 hidden lg:block sticky top-20 self-start">
|
|
<FeedSidebar
|
|
user={user}
|
|
profile={profile}
|
|
stats={stats}
|
|
followerCount={followerCount}
|
|
recentFollowers={recentFollowers}
|
|
socialLinks={socialLinks}
|
|
countryName={countryName}
|
|
isLoggedIn={!!authUser}
|
|
onTabChange={onTabChange}
|
|
/>
|
|
</aside>
|
|
</div>
|
|
)
|
|
}
|