import React, { useState, useEffect } from 'react'
import axios from 'axios'
// ─────────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────────
function fmt(n) {
if (n === null || n === undefined) return '0'
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M'
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.0$/, '') + 'k'
return String(n)
}
const SOCIAL_META = {
twitter: { icon: 'fa-brands fa-x-twitter', label: 'Twitter / X', prefix: 'https://x.com/' },
instagram: { icon: 'fa-brands fa-instagram', label: 'Instagram', prefix: 'https://instagram.com/' },
deviantart: { icon: 'fa-brands fa-deviantart', label: 'DeviantArt', prefix: 'https://deviantart.com/' },
artstation: { icon: 'fa-brands fa-artstation', label: 'ArtStation', prefix: 'https://artstation.com/' },
behance: { icon: 'fa-brands fa-behance', label: 'Behance', prefix: 'https://behance.net/' },
website: { icon: 'fa-solid fa-globe', label: 'Website', prefix: '' },
youtube: { icon: 'fa-brands fa-youtube', label: 'YouTube', prefix: '' },
twitch: { icon: 'fa-brands fa-twitch', label: 'Twitch', prefix: '' },
}
function SideCard({ title, icon, children, className = '' }) {
return (
{title && (
{icon && }
{title}
)}
{children}
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Stats card
// ─────────────────────────────────────────────────────────────────────────────
function StatsCard({ stats, followerCount, user, onTabChange }) {
const items = [
{
label: 'Artworks',
value: fmt(stats?.uploads_count ?? 0),
icon: 'fa-solid fa-image',
color: 'text-sky-400',
tab: 'artworks',
},
{
label: 'Followers',
value: fmt(followerCount ?? stats?.followers_count ?? 0),
icon: 'fa-solid fa-user-group',
color: 'text-violet-400',
tab: null,
},
{
label: 'Following',
value: fmt(stats?.following_count ?? 0),
icon: 'fa-solid fa-user-plus',
color: 'text-emerald-400',
tab: null,
},
{
label: 'Awards',
value: fmt(stats?.awards_received_count ?? 0),
icon: 'fa-solid fa-trophy',
color: 'text-amber-400',
tab: 'stats',
},
]
return (
{items.map((item) => (
))}
)
}
// ─────────────────────────────────────────────────────────────────────────────
// About card
// ─────────────────────────────────────────────────────────────────────────────
function AboutCard({ user, profile, socialLinks, countryName }) {
const bio = profile?.bio || profile?.about || profile?.description
const website = profile?.website || user?.website
const hasSocials = socialLinks && Object.keys(socialLinks).length > 0
const hasContent = bio || countryName || website || hasSocials
if (!hasContent) return null
return (
{bio && (
{bio}
)}
{countryName && (
{countryName}
)}
{website && (
)}
{hasSocials && (
{Object.entries(socialLinks).map(([platform, link]) => {
const meta = SOCIAL_META[platform] ?? SOCIAL_META.website
const url = link.url || (meta.prefix ? meta.prefix + link.handle : null)
if (!url) return null
return (
)
})}
)}
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Recent followers card
// ─────────────────────────────────────────────────────────────────────────────
function RecentFollowersCard({ recentFollowers, followerCount, onTabChange }) {
const followers = recentFollowers ?? []
if (followers.length === 0) return null
return (
{followers.slice(0, 6).map((f) => (
{f.name || f.uname || f.username}
@{f.username}
))}
{followerCount > 6 && (
)}
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Trending hashtags card
// ─────────────────────────────────────────────────────────────────────────────
function TrendingHashtagsCard() {
const [tags, setTags] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
axios.get('/api/feed/hashtags/trending', { params: { limit: 8 } })
.then(({ data }) => setTags(Array.isArray(data.hashtags) ? data.hashtags : []))
.catch(() => {})
.finally(() => setLoading(false))
}, [])
if (!loading && tags.length === 0) return null
return (
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Suggested to follow card
// ─────────────────────────────────────────────────────────────────────────────
function SuggestionsCard({ excludeUsername, isLoggedIn }) {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
if (!isLoggedIn) { setLoading(false); return }
axios.get('/api/search/users', { params: { q: '', per_page: 5 } })
.then(({ data }) => {
const list = (data.data ?? []).filter((u) => u.username !== excludeUsername).slice(0, 4)
setUsers(list)
})
.catch(() => {})
.finally(() => setLoading(false))
}, [excludeUsername, isLoggedIn])
if (!isLoggedIn) return null
if (!loading && users.length === 0) return null
return (
)
}
// ─────────────────────────────────────────────────────────────────────────────
// Main export
// ─────────────────────────────────────────────────────────────────────────────
/**
* FeedSidebar
*
* Props:
* user object { id, username, name, uploads_count, ...}
* profile object { bio, about, country, website, ... }
* stats object from user_statistics
* followerCount number
* recentFollowers array [{ id, username, name, avatar_url, profile_url }]
* socialLinks object keyed by platform
* countryName string|null
* isLoggedIn boolean
* onTabChange function(tab)
*/
export default function FeedSidebar({
user,
profile,
stats,
followerCount,
recentFollowers,
socialLinks,
countryName,
isLoggedIn,
onTabChange,
}) {
return (
)
}