this repo has no description
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}