this repo has no description
at main 132 lines 5.6 kB view raw
1'use client'; 2 3import {useState} from 'react'; 4import Image from 'next/image'; 5import {UserProfile} from '@/lib/atproto'; 6import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/24/solid'; 7 8interface UserListProps { 9 users: UserProfile[]; 10} 11 12export function UserList({users}: UserListProps) { 13 const [isExpanded, setIsExpanded] = useState(false); 14 const INITIAL_COUNT = 8; 15 const displayedUsers = isExpanded ? users : users.slice(0, INITIAL_COUNT); 16 const hasMore = users.length > INITIAL_COUNT; 17 18 const colors = [ 19 'bg-red-400', 20 'bg-blue-400', 21 'bg-yellow-400', 22 'bg-green-400', 23 'bg-purple-400', 24 'bg-pink-400', 25 'bg-orange-400', 26 'bg-cyan-400', 27 ]; 28 29 return ( 30 <div className="space-y-6"> 31 <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> 32 {displayedUsers.map((user, index) => { 33 const isInvalidHandle = user.handle.endsWith('.invalid') || user.handle === user.did; 34 const bskyAppUrlRaw = process.env.NEXT_PUBLIC_BSKY_APP_URL || 'https://bsky.app'; 35 const bskyAppUrl = bskyAppUrlRaw.startsWith('http') 36 ? bskyAppUrlRaw 37 : `https://${bskyAppUrlRaw}`; 38 const profileUrl = isInvalidHandle 39 ? `${bskyAppUrl}/profile/${user.did}` 40 : `${bskyAppUrl}/profile/${user.handle}`; 41 const avatarInitial = isInvalidHandle ? '⚠️' : user.handle[0]?.toUpperCase() || '?'; 42 43 return ( 44 <a 45 key={user.did} 46 href={profileUrl} 47 target="_blank" 48 rel="noopener noreferrer" 49 className="comic-panel interactive-panel p-4 flex flex-col h-full" 50 > 51 <div className="flex items-center space-x-3"> 52 {user.avatar ? ( 53 <Image 54 src={user.avatar} 55 alt={isInvalidHandle ? 'Invalid Handle' : user.handle} 56 width={48} 57 height={48} 58 className="w-12 h-12 rounded-full border-3 border-black" 59 style={{border: '3px solid #000'}} 60 unoptimized={!user.avatar.includes('cdn.bsky.app')} 61 /> 62 ) : ( 63 <div 64 className={`w-12 h-12 rounded-full flex items-center justify-center text-white font-bold border-3 border-black ${colors[index % colors.length]}`} 65 style={{border: '3px solid #000'}} 66 > 67 <span className="text-xl">{avatarInitial}</span> 68 </div> 69 )} 70 <div className="overflow-hidden flex-1"> 71 <p className="text-sm font-bold text-black truncate flex items-center gap-1"> 72 <span>{user.displayName || user.handle}</span> 73 {!isInvalidHandle && 74 user.verified && 75 (user.trustedVerifier ? ( 76 <svg className="w-3.5 h-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24"> 77 <path 78 fill="#1185FE" 79 d="M8.792 1.615a4.154 4.154 0 0 1 6.416 0 4.15 4.15 0 0 0 3.146 1.515 4.154 4.154 0 0 1 4 5.017 4.15 4.15 0 0 0 .777 3.404 4.154 4.154 0 0 1-1.427 6.255 4.15 4.15 0 0 0-2.177 2.73 4.154 4.154 0 0 1-5.781 2.784 4.15 4.15 0 0 0-3.492 0 4.154 4.154 0 0 1-5.78-2.784 4.15 4.15 0 0 0-2.178-2.73A4.154 4.154 0 0 1 .87 11.551a4.15 4.15 0 0 0 .776-3.404 4.154 4.154 0 0 1 4-5.017 4.15 4.15 0 0 0 3.146-1.515Z" 80 /> 81 <path 82 fill="#fff" 83 fillRule="evenodd" 84 d="M17.861 8.26a1.44 1.44 0 0 1 0 2.033l-6.571 6.571a1.437 1.437 0 0 1-2.033 0L5.97 13.58a1.438 1.438 0 0 1 2.033-2.033l2.27 2.269 5.554-5.555a1.437 1.437 0 0 1 2.033 0Z" 85 clipRule="evenodd" 86 /> 87 </svg> 88 ) : ( 89 <svg className="w-3.5 h-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24"> 90 <circle cx="12" cy="12" r="11.5" fill="#1183FE" /> 91 <path 92 fill="#fff" 93 fillRule="evenodd" 94 d="M17.659 8.175a1.36 1.36 0 0 1 0 1.925l-6.224 6.223a1.36 1.36 0 0 1-1.925 0L6.4 13.212a1.361 1.361 0 0 1 1.925-1.925l2.149 2.148 5.26-5.26a1.36 1.36 0 0 1 1.925 0Z" 95 clipRule="evenodd" 96 /> 97 </svg> 98 ))} 99 </p> 100 <p className="text-xs text-zinc-600 truncate"> 101 {isInvalidHandle ? '⚠️ Invalid Handle' : `@${user.handle}`} 102 </p> 103 </div> 104 </div> 105 </a> 106 ); 107 })} 108 </div> 109 110 {hasMore && ( 111 <div className="flex justify-center"> 112 <button 113 onClick={() => setIsExpanded(!isExpanded)} 114 className="comic-button bg-white px-6 py-2 flex items-center gap-2 hover:bg-zinc-50" 115 > 116 {isExpanded ? ( 117 <> 118 <span>Show Less</span> 119 <ChevronUpIcon className="w-5 h-5" /> 120 </> 121 ) : ( 122 <> 123 <span>Show All Users ({users.length})</span> 124 <ChevronDownIcon className="w-5 h-5" /> 125 </> 126 )} 127 </button> 128 </div> 129 )} 130 </div> 131 ); 132}