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 formatCompactNumber(value) {
return Number(value ?? 0).toLocaleString()
}
function EmptyPostsState({ isOwner, username }) {
return (
No posts yet
{isOwner ? (
Share works in progress, announce releases, or add a bit of personality beyond the gallery.
) : (
@{username} has not published any profile posts yet.
)}
)
}
function ErrorPostsState({ onRetry }) {
return (
Posts could not be loaded
The profile shell loaded, but the posts feed request failed. Retry without leaving the page.
)
}
/**
* 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,
suggestedUsers,
socialLinks,
countryName,
profileUrl,
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)
const [error, setError] = useState(false)
React.useEffect(() => {
fetchFeed(1)
}, [username])
const fetchFeed = async (p = 1) => {
setLoading(true)
setError(false)
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 {
setError(true)
} 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))
}, [])
const summaryCards = [
{ label: 'Followers', value: formatCompactNumber(followerCount), icon: 'fa-user-group' },
{ label: 'Uploads', value: formatCompactNumber(stats?.uploads_count ?? 0), icon: 'fa-image' },
{ label: 'Awards', value: formatCompactNumber(stats?.awards_received_count ?? 0), icon: 'fa-trophy' },
{ label: 'Location', value: countryName || 'Unknown', icon: 'fa-location-dot' },
]
return (
Profile posts
Updates, thoughts, and shared work from @{username}
This stream adds the human layer to the profile: quick notes, shared artwork posts, and announcements that do not belong inside the gallery grid.
{profileUrl ? (
Canonical profile
) : null}
{summaryCards.map((card) => (
))}
{isOwner && authUser && (
)}
{!loaded && loading && (
)}
{loaded && error && posts.length === 0 && (
fetchFeed(1)} />
)}
{loaded && !loading && !error && posts.length === 0 && (
)}
{posts.length > 0 && (
{posts.map((post) => (
))}
)}
{loaded && hasMore && (
)}
)
}