All files / src/components UserProfileDropdown.tsx

3.03% Statements 1/33
0% Branches 0/29
0% Functions 0/14
3.33% Lines 1/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151                      1x                                                                                                                                                                                                                                                                                      
import React, { useState, useRef, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { User, Settings, LogOut, ChevronDown } from 'lucide-react';
import { User as UserType } from '../types';
import { useLogout } from '../hooks/useAuth';
 
interface UserProfileDropdownProps {
  user: UserType;
  variant?: 'default' | 'light'; // 'light' for colored headers
}
 
const UserProfileDropdown: React.FC<UserProfileDropdownProps> = ({ user, variant = 'default' }) => {
  const [isOpen, setIsOpen] = useState(false);
  const dropdownRef = useRef<HTMLDivElement>(null);
  const { mutate: logout, isPending: isLoggingOut } = useLogout();
  const location = useLocation();
 
  // Determine the profile route based on current path
  const isPlatform = location.pathname.startsWith('/platform');
  const profilePath = isPlatform ? '/platform/profile' : '/profile';
 
  const isLight = variant === 'light';
 
  // Close dropdown when clicking outside
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
        setIsOpen(false);
      }
    };
 
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);
 
  // Close dropdown on escape key
  useEffect(() => {
    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setIsOpen(false);
      }
    };
 
    document.addEventListener('keydown', handleEscape);
    return () => document.removeEventListener('keydown', handleEscape);
  }, []);
 
  const handleSignOut = () => {
    logout();
  };
 
  // Get user initials for fallback avatar
  const getInitials = (name: string) => {
    return name
      .split(' ')
      .map(part => part[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
  };
 
  // Format role for display
  const formatRole = (role: string) => {
    return role.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
  };
 
  return (
    <div className="relative" ref={dropdownRef}>
      <button
        onClick={() => setIsOpen(!isOpen)}
        className={`flex items-center gap-3 pl-6 border-l hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg ${
          isLight
            ? 'border-white/20 focus:ring-white/50'
            : 'border-gray-200 dark:border-gray-700 focus:ring-brand-500'
        }`}
        aria-expanded={isOpen}
        aria-haspopup="true"
      >
        <div className="text-right hidden sm:block">
          <p className={`text-sm font-medium ${isLight ? 'text-white' : 'text-gray-900 dark:text-white'}`}>
            {user.name}
          </p>
          <p className={`text-xs ${isLight ? 'text-white/70' : 'text-gray-500 dark:text-gray-400'}`}>
            {formatRole(user.role)}
          </p>
        </div>
        {user.avatarUrl ? (
          <img
            src={user.avatarUrl}
            alt={user.name}
            className={`w-10 h-10 rounded-full object-cover ${
              isLight ? 'border-2 border-white/30' : 'border border-gray-200 dark:border-gray-600'
            }`}
          />
        ) : (
          <div className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium ${
            isLight
              ? 'border-2 border-white/30 bg-white/20 text-white'
              : 'border border-gray-200 dark:border-gray-600 bg-brand-500 text-white'
          }`}>
            {getInitials(user.name)}
          </div>
        )}
        <ChevronDown
          size={16}
          className={`transition-transform duration-200 ${isOpen ? 'rotate-180' : ''} ${
            isLight ? 'text-white/70' : 'text-gray-400'
          }`}
        />
      </button>
 
      {/* Dropdown Menu */}
      {isOpen && (
        <div className="absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
          {/* User Info Header */}
          <div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
            <p className="text-sm font-medium text-gray-900 dark:text-white truncate">{user.name}</p>
            <p className="text-xs text-gray-500 dark:text-gray-400 truncate">{user.email}</p>
          </div>
 
          {/* Menu Items */}
          <div className="py-1">
            <Link
              to={profilePath}
              onClick={() => setIsOpen(false)}
              className="flex items-center gap-3 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
            >
              <Settings size={16} className="text-gray-400" />
              Profile Settings
            </Link>
          </div>
 
          {/* Sign Out */}
          <div className="border-t border-gray-200 dark:border-gray-700 py-1">
            <button
              onClick={handleSignOut}
              disabled={isLoggingOut}
              className="flex items-center gap-3 w-full px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors disabled:opacity-50"
            >
              <LogOut size={16} />
              {isLoggingOut ? 'Signing out...' : 'Sign Out'}
            </button>
          </div>
        </div>
      )}
    </div>
  );
};
 
export default UserProfileDropdown;