/* 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.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.
)}
);
};
export default memo(ChatUserList);