![]() 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/ |
'use client';
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { formatDistanceToNow } from 'date-fns';
import {
Users,
MessageSquare,
ThumbsUp,
UserPlus,
Eye,
Star,
Activity,
Zap
} from 'lucide-react';
interface ActivityItem {
id: string;
type: 'follow' | 'unfollow' | 'friend_request' | 'friend_accept' | 'endorse' | 'message' | 'profile_view';
fromUserId: string;
fromUserName: string;
fromUserAvatar?: string;
toUserId: string;
timestamp: number;
data?: any;
}
interface RealTimeActivityFeedProps {
userId: string;
maxItems?: number;
showLiveIndicator?: boolean;
}
const RealTimeActivityFeed: React.FC<RealTimeActivityFeedProps> = ({
userId,
maxItems = 10,
showLiveIndicator = true
}) => {
const [activities, setActivities] = useState<ActivityItem[]>([]);
const [isLive, setIsLive] = useState(false);
const [lastUpdate, setLastUpdate] = useState<number | null>(null);
// Simulate real-time updates (in real implementation, this would come from WebSocket)
useEffect(() => {
const interval = setInterval(() => {
// Simulate new activity
const shouldAddActivity = Math.random() < 0.1; // 10% chance every 5 seconds
if (shouldAddActivity) {
const newActivity: ActivityItem = {
id: `activity_${Date.now()}_${Math.random().toString(36).substr(2, 9)}
type: ['follow', 'endorse', 'profile_view', 'message'][Math.floor(Math.random() * 4)] as ActivityItem['type'],
fromUserId: `user_${Math.random().toString(36).substr(2, 9)}
fromUserName: `User ${Math.floor(Math.random() * 1000)}
toUserId: userId,
timestamp: Date.now(),
data: {}
};
setActivities(prev => [newActivity, ...prev.slice(0, maxItems - 1)]);
setLastUpdate(Date.now());
setIsLive(true);
// Hide live indicator after 3 seconds
setTimeout(() => setIsLive(false), 3000);
}
}, 5000);
return () => clearInterval(interval);
}, [userId, maxItems]);
const getActivityIcon = (type: ActivityItem['type']) => {
switch (type) {
case 'follow':
return <Users className="h-4 w-4 text-blue-500" />;
case 'friend_request':
return <UserPlus className="h-4 w-4 text-green-500" />;
case 'endorse':
return <ThumbsUp className="h-4 w-4 text-purple-500" />;
case 'message':
return <MessageSquare className="h-4 w-4 text-orange-500" />;
case 'profile_view':
return <Eye className="h-4 w-4 text-gray-500" />;
default:
return <Activity className="h-4 w-4 text-gray-500" />;
}
};
const getActivityText = (activity: ActivityItem) => {
switch (activity.type) {
case 'follow':
return `${activity.fromUserName} started following you
case 'friend_request':
return `${activity.fromUserName} sent you a friend request
case 'endorse':
return `${activity.fromUserName} endorsed you
case 'message':
return `${activity.fromUserName} sent you a message
case 'profile_view':
return `${activity.fromUserName} viewed your profile
default:
return `${activity.fromUserName} interacted with your profile
}
};
const getActivityColor = (type: ActivityItem['type']) => {
switch (type) {
case 'follow':
return 'bg-blue-50 border-blue-200';
case 'friend_request':
return 'bg-green-50 border-green-200';
case 'endorse':
return 'bg-purple-50 border-purple-200';
case 'message':
return 'bg-orange-50 border-orange-200';
case 'profile_view':
return 'bg-gray-50 border-gray-200';
default:
return 'bg-gray-50 border-gray-200';
}
};
return (
<div className="space-y-4">
{/* Live Indicator */}
{showLiveIndicator && (
<div className="flex items-center gap-2 text-sm text-gray-600">
<div className="flex items-center gap-1">
<Zap className={`h-3 w-3 ${isLive ? 'text-green-500 animate-pulse' : 'text-gray-400'}
<span>{isLive ? 'Live' : 'Real-time'}</span>
</div>
{lastUpdate && (
<span className="text-xs text-gray-400">
Last update: {formatDistanceToNow(lastUpdate, { addSuffix: true })}
</span>
)}
</div>
)}
{/* Activity List */}
<div className="space-y-3">
<AnimatePresence>
{activities.map((activity, index) => (
<motion.div
key={activity.id}
initial={{ opacity: 0, y: -10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -10, scale: 0.95 }}
transition={{
duration: 0.3,
delay: index * 0.1,
type: "spring",
stiffness: 300
}}
className={`p-3 rounded-lg border ${getActivityColor(activity.type)} transition-all hover:shadow-sm
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0">
{activity.fromUserAvatar ? (
<img
src={activity.fromUserAvatar}
alt={activity.fromUserName}
className="w-8 h-8 rounded-full object-cover"
/>
) : (
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
{getActivityIcon(activity.type)}
</div>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-medium text-gray-900 truncate">
{activity.fromUserName}
</span>
{getActivityIcon(activity.type)}
</div>
<p className="text-sm text-gray-600 mt-1">
{getActivityText(activity)}
</p>
<p className="text-xs text-gray-400 mt-1">
{formatDistanceToNow(activity.timestamp, { addSuffix: true })}
</p>
</div>
{/* New indicator for recent activities */}
{Date.now() - activity.timestamp < 30000 && (
<div className="flex-shrink-0">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
</div>
)}
</div>
</motion.div>
))}
</AnimatePresence>
{/* Empty State */}
{activities.length === 0 && (
<div className="text-center py-8 text-gray-500">
<Activity className="h-12 w-12 mx-auto mb-3 text-gray-300" />
<p className="text-sm">No recent activity</p>
<p className="text-xs mt-1">Activity will appear here in real-time</p>
</div>
)}
</div>
{/* Activity Stats */}
{activities.length > 0 && (
<div className="pt-4 border-t border-gray-200">
<div className="flex justify-between text-xs text-gray-500">
<span>{activities.length} recent activities</span>
<span>Real-time updates enabled</span>
</div>
</div>
)}
</div>
);
};
export default RealTimeActivityFeed;