/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import React, { useState, useEffect, useCallback, memo, useMemo, useRef } from 'react'; import { useAtom } from 'jotai'; import { cn } from './utils'; import { ChatUser } from './types'; import Image from 'next/image'; import { Search, Filter, Plus } from 'lucide-react'; import { isMobileAtom, selectedUserAtom, chatDetailOpenAtom } from './store'; // Mock data for chat users const mockChatUsers: ChatUser[] = [ { id: '1', name: 'Sam Ahmad', lastMessage: 'Hello, how are you?', timestamp: '10:30 AM', unread: 2, }, { id: '2', name: 'John Doe', lastMessage: 'When is the meeting?', timestamp: 'Yesterday', }, { id: '3', name: 'Sarah Johnson', lastMessage: 'Thanks for the update!', timestamp: 'Monday', }, { id: '4', name: 'Alex Williams', lastMessage: 'Check the latest report', timestamp: 'Jun 15', unread: 1, }, { id: '5', name: 'Emily Davis', lastMessage: 'I appreciate your help', timestamp: 'Jun 10', }, ]; // Status indicator component const StatusIndicator = memo(({ status }: { status?: 'online' | 'away' | 'offline' }) => { if (!status || status === 'offline') return null; return ( ); }); StatusIndicator.displayName = 'StatusIndicator'; // Individual chat user item - memoized to prevent unnecessary renders const ChatUserItem = memo(({ user, isMobile, onSelect, isActive = false }: { user: ChatUser; isMobile: boolean; onSelect: (user: ChatUser) => void; isActive?: boolean; }) => { const handleClick = useCallback(() => { onSelect(user); }, [user, onSelect]); return (
{user.avatar ? (
{user.name}
) : ( {user.name.charAt(0)} )}
{user.unread && (
{user.unread}
)}

{user.name}

{user.timestamp}

{user.lastMessage}

); }); ChatUserItem.displayName = 'ChatUserItem'; interface ChatUserListProps { onSelectUser: (user: ChatUser) => void; } // Generate more users for pagination demonstration const generateMoreUsers = (startIndex: number, count: number): ChatUser[] => { return Array.from({ length: count }, (_, i) => { const index = startIndex + i; return { id: `generated-${index}`, name: `User ${index}`, lastMessage: `This is message ${index} from generated user`, timestamp: i === 0 ? 'Now' : i < 5 ? 'Today' : `${i} days ago`, unread: i % 5 === 0 ? Math.floor(Math.random() * 5) + 1 : undefined }; }); }; const ChatUserList: React.FC = ({ onSelectUser }) => { const [isMobile, setIsMobile] = useAtom(isMobileAtom); const [selectedUser, setSelectedUser] = useAtom(selectedUserAtom); const [, setChatDetailOpen] = useAtom(chatDetailOpenAtom); const [users, setUsers] = useState(mockChatUsers); const [loading, setLoading] = useState(false); const [, setPage] = useState(1); const [searchTerm, setSearchTerm] = useState(''); const [showFilters, setShowFilters] = useState(false); const observerRef = useRef(null); const loadMoreRef = useRef(null); // Detect mobile screen size with debounce useEffect(() => { let timeoutId: NodeJS.Timeout; const handleResize = () => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { setIsMobile(typeof window !== 'undefined' && window.innerWidth < 768); }, 100); }; // Initial check handleResize(); // Add event listener with passive option for better performance if (typeof window !== 'undefined') { window.addEventListener('resize', handleResize, { passive: true }); } // Clean up on unmount return () => { if (typeof window !== 'undefined') { window.removeEventListener('resize', handleResize); } clearTimeout(timeoutId); }; }, [setIsMobile]); // Filter users based on search term const filteredUsers = useMemo(() => { if (!searchTerm.trim()) return users; const normalizedSearchTerm = searchTerm.toLowerCase(); return users.filter(user => user.name.toLowerCase().includes(normalizedSearchTerm) || user.lastMessage.toLowerCase().includes(normalizedSearchTerm) ); }, [users, searchTerm]); // Function to load more users const loadMoreUsers = useCallback(() => { if (loading || users.length >= 100) return; setLoading(true); // Simulate API call with delay setTimeout(() => { const newUsers = generateMoreUsers(users.length, 10); setUsers(prev => [...prev, ...newUsers]); setPage(prev => prev + 1); setLoading(false); }, 500); }, [loading, users.length]); // Handle scrolling to load more content const handleScroll = useCallback((e: React.UIEvent) => { const target = e.currentTarget as HTMLDivElement; const bottom = target.scrollHeight - target.scrollTop <= target.clientHeight + 100; if (bottom && !loading && users.length < 100) { loadMoreUsers(); } }, [loading, users.length, loadMoreUsers]); // Setup IntersectionObserver for infinite scrolling useEffect(() => { // Early return if loading or browser doesn't support IntersectionObserver if (loading || typeof window === 'undefined' || typeof window.IntersectionObserver === 'undefined') return; const options = { root: null, rootMargin: '0px', threshold: 0.1 }; const handleObserver = (entries: any[]) => { const target = entries[0]; if (target.isIntersecting && !loading && users.length < 100) { loadMoreUsers(); } }; // Cleanup previous observer if (observerRef.current) { observerRef.current.disconnect(); } // Create new observer observerRef.current = new window.IntersectionObserver(handleObserver, options); // Start observing if (loadMoreRef.current) { observerRef.current.observe(loadMoreRef.current); } // Cleanup on unmount or deps change return () => { if (observerRef.current) { observerRef.current.disconnect(); } }; }, [loading, users.length, loadMoreUsers]); // Optimized handler to select a user const handleSelectUser = useCallback((user: ChatUser) => { setSelectedUser(user); setChatDetailOpen(true); onSelectUser(user); }, [setSelectedUser, setChatDetailOpen, onSelectUser]); // Handle search input changes const handleSearchChange = useCallback((e: React.ChangeEvent) => { setSearchTerm(e.target.value); }, []); return (

Messages

{isMobile && ( )}
{showFilters && (
)}
{filteredUsers.length > 0 ? ( filteredUsers.map(user => ( )) ) : (

No messages found matching your search.

)}
{loading && (
Loading more...
)}
); }; export default memo(ChatUserList);