Skip to content

Commit 1f3fc0e

Browse files
committed
Introduce efp
1 parent 513cd0a commit 1f3fc0e

File tree

4 files changed

+348
-437
lines changed

4 files changed

+348
-437
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { Link } from '@tanstack/react-router';
2+
import { LuMapPin, LuMail, LuGlobe, LuTwitter, LuGithub, LuMessageSquare, LuSend } from "react-icons/lu";
3+
import { ChainIcon } from './ChainIcon';
4+
import { getChainIconUrl } from '../utils/chainIcons';
5+
import { useFollowers } from '../hooks/useFollowers';
6+
7+
interface Profile {
8+
name: string;
9+
display: string;
10+
address?: string;
11+
avatar?: string;
12+
header?: string;
13+
records?: {
14+
header?: string;
15+
avatar?: string;
16+
description?: string;
17+
location?: string;
18+
email?: string;
19+
url?: string;
20+
'com.twitter'?: string;
21+
'com.github'?: string;
22+
'com.discord'?: string;
23+
'org.telegram'?: string;
24+
};
25+
chains?: Record<string, string>;
26+
}
27+
28+
interface SearchResultProps {
29+
profile: Profile;
30+
}
31+
32+
export function SearchResult({ profile }: SearchResultProps) {
33+
const { data: followersData } = useFollowers(profile.name);
34+
35+
return (
36+
<div className="bg-white rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-all duration-200">
37+
<Link
38+
to="/$profileId"
39+
params={{ profileId: profile.name }}
40+
className="block h-full"
41+
>
42+
<div className="relative h-full flex flex-col">
43+
{/* Header/Banner image */}
44+
{profile.header || profile.records?.header ? (
45+
<div className="w-full aspect-[3/1] overflow-hidden">
46+
<img
47+
src={profile.header || profile.records?.header}
48+
alt={`${profile.display} banner`}
49+
className="w-full h-full object-cover"
50+
/>
51+
</div>
52+
) : (
53+
<div className="w-full aspect-[3/1] bg-gradient-to-r from-blue-500 to-purple-600"></div>
54+
)}
55+
56+
{/* Profile information with avatar */}
57+
<div className="p-2 flex-1">
58+
<div className="flex items-start space-x-2 pr-2 pb-1 h-full">
59+
{/* Avatar */}
60+
<div className={`${profile.header || profile.records?.header ? '-mt-7' : ''} flex-shrink-0`}>
61+
{profile.avatar ? (
62+
<img
63+
src={profile.avatar}
64+
alt={profile.display}
65+
className="h-14 w-14 rounded-full border-2 border-white shadow-md object-cover"
66+
/>
67+
) : (
68+
<div className="h-14 w-14 rounded-full bg-gray-200 flex items-center justify-center text-gray-500 text-xl font-bold">
69+
{profile.display.charAt(0).toUpperCase()}
70+
</div>
71+
)}
72+
</div>
73+
74+
{/* Profile details */}
75+
<div className="flex-1 min-w-0 h-full flex flex-col">
76+
<h3 className="text-base font-semibold text-blue-600 truncate">
77+
{profile.display}
78+
</h3>
79+
{profile.address && (
80+
<p className="text-xs text-gray-500 truncate">
81+
{profile.address}
82+
</p>
83+
)}
84+
<p className="mt-1 text-xs text-gray-600 whitespace-pre-line line-clamp-2">
85+
{profile.records?.description || ''}
86+
</p>
87+
88+
{/* Chain addresses */}
89+
{profile.chains && Object.keys(profile.chains).length > 0 && (
90+
<div className="mt-1.5 flex flex-wrap gap-x-2 gap-y-1">
91+
{Object.entries(profile.chains).map(([chain, address]) => (
92+
<div key={chain} className="flex items-center text-xs text-gray-500" title={`${chain.toUpperCase()}: ${address}`}>
93+
<ChainIcon
94+
chain={chain}
95+
iconUrl={getChainIconUrl(chain)}
96+
className="mr-1"
97+
/>
98+
<span className="truncate max-w-[100px]">{address}</span>
99+
</div>
100+
))}
101+
</div>
102+
)}
103+
104+
{/* Profile metadata */}
105+
<div className="mt-1.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-gray-500">
106+
{profile.records?.location && (
107+
<div className="flex items-center">
108+
<LuMapPin className="mr-1 h-4 w-4" />
109+
<span>{profile.records.location}</span>
110+
</div>
111+
)}
112+
{profile.records?.email && (
113+
<div className="flex items-center">
114+
<LuMail className="mr-1 h-4 w-4" />
115+
<span>{profile.records.email}</span>
116+
</div>
117+
)}
118+
{profile.records?.url && (
119+
<div className="flex items-center">
120+
<LuGlobe className="mr-1 h-4 w-4" />
121+
<span>{profile.records.url}</span>
122+
</div>
123+
)}
124+
</div>
125+
126+
{/* Social links and followers */}
127+
<div className="mt-1.5 flex justify-between items-center flex-1">
128+
<div className="flex space-x-2">
129+
{profile.records?.['com.twitter'] && (
130+
<div className="text-blue-400 hover:text-blue-600">
131+
<LuTwitter className="h-4 w-4" title={`Twitter: ${profile.records['com.twitter']}`} />
132+
</div>
133+
)}
134+
{profile.records?.['com.github'] && (
135+
<div className="text-gray-700 hover:text-gray-900">
136+
<LuGithub className="h-4 w-4" title={`GitHub: ${profile.records['com.github']}`} />
137+
</div>
138+
)}
139+
{profile.records?.['com.discord'] && (
140+
<div className="text-indigo-500 hover:text-indigo-700">
141+
<LuMessageSquare className="h-4 w-4" title={`Discord: ${profile.records['com.discord']}`} />
142+
</div>
143+
)}
144+
{profile.records?.['org.telegram'] && (
145+
<div className="text-blue-500 hover:text-blue-700">
146+
<LuSend className="h-4 w-4" title={`Telegram: ${profile.records['org.telegram']}`} />
147+
</div>
148+
)}
149+
</div>
150+
151+
{/* Followers data */}
152+
{followersData && (
153+
<div className="flex gap-3 items-center self-end" onClick={(e) => e.stopPropagation()}>
154+
<a href={`https://ethfollow.xyz/${profile.name}?tab=following`} target="_blank" rel="noopener noreferrer" className="text-xs text-gray-500 flex gap-1 items-center hover:underline cursor-pointer">
155+
<span className="font-bold">{followersData.following_count}</span>
156+
<span className="text-gray-500">Following</span>
157+
</a>
158+
<a href={`https://ethfollow.xyz/${profile.name}?tab=followers`} target="_blank" rel="noopener noreferrer" className="text-xs text-gray-500 flex gap-1 items-center hover:underline cursor-pointer">
159+
<span className="font-bold">{followersData.followers_count}</span>
160+
<span className="text-gray-500">Followers</span>
161+
</a>
162+
</div>
163+
)}
164+
</div>
165+
</div>
166+
</div>
167+
</div>
168+
</div>
169+
</Link>
170+
</div>
171+
);
172+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import axios from 'axios';
3+
4+
export interface FollowerData {
5+
followers_count: string;
6+
following_count: string;
7+
}
8+
9+
export const useFollowers = (searchTerm: string) => {
10+
return useQuery({
11+
queryKey: ['followers', searchTerm],
12+
queryFn: async () => {
13+
if (!searchTerm.trim()) {
14+
return undefined;
15+
}
16+
17+
const apiUrl = `https://api.ethfollow.xyz/api/v1/users/${searchTerm}/stats`;
18+
const response = await axios.get<FollowerData>(apiUrl
19+
);
20+
21+
return response.data;
22+
},
23+
enabled: Boolean(searchTerm.trim()),
24+
staleTime: 1000 * 60 * 5, // 5 minutes
25+
});
26+
};

0 commit comments

Comments
 (0)