···
25
25
const [entries, setEntries] = useState<FlushingEntry[]>([]);
26
26
const [totalCount, setTotalCount] = useState<number>(0);
27
27
const [loading, setLoading] = useState(true);
28
28
+
const [profileLoading, setProfileLoading] = useState(true);
28
29
const [error, setError] = useState<string | null>(null);
30
30
+
const [profileError, setProfileError] = useState<string | null>(null);
29
31
const [flushesPerDay, setFlushesPerDay] = useState<number>(0);
30
32
const [chartData, setChartData] = useState<{date: string, count: number}[]>([]);
33
33
+
// Match Bluesky's API response format
31
34
interface ProfileData {
32
32
-
did?: string;
33
33
-
handle?: string;
35
35
+
did: string;
36
36
+
handle: string;
34
37
displayName?: string;
35
38
description?: string;
39
39
+
avatar?: string;
40
40
+
banner?: string;
41
41
+
followersCount?: number;
42
42
+
followsCount?: number;
43
43
+
postsCount?: number;
44
44
+
indexedAt?: string;
45
45
+
viewer?: any;
36
46
}
37
47
const [profileData, setProfileData] = useState<ProfileData | null>(null);
38
48
39
49
useEffect(() => {
40
40
-
// Fetch the user's statuses when the component mounts
50
50
+
// Fetch the user's statuses and profile data when the component mounts
41
51
fetchUserStatuses();
52
52
+
fetchProfileData();
42
53
}, [handle]);
54
54
+
55
55
+
// Function to fetch the user's profile data directly
56
56
+
const fetchProfileData = async () => {
57
57
+
try {
58
58
+
setProfileLoading(true);
59
59
+
setProfileError(null);
60
60
+
61
61
+
// Fetch profile data directly from Bluesky API
62
62
+
const profileResponse = await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(handle)}`);
63
63
+
64
64
+
if (profileResponse.ok) {
65
65
+
const profileData = await profileResponse.json();
66
66
+
setProfileData(profileData);
67
67
+
console.log("Fetched profile data:", profileData);
68
68
+
} else {
69
69
+
const errorText = await profileResponse.text();
70
70
+
console.warn(`Failed to fetch profile data: ${profileResponse.statusText}`, errorText);
71
71
+
setProfileError(`Failed to fetch profile: ${profileResponse.status}`);
72
72
+
}
73
73
+
} catch (error: any) {
74
74
+
console.error("Error fetching profile:", error);
75
75
+
setProfileError(error.message || "Failed to fetch profile data");
76
76
+
} finally {
77
77
+
setProfileLoading(false);
78
78
+
}
79
79
+
};
43
80
44
81
// Function to fetch the user's statuses
45
82
const fetchUserStatuses = async () => {
···
65
102
setEntries(userEntries);
66
103
setTotalCount(data.count || 0);
67
104
68
68
-
// Set profile data if available
69
69
-
if (data.profile) {
70
70
-
setProfileData(data.profile);
71
71
-
}
105
105
+
// We now fetch profile data separately
72
106
73
107
// Calculate statistics and chart data
74
108
if (userEntries.length > 0) {
···
125
159
126
160
<div className={styles.profileHeader}>
127
161
<div className={styles.profileInfo}>
128
128
-
{profileData?.displayName ? (
162
162
+
{profileLoading ? (
163
163
+
<div className={styles.profileLoading}>
164
164
+
<h2 className={`${styles.profileTitle} font-bold`}>@{handle}</h2>
165
165
+
<div className={styles.smallLoader}></div>
166
166
+
</div>
167
167
+
) : profileError ? (
168
168
+
<div>
169
169
+
<h2 className={`${styles.profileTitle} font-bold`}>@{handle}</h2>
170
170
+
<p className={styles.smallError}>Unable to load profile details</p>
171
171
+
</div>
172
172
+
) : (
129
173
<>
130
130
-
<h2 className={`${styles.profileTitle} font-bold`}>{profileData.displayName}</h2>
131
131
-
<h3 className={`${styles.profileHandle} font-medium`}>@{handle}</h3>
174
174
+
{profileData?.displayName ? (
175
175
+
<>
176
176
+
<h2 className={`${styles.profileTitle} font-bold`}>{profileData.displayName}</h2>
177
177
+
<h3 className={`${styles.profileHandle} font-medium`}>@{handle}</h3>
178
178
+
</>
179
179
+
) : (
180
180
+
<h2 className={`${styles.profileTitle} font-bold`}>@{handle}</h2>
181
181
+
)}
182
182
+
183
183
+
{profileData?.description && (
184
184
+
<p className={`${styles.description} font-regular`}>{profileData.description}</p>
185
185
+
)}
132
186
</>
133
133
-
) : (
134
134
-
<h2 className={`${styles.profileTitle} font-bold`}>@{handle}</h2>
135
135
-
)}
136
136
-
137
137
-
{profileData?.description && (
138
138
-
<p className={`${styles.description} font-regular`}>{profileData.description}</p>
139
187
)}
140
188
141
189
<a
···
263
263
margin-bottom: 1rem;
264
264
}
265
265
266
266
+
.smallLoader {
267
267
+
border: 3px solid var(--input-background);
268
268
+
border-top: 3px solid var(--primary-color);
269
269
+
border-radius: 50%;
270
270
+
width: 18px;
271
271
+
height: 18px;
272
272
+
animation: spin 1s linear infinite;
273
273
+
margin-top: 0.5rem;
274
274
+
display: inline-block;
275
275
+
}
276
276
+
277
277
+
.profileLoading {
278
278
+
display: flex;
279
279
+
flex-direction: column;
280
280
+
align-items: flex-start;
281
281
+
gap: 0.5rem;
282
282
+
}
283
283
+
284
284
+
.smallError {
285
285
+
color: var(--timestamp-color);
286
286
+
font-size: 0.9rem;
287
287
+
font-style: italic;
288
288
+
margin-top: 0.3rem;
289
289
+
}
290
290
+
266
291
@keyframes spin {
267
292
0% { transform: rotate(0deg); }
268
293
100% { transform: rotate(360deg); }