/**
* ProfileStats Component
*
* Enhanced stats bar with sticky behavior, reputation tiers, and improved mobile layout
* Displays user statistics (posts, followers, following, reputation)
*/
'use client';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import { MessageSquare, Users, UserPlus, Award, TrendingUp } from 'lucide-react';
import { cn } from '@canadagpt/design-system';
interface ProfileStatsProps {
username: string;
stats: {
posts_count: number;
replies_count: number;
followers_count: number;
following_count: number;
reputation_score: number;
};
}
// Reputation tier definitions
type ReputationTier = {
name: string;
minScore: number;
color: string;
bgColor: string;
borderColor: string;
};
const REPUTATION_TIERS: ReputationTier[] = [
{
name: 'Newcomer',
minScore: 0,
color: 'text-gray-600 dark:text-gray-400',
bgColor: 'bg-gray-100 dark:bg-gray-800',
borderColor: 'border-gray-300 dark:border-gray-600',
},
{
name: 'Contributor',
minScore: 10,
color: 'text-green-600 dark:text-green-400',
bgColor: 'bg-green-50 dark:bg-green-900/20',
borderColor: 'border-green-300 dark:border-green-700',
},
{
name: 'Active',
minScore: 50,
color: 'text-blue-600 dark:text-blue-400',
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
borderColor: 'border-blue-300 dark:border-blue-700',
},
{
name: 'Respected',
minScore: 100,
color: 'text-purple-600 dark:text-purple-400',
bgColor: 'bg-purple-50 dark:bg-purple-900/20',
borderColor: 'border-purple-300 dark:border-purple-700',
},
{
name: 'Expert',
minScore: 500,
color: 'text-amber-600 dark:text-amber-400',
bgColor: 'bg-amber-50 dark:bg-amber-900/20',
borderColor: 'border-amber-300 dark:border-amber-700',
},
{
name: 'Leader',
minScore: 1000,
color: 'text-red-600 dark:text-red-400',
bgColor: 'bg-red-50 dark:bg-red-900/20',
borderColor: 'border-red-300 dark:border-red-700',
},
];
function getReputationTier(score: number): ReputationTier {
// Find the highest tier the user qualifies for
for (let i = REPUTATION_TIERS.length - 1; i >= 0; i--) {
if (score >= REPUTATION_TIERS[i].minScore) {
return REPUTATION_TIERS[i];
}
}
return REPUTATION_TIERS[0];
}
export function ProfileStats({ username, stats }: ProfileStatsProps) {
const [isSticky, setIsSticky] = useState(false);
const statsRef = useRef<HTMLDivElement>(null);
const sentinelRef = useRef<HTMLDivElement>(null);
// Sticky detection using Intersection Observer
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const observer = new IntersectionObserver(
([entry]) => {
// When sentinel is not visible (scrolled past), make sticky
setIsSticky(!entry.isIntersecting);
},
{
threshold: 0,
rootMargin: '-1px 0px 0px 0px',
}
);
observer.observe(sentinel);
return () => observer.disconnect();
}, []);
const tier = getReputationTier(stats.reputation_score);
const statItems = [
{
label: 'Posts',
value: stats.posts_count + stats.replies_count,
icon: MessageSquare,
href: `/users/${username}?tab=posts`,
},
{
label: 'Followers',
value: stats.followers_count,
icon: Users,
href: `/users/${username}/followers`,
},
{
label: 'Following',
value: stats.following_count,
icon: UserPlus,
href: `/users/${username}/following`,
},
];
return (
<>
{/* Sentinel element for intersection observer */}
<div ref={sentinelRef} className="h-0" aria-hidden="true" />
{/* Stats Bar */}
<div
ref={statsRef}
className={cn(
'border-y bg-muted/30 transition-all duration-200',
isSticky && 'md:sticky md:top-0 md:z-40 md:shadow-md md:backdrop-blur-sm md:bg-background/95'
)}
>
<div className="max-w-5xl mx-auto px-4 py-4 md:px-6">
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
{/* Standard Stats */}
{statItems.map((item) => (
<Link
key={item.label}
href={item.href}
className="flex flex-col items-center gap-1.5 rounded-lg p-2 transition-colors hover:bg-muted/60"
>
<div className="flex items-center gap-2">
<item.icon className="h-4 w-4 text-muted-foreground" />
<span className="text-xl sm:text-2xl font-bold">
{item.value.toLocaleString()}
</span>
</div>
<span className="text-xs sm:text-sm text-muted-foreground">{item.label}</span>
</Link>
))}
{/* Reputation with Tier Badge */}
<div className="flex flex-col items-center gap-1.5 rounded-lg p-2">
<div className="flex items-center gap-2">
<Award className={cn('h-4 w-4', tier.color)} />
<span className="text-xl sm:text-2xl font-bold">
{stats.reputation_score.toLocaleString()}
</span>
</div>
<div className="flex items-center gap-1.5">
<span className="text-xs sm:text-sm text-muted-foreground">Reputation</span>
<span
className={cn(
'px-1.5 py-0.5 rounded text-[10px] sm:text-xs font-medium border',
tier.bgColor,
tier.color,
tier.borderColor
)}
title={`${tier.name} tier (${tier.minScore}+ points)`}
>
{tier.name}
</span>
</div>
</div>
</div>
</div>
</div>
</>
);
}