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) => (
{card.label}
{card.value}
))}
{isOwner && authUser && ( )} {!loaded && loading && (
{[1, 2, 3].map((i) => )}
)} {loaded && error && posts.length === 0 && ( fetchFeed(1)} /> )} {loaded && !loading && !error && posts.length === 0 && ( )} {posts.length > 0 && (
{posts.map((post) => ( ))}
)} {loaded && hasMore && (
)}
) }