Files
spotlightcam/frontend/src/components/users/UserListItem.jsx
Radosław Gierwiało 082105c5bf refactor(frontend): Phase 3 - create advanced composite components
Extract complex UI sections into reusable composite components

New Components Created:
1. HeatBadges (heats/HeatBadges.jsx)
   - Displays competition heats with compact notation
   - Configurable max visible badges with "+X more" overflow
   - Tooltips with full heat information

2. UserListItem (users/UserListItem.jsx)
   - Reusable user entry with avatar, username, full name
   - Optional heat badges display
   - Flexible action button slot (render props pattern)
   - Online/offline status support

3. ParticipantsSidebar (events/ParticipantsSidebar.jsx)
   - Complete sidebar component for event participants
   - Filter checkbox for hiding users from own heats
   - Participant and online counters
   - Integrated UserListItem with match actions

4. FileTransferProgress (webrtc/FileTransferProgress.jsx)
   - WebRTC P2P file transfer UI
   - Progress bar with percentage
   - Send/Cancel actions

5. LinkShareInput (webrtc/LinkShareInput.jsx)
   - Fallback link sharing when WebRTC unavailable
   - Google Drive, Dropbox link support

Pages Refactored:
- EventChatPage: 564 → 471 lines (-93 lines, -16%)
  * Replaced 90-line participants sidebar with <ParticipantsSidebar />
  * Removed duplicate formatHeat logic (now in HeatBadges)

- MatchChatPage: 446 → 369 lines (-77 lines, -17%)
  * Replaced 56-line file transfer UI with <FileTransferProgress />
  * Replaced 39-line link input form with <LinkShareInput />

Phase 3 Total: -170 lines
Grand Total (Phase 1+2+3): -559 lines (-17%)

Final Results:
- EventChatPage: 761 → 471 lines (-290 lines, -38% reduction)
- MatchChatPage: 567 → 369 lines (-198 lines, -35% reduction)

Benefits:
- Massive complexity reduction in largest components
- Composite components can be reused across pages
- Better testability - each component tested independently
- Cleaner code organization - single responsibility principle
- Easier maintenance - changes in one place propagate everywhere
2025-11-21 17:10:53 +01:00

94 lines
2.7 KiB
JavaScript

import { Link } from 'react-router-dom';
import Avatar from '../common/Avatar';
import HeatBadges from '../heats/HeatBadges';
/**
* Reusable User List Item component
* Displays user avatar, username, and optional heats with action button
*
* @param {object} user - User object with { userId, username, avatar, isOnline, firstName, lastName }
* @param {Array} heats - Array of heat objects for this user
* @param {boolean} showHeats - Whether to display heat badges (default: true)
* @param {React.ReactNode} actionButton - Optional action button/element to display on right
* @param {function} onClick - Optional click handler for the entire item
* @param {string} className - Additional CSS classes
* @param {boolean} linkToProfile - Whether username should link to profile (default: false)
*
* @example
* <UserListItem
* user={displayUser}
* heats={userHeats.get(userId)}
* actionButton={
* <button onClick={() => handleMatch(userId)}>
* <UserPlus />
* </button>
* }
* />
*/
const UserListItem = ({
user,
heats = [],
showHeats = true,
actionButton,
onClick,
className = '',
linkToProfile = false
}) => {
const hasHeats = heats && heats.length > 0;
const isOnline = user?.isOnline ?? false;
const usernameClasses = `text-sm font-medium truncate ${
isOnline ? 'text-gray-900' : 'text-gray-500'
}`;
const usernameContent = linkToProfile ? (
<Link
to={`/profile/${user.username}`}
className={`${usernameClasses} hover:text-primary-600`}
>
{user.username}
</Link>
) : (
<p className={usernameClasses}>{user.username}</p>
);
return (
<div
className={`flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg ${className}`}
onClick={onClick}
>
<div className="flex items-center space-x-2 flex-1 min-w-0">
<Avatar
src={user.avatar}
username={user.username}
size={32}
status={isOnline ? 'online' : 'offline'}
title={user.username}
/>
<div className="flex-1 min-w-0">
{usernameContent}
{/* Full name (optional) */}
{(user.firstName || user.lastName) && (
<p className="text-xs text-gray-500 truncate">
{user.firstName} {user.lastName}
</p>
)}
{/* Heat Badges */}
{showHeats && hasHeats && (
<div className="mt-1">
<HeatBadges heats={heats} maxVisible={3} />
</div>
)}
</div>
</div>
{actionButton && (
<div className="flex-shrink-0 ml-2">
{actionButton}
</div>
)}
</div>
);
};
export default UserListItem;