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/private_html/src/components/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/components/ProfilePopover.tsx
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useSession } from 'next-auth/react';
import toast from 'react-hot-toast';

interface UserProfile {
  id: string;
  email: string;
  name: string;
  role: string;
  profilePicture?: string;
  bio?: string;
  title?: string;
  specialization?: string;
  yearsOfExperience?: number;
  education?: string;
  officeLocation?: string;
  linkedinUrl?: string;
  websiteUrl?: string;
  availability?: string;
  timezone?: string;
  pronouns?: string;
  isProfilePublic: boolean;
  lastActive?: string;
  language: string;
  createdAt: string;
}

interface ProfilePopoverProps {
  userId: string;
  isOpen: boolean;
  onClose: () => void;
  position?: 'left' | 'right' | 'center';
  onStartDirectMessage?: (userId: string, userName: string) => void;
}

const ProfilePopover: React.FC<ProfilePopoverProps> = ({
  userId,
  isOpen,
  onClose,
  position = 'center',
  onStartDirectMessage
}) => {
  const { data: session } = useSession();
  const [profile, setProfile] = useState<UserProfile | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (isOpen && userId) {
      fetchProfile();
    }
  }, [isOpen, userId]);

  const fetchProfile = async () => {
    setLoading(true);
    setError(null);
    
    try {
      const response = await fetch(`/api/user/profile?userId=${userId}`);
      if (response.ok) {
        const data = await response.json();
        setProfile(data);
      } else if (response.status === 403) {
        setError('This profile is private');
      } else {
        setError('Failed to load profile');
      }
    } catch (error) {
      setError('Error loading profile');
    } finally {
      setLoading(false);
    }
  };

  const getRoleIcon = (role: string) => {
    switch (role) {
      case 'ADMIN':
        return '⚖️';
      case 'LAWYER':
        return '👩‍⚖️';
      default:
        return '👤';
    }
  };

  const getRoleName = (role: string) => {
    switch (role) {
      case 'ADMIN':
        return 'Legal Team';
      case 'LAWYER':
        return 'Lawyer';
      default:
        return 'Client';
    }
  };

  const getStatusColor = (availability?: string) => {
    switch (availability) {
      case 'Available':
        return 'bg-green-400';
      case 'Busy':
        return 'bg-yellow-400';
      case 'Away':
        return 'bg-orange-400';
      case 'Do Not Disturb':
        return 'bg-red-400';
      default:
        return 'bg-gray-400';
    }
  };

  const isOnline = (lastActive?: string) => {
    if (!lastActive) return false;
    const lastActiveDate = new Date(lastActive);
    const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
    return lastActiveDate > fiveMinutesAgo;
  };

  const formatLastActive = (lastActive?: string) => {
    if (!lastActive) return 'Unknown';
    const date = new Date(lastActive);
    const now = new Date();
    const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
    
    if (diffInMinutes < 1) return 'Just now';
    if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
    if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}h ago`;
    return date.toLocaleDateString();
  };

  const getInitials = (name: string) => {
    return name
      .split(' ')
      .map(n => n[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
  };

  const handleStartDirectMessage = () => {
    if (profile && onStartDirectMessage) {
      onStartDirectMessage(profile.id, profile.name);
      onClose();
    }
  };

  const copyEmail = () => {
    if (profile?.email) {
      navigator.clipboard.writeText(profile.email);
      toast.success('Email copied to clipboard');
    }
  };

  const openLinkedIn = () => {
    if (profile?.linkedinUrl) {
      window.open(profile.linkedinUrl, '_blank');
    }
  };

  const openWebsite = () => {
    if (profile?.websiteUrl) {
      window.open(profile.websiteUrl, '_blank');
    }
  };

  const positionClasses = {
    left: 'right-0',
    right: 'left-0',
    center: 'left-1/2 transform -translate-x-1/2'
  };

  if (!isOpen) return null;

  return (
    <AnimatePresence>
      <motion.div
        initial={{ opacity: 0, scale: 0.9, y: 10 }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{ opacity: 0, scale: 0.9, y: 10 }}
        className={`absolute top-full mt-2 ${positionClasses[position]} bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 w-80 max-w-sm`}
        style={{ zIndex: 99999 }}
      >
        {/* Header */}
        <div className="flex justify-between items-start p-4 border-b border-gray-200 dark:border-gray-700">
          <h3 className="text-lg font-semibold text-gray-900 dark:text-white">
            User Profile
          </h3>
          <button
            onClick={onClose}
            className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
          >
            <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
            </svg>
          </button>
        </div>

        {/* Content */}
        <div className="p-4">
          {loading ? (
            <div className="flex items-center justify-center py-8">
              <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
            </div>
          ) : error ? (
            <div className="text-center py-8">
              <div className="text-gray-400 text-4xl mb-2">🔒</div>
              <p className="text-gray-600 dark:text-gray-400">{error}</p>
            </div>
          ) : profile ? (
            <div className="space-y-4">
              {/* Avatar and basic info */}
              <div className="flex items-start space-x-4">
                <div className="relative">
                  <div className="w-16 h-16 rounded-full overflow-hidden bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
                    {profile.profilePicture ? (
                      <img
                        src={profile.profilePicture}
                        alt={profile.name}
                        className="w-full h-full object-cover"
                      />
                    ) : (
                      <span className="text-white font-semibold text-xl">
                        {getInitials(profile.name)}
                      </span>
                    )}
                  </div>
                  
                  {/* Status indicator */}
                  <div className="absolute -bottom-1 -right-1">
                    <div
                      className={`w-4 h-4 rounded-full border-2 border-white ${
                        isOnline(profile.lastActive) ? 'bg-green-400' : getStatusColor(profile.availability)
                      }`}
                    />
                  </div>
                </div>
                
                <div className="flex-1 min-w-0">
                  <div className="flex items-center space-x-2">
                    <h4 className="font-semibold text-gray-900 dark:text-white truncate">
                      {profile.name}
                    </h4>
                    <span className="text-lg">{getRoleIcon(profile.role)}</span>
                  </div>
                  
                  <p className="text-sm text-gray-600 dark:text-gray-400">
                    {getRoleName(profile.role)}
                  </p>
                  
                  {profile.title && (
                    <p className="text-sm text-gray-500 dark:text-gray-500 truncate">
                      {profile.title}
                    </p>
                  )}
                  
                  <div className="flex items-center space-x-2 mt-1">
                    <div
                      className={`w-2 h-2 rounded-full ${
                        isOnline(profile.lastActive) ? 'bg-green-400' : getStatusColor(profile.availability)
                      }`}
                    />
                    <span className="text-xs text-gray-500 dark:text-gray-500">
                      {isOnline(profile.lastActive) 
                        ? 'Online' 
                        : profile.availability || formatLastActive(profile.lastActive)
                      }
                    </span>
                  </div>
                </div>
              </div>

              {/* Bio */}
              {profile.bio && (
                <div>
                  <h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">About</h5>
                  <p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed">
                    {profile.bio}
                  </p>
                </div>
              )}

              {/* Professional info */}
              {(profile.specialization || profile.yearsOfExperience || profile.education) && (
                <div>
                  <h5 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Professional</h5>
                  <div className="space-y-1">
                    {profile.specialization && (
                      <div className="flex items-center text-sm">
                        <span className="text-gray-500 dark:text-gray-400 w-20">Specialty:</span>
                        <span className="text-gray-700 dark:text-gray-300">{profile.specialization}</span>
                      </div>
                    )}
                    {profile.yearsOfExperience && (
                      <div className="flex items-center text-sm">
                        <span className="text-gray-500 dark:text-gray-400 w-20">Experience:</span>
                        <span className="text-gray-700 dark:text-gray-300">{profile.yearsOfExperience} years</span>
                      </div>
                    )}
                    {profile.officeLocation && (
                      <div className="flex items-center text-sm">
                        <span className="text-gray-500 dark:text-gray-400 w-20">Location:</span>
                        <span className="text-gray-700 dark:text-gray-300">{profile.officeLocation}</span>
                      </div>
                    )}
                  </div>
                </div>
              )}

              {/* Contact and links */}
              <div className="space-y-2">
                <div className="flex items-center space-x-2">
                  <button
                    onClick={copyEmail}
                    className="flex items-center space-x-2 text-sm text-blue-600 dark:text-blue-400 hover:underline"
                  >
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
                    </svg>
                    <span>{profile.email}</span>
                  </button>
                </div>

                <div className="flex items-center space-x-3">
                  {profile.linkedinUrl && (
                    <button
                      onClick={openLinkedIn}
                      className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
                      title="LinkedIn Profile"
                    >
                      <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
                        <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
                      </svg>
                    </button>
                  )}
                  
                  {profile.websiteUrl && (
                    <button
                      onClick={openWebsite}
                      className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300"
                      title="Website"
                    >
                      <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9v-9m0-9v9m0 9c-5 0-9-4-9-9s4-9 9-9" />
                      </svg>
                    </button>
                  )}
                </div>
              </div>

              {/* Actions */}
              {session?.user?.id !== profile.id && (
                <div className="pt-3 border-t border-gray-200 dark:border-gray-700">
                  <button
                    onClick={handleStartDirectMessage}
                    className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium flex items-center justify-center space-x-2"
                  >
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
                    </svg>
                    <span>Send Message</span>
                  </button>
                </div>
              )}
            </div>
          ) : (
            <div className="text-center py-8">
              <p className="text-gray-600 dark:text-gray-400">No profile data available</p>
            </div>
          )}
        </div>
      </motion.div>
    </AnimatePresence>
  );
};

export default ProfilePopover; 

CasperSecurity Mini