import React from 'react'
const SOCIAL_ICONS = {
twitter: { icon: 'fa-brands fa-x-twitter', label: 'X / Twitter' },
deviantart: { icon: 'fa-brands fa-deviantart', label: 'DeviantArt' },
instagram: { icon: 'fa-brands fa-instagram', label: 'Instagram' },
behance: { icon: 'fa-brands fa-behance', label: 'Behance' },
artstation: { icon: 'fa-solid fa-palette', label: 'ArtStation' },
youtube: { icon: 'fa-brands fa-youtube', label: 'YouTube' },
website: { icon: 'fa-solid fa-link', label: 'Website' },
}
function formatNumber(value) {
return Number(value ?? 0).toLocaleString()
}
function formatRelativeDate(value) {
if (!value) return null
try {
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
const now = new Date()
const diffSeconds = Math.round((date.getTime() - now.getTime()) / 1000)
const absSeconds = Math.abs(diffSeconds)
const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
if (absSeconds < 3600) {
return formatter.format(Math.round(diffSeconds / 60), 'minute')
}
if (absSeconds < 86400) {
return formatter.format(Math.round(diffSeconds / 3600), 'hour')
}
if (absSeconds < 604800) {
return formatter.format(Math.round(diffSeconds / 86400), 'day')
}
if (absSeconds < 2629800) {
return formatter.format(Math.round(diffSeconds / 604800), 'week')
}
return formatter.format(Math.round(diffSeconds / 2629800), 'month')
} catch {
return null
}
}
function formatShortDate(value) {
if (!value) return null
try {
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
} catch {
return null
}
}
function truncateText(value, maxLength = 140) {
const text = String(value ?? '').trim()
if (!text) return ''
if (text.length <= maxLength) return text
return `${text.slice(0, maxLength).trimEnd()}...`
}
function buildInterestGroups(artworks = []) {
const categoryMap = new Map()
const contentTypeMap = new Map()
artworks.forEach((artwork) => {
const categoryKey = String(artwork?.category_slug || artwork?.category || '').trim().toLowerCase()
const categoryLabel = String(artwork?.category || '').trim()
const contentTypeKey = String(artwork?.content_type_slug || artwork?.content_type || '').trim().toLowerCase()
const contentTypeLabel = String(artwork?.content_type || '').trim()
if (categoryKey && categoryLabel) {
categoryMap.set(categoryKey, {
label: categoryLabel,
count: (categoryMap.get(categoryKey)?.count ?? 0) + 1,
})
}
if (contentTypeKey && contentTypeLabel) {
contentTypeMap.set(contentTypeKey, {
label: contentTypeLabel,
count: (contentTypeMap.get(contentTypeKey)?.count ?? 0) + 1,
})
}
})
const toSortedList = (source) => Array.from(source.values())
.sort((left, right) => right.count - left.count || left.label.localeCompare(right.label))
.slice(0, 5)
return {
categories: toSortedList(categoryMap),
contentTypes: toSortedList(contentTypeMap),
}
}
function InfoRow({ icon, label, children }) {
return (
)
}
function StatCard({ icon, label, value, tone = 'sky' }) {
const tones = {
sky: 'text-sky-300 bg-sky-400/10 border-sky-300/15',
amber: 'text-amber-200 bg-amber-300/10 border-amber-300/15',
emerald: 'text-emerald-200 bg-emerald-400/10 border-emerald-300/15',
violet: 'text-violet-200 bg-violet-400/10 border-violet-300/15',
}
return (
)
}
function SectionCard({ icon, eyebrow, title, children, className = '' }) {
return (
)
}
/**
* TabAbout
* Bio, social links, metadata - replaces old sidebar profile card.
*/
export default function TabAbout({ user, profile, stats, achievements, artworks, creatorStories, profileComments, socialLinks, countryName, followerCount, recentFollowers, leaderboardRank }) {
const uname = user.username || user.name
const displayName = user.name || uname
const about = profile?.about
const website = profile?.website
const joinDate = user.created_at
? new Date(user.created_at).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
: null
const lastVisit = user.last_visit_at
? (() => {
try {
const d = new Date(user.last_visit_at)
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
} catch { return null }
})()
: null
const genderMap = { M: 'Male', F: 'Female', X: 'Non-binary / N/A' }
const genderLabel = genderMap[profile?.gender?.toUpperCase()] ?? null
const birthDate = profile?.birthdate
? (() => {
try {
return new Date(profile.birthdate).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })
} catch { return null }
})()
: null
const lastSeenRelative = formatRelativeDate(user.last_visit_at)
const socialEntries = socialLinks
? Object.entries(socialLinks).filter(([, link]) => link?.url)
: []
const followers = recentFollowers ?? []
const recentAchievements = Array.isArray(achievements?.recent) ? achievements.recent : []
const stories = Array.isArray(creatorStories) ? creatorStories : []
const comments = Array.isArray(profileComments) ? profileComments : []
const interestGroups = buildInterestGroups(Array.isArray(artworks) ? artworks : [])
const summaryCards = [
{ icon: 'fa-user-group', label: 'Followers', value: formatNumber(followerCount), tone: 'sky' },
{ icon: 'fa-images', label: 'Uploads', value: formatNumber(stats?.uploads_count ?? 0), tone: 'violet' },
{ icon: 'fa-eye', label: 'Profile views', value: formatNumber(stats?.profile_views_count ?? 0), tone: 'emerald' },
{ icon: 'fa-trophy', label: 'Weekly rank', value: leaderboardRank?.rank ? `#${formatNumber(leaderboardRank.rank)}` : 'Unranked', tone: 'amber' },
]
return (
{summaryCards.map((card) => (
))}
{about ? (
{about}
) : (
This creator has not written a public bio yet.
)}
{followers.length > 0 ? (
) : null}
{recentAchievements.length > 0 ? (
{recentAchievements.slice(0, 4).map((achievement) => (
{achievement.name}
{achievement.description ? (
{achievement.description}
) : null}
{achievement.unlocked_at ? (
{formatShortDate(achievement.unlocked_at) || 'Unlocked'}
) : null}
+{formatNumber(achievement.xp_reward ?? 0)} XP
))}
) : null}
{stories.length > 0 || comments.length > 0 ? (
{stories.length > 0 ? (
Latest story
{formatShortDate(stories[0]?.published_at) || 'Published'}
{stories[0].title}
{stories[0].excerpt ? (
{truncateText(stories[0].excerpt, 180)}
) : null}
{stories[0].reading_time ? (
{stories[0].reading_time} min read
) : null}
{formatNumber(stories[0].views ?? 0)} views
{formatNumber(stories[0].comments_count ?? 0)} comments
) : null}
{comments.length > 0 ? (
Latest guestbook comment
{formatRelativeDate(comments[0]?.created_at) || 'Recently'}
![{comments[0].author_name}]({comments[0].author_avatar)
{ e.target.src = '/images/avatar_default.webp' }}
/>
) : null}
) : null}
Creator level
Lv {formatNumber(user?.level ?? 1)}
{user?.rank || 'Creator'}
XP
{formatNumber(user?.xp ?? 0)}
Weekly rank
{leaderboardRank?.rank ? `#${formatNumber(leaderboardRank.rank)}` : 'Not ranked'}
{leaderboardRank?.score ?
Score {formatNumber(leaderboardRank.score)}
: null}
Community size
{formatNumber(followerCount)}
Followers
{formatNumber(stats?.uploads_count ?? 0)}
{formatNumber(stats?.artwork_views_received_count ?? 0)}
{formatNumber(stats?.downloads_received_count ?? 0)}
{formatNumber(stats?.favourites_received_count ?? 0)}
{formatNumber(stats?.comments_received_count ?? 0)}
{interestGroups.categories.length > 0 || interestGroups.contentTypes.length > 0 ? (
{interestGroups.categories.length > 0 ? (
Top categories
{interestGroups.categories.map((category) => (
{category.label}
{formatNumber(category.count)}
))}
) : null}
{interestGroups.contentTypes.length > 0 ? (
Preferred formats
{interestGroups.contentTypes.map((contentType) => (
{contentType.label}
{formatNumber(contentType.count)}
))}
) : null}
) : null}
{socialEntries.length > 0 ? (
{socialEntries.map(([platform, link]) => {
const si = SOCIAL_ICONS[platform] ?? { icon: 'fa-solid fa-link', label: platform }
const href = link.url.startsWith('http') ? link.url : `https://${link.url}`
return (
{si.label}
)
})}
) : null}
)
}