![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.quebec/private_html/src/components/ |
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Trophy,
Award,
Star,
MessageCircle,
Eye,
TrendingUp,
Calendar,
Clock,
Users,
FileText,
Gavel,
Heart,
ThumbsUp,
Activity
} from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
interface ActivityItem {
id: string;
type: 'achievement' | 'case' | 'review' | 'endorsement' | 'activity' | 'milestone';
title: string;
description: string;
timestamp: string;
icon?: string;
color?: string;
badge?: string;
}
interface ProfileActivityFeedProps {
profileId: string;
isOwnProfile: boolean;
}
const ProfileActivityFeed: React.FC<ProfileActivityFeedProps> = ({
profileId,
isOwnProfile
}) => {
const [activities, setActivities] = useState<ActivityItem[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'recent' | 'achievements' | 'milestones'>('recent');
useEffect(() => {
fetchActivities();
}, [profileId]);
const fetchActivities = async () => {
try {
const response = await fetch(
if (response.ok) {
const data = await response.json();
setActivities(data.activities || []);
}
} catch (error) {
} finally {
setLoading(false);
}
};
const getActivityIcon = (type: string) => {
switch (type) {
case 'achievement':
return <Trophy className="h-5 w-5 text-yellow-500" />;
case 'case':
return <Gavel className="h-5 w-5 text-blue-500" />;
case 'review':
return <Star className="h-5 w-5 text-purple-500" />;
case 'endorsement':
return <ThumbsUp className="h-5 w-5 text-green-500" />;
case 'milestone':
return <Award className="h-5 w-5 text-orange-500" />;
default:
return <Activity className="h-5 w-5 text-gray-500" />;
}
};
const getActivityColor = (type: string) => {
switch (type) {
case 'achievement':
return 'bg-yellow-50 border-yellow-200';
case 'case':
return 'bg-blue-50 border-blue-200';
case 'review':
return 'bg-purple-50 border-purple-200';
case 'endorsement':
return 'bg-green-50 border-green-200';
case 'milestone':
return 'bg-orange-50 border-orange-200';
default:
return 'bg-gray-50 border-gray-200';
}
};
const filteredActivities = activities.filter(activity => {
switch (activeTab) {
case 'recent':
return true;
case 'achievements':
return activity.type === 'achievement' || activity.type === 'milestone';
case 'milestones':
return activity.type === 'milestone';
default:
return true;
}
});
if (loading) {
return (
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-100">
<div className="animate-pulse">
<div className="h-6 bg-gray-200 rounded w-1/3 mb-4"></div>
<div className="space-y-3">
{[1, 2, 3].map(i => (
<div key={i} className="flex items-center space-x-3">
<div className="h-10 w-10 bg-gray-200 rounded-full"></div>
<div className="flex-1">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
</div>
</div>
))}
</div>
</div>
</div>
);
}
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white rounded-xl shadow-lg border border-gray-100"
>
{/* Header */}
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
<Activity className="h-5 w-5 mr-2 text-blue-600" />
Recent Activity
</h3>
<div className="flex space-x-1">
{['recent', 'achievements', 'milestones'].map(tab => (
<button
key={tab}
onClick={() => setActiveTab(tab as any)}
className={
activeTab === tab
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:text-gray-900'
}
>
{tab.charAt(0).toUpperCase() + tab.slice(1)}
</button>
))}
</div>
</div>
</div>
{/* Activity List */}
<div className="p-6">
<AnimatePresence mode="wait">
{filteredActivities.length > 0 ? (
<motion.div
key={activeTab}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="space-y-4"
>
{filteredActivities.map((activity, index) => (
<motion.div
key={activity.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.1 }}
className={`flex items-start space-x-3 p-4 rounded-lg border ${getActivityColor(activity.type)}
>
<div className="flex-shrink-0 mt-1">
{getActivityIcon(activity.type)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium text-gray-900">
{activity.title}
</h4>
{activity.badge && (
<span className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{activity.badge}
</span>
)}
</div>
<p className="text-sm text-gray-600 mt-1">
{activity.description}
</p>
<div className="flex items-center text-xs text-gray-500 mt-2">
<Clock className="h-3 w-3 mr-1" />
{formatDistanceToNow(new Date(activity.timestamp), { addSuffix: true })}
</div>
</div>
</motion.div>
))}
</motion.div>
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-center py-8"
>
<Activity className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">
{activeTab === 'recent' && 'No recent activity'}
{activeTab === 'achievements' && 'No achievements yet'}
{activeTab === 'milestones' && 'No milestones yet'}
</p>
</motion.div>
)}
</AnimatePresence>
</div>
{/* Quick Stats */}
{isOwnProfile && (
<div className="border-t border-gray-200 p-6">
<h4 className="text-sm font-medium text-gray-900 mb-3">This Week</h4>
<div className="grid grid-cols-3 gap-4 text-center">
<div>
<div className="text-lg font-bold text-blue-600">
{activities.filter(a => a.type === 'case').length}
</div>
<div className="text-xs text-gray-600">Cases</div>
</div>
<div>
<div className="text-lg font-bold text-green-600">
{activities.filter(a => a.type === 'endorsement').length}
</div>
<div className="text-xs text-gray-600">Endorsements</div>
</div>
<div>
<div className="text-lg font-bold text-purple-600">
{activities.filter(a => a.type === 'review').length}
</div>
<div className="text-xs text-gray-600">Reviews</div>
</div>
</div>
</div>
)}
</motion.div>
);
};
export default ProfileActivityFeed;