T.ME/BIBIL_0DAY
CasperSecurity


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.ca/public_html/src/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/public_html/src/components/ProfileActivityFeed.tsx
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(`/api/profile/${profileId}/activity`);
      if (response.ok) {
        const data = await response.json();
        setActivities(data.activities || []);
      }
    } catch (error) {
      console.error('Error fetching activities:', 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={`px-3 py-1 text-sm rounded-lg transition-colors ${
                  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; 

CasperSecurity Mini