···
45
45
46
46
// Step 1: Get the user's DID from their handle
47
47
let did = handle;
48
48
+
let userProfile = null;
49
49
+
48
50
// If the handle doesn't look like a DID, resolve it
49
51
if (!handle.startsWith('did:')) {
50
52
try {
···
59
61
60
62
const resolveData = await resolveResponse.json();
61
63
did = resolveData.did;
64
64
+
65
65
+
// Step 1.5: Get user profile data including description
66
66
+
try {
67
67
+
const profileResponse = await fetch(`https://bsky.social/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`);
68
68
+
69
69
+
if (profileResponse.ok) {
70
70
+
userProfile = await profileResponse.json();
71
71
+
console.log(`Fetched profile data for ${handle}: ${userProfile.description ? 'Has description' : 'No description'}`);
72
72
+
} else {
73
73
+
console.warn(`Failed to fetch profile data: ${profileResponse.statusText}`);
74
74
+
}
75
75
+
} catch (profileError: any) {
76
76
+
console.warn(`Error fetching profile data: ${profileError.message}`);
77
77
+
}
62
78
} catch (error: any) {
63
79
return NextResponse.json(
64
80
{ error: `Failed to resolve handle: ${error.message}` },
···
148
164
return NextResponse.json({
149
165
entries: transformedEntries,
150
166
count: transformedEntries.length,
151
151
-
cursor: fallbackData.cursor
167
167
+
cursor: fallbackData.cursor,
168
168
+
profile: userProfile
152
169
});
153
170
}
154
171
···
185
202
return NextResponse.json({
186
203
entries: transformedEntries,
187
204
count: transformedEntries.length,
188
188
-
cursor: recordsData.cursor
205
205
+
cursor: recordsData.cursor,
206
206
+
profile: userProfile
189
207
});
190
208
} catch (error: any) {
191
209
console.error('Error fetching records:', error);
···
227
245
228
246
return NextResponse.json({
229
247
entries: filteredEntries,
230
230
-
count: filteredEntries.length // Update count to reflect filtered entries
248
248
+
count: filteredEntries.length, // Update count to reflect filtered entries
249
249
+
profile: userProfile
231
250
});
232
251
}
233
252
···
28
28
const [error, setError] = useState<string | null>(null);
29
29
const [flushesPerDay, setFlushesPerDay] = useState<number>(0);
30
30
const [chartData, setChartData] = useState<{date: string, count: number}[]>([]);
31
31
+
interface ProfileData {
32
32
+
did?: string;
33
33
+
handle?: string;
34
34
+
displayName?: string;
35
35
+
description?: string;
36
36
+
}
37
37
+
const [profileData, setProfileData] = useState<ProfileData | null>(null);
31
38
32
39
useEffect(() => {
33
40
// Fetch the user's statuses when the component mounts
···
57
64
const userEntries = data.entries || [];
58
65
setEntries(userEntries);
59
66
setTotalCount(data.count || 0);
67
67
+
68
68
+
// Set profile data if available
69
69
+
if (data.profile) {
70
70
+
setProfileData(data.profile);
71
71
+
}
60
72
61
73
// Calculate statistics and chart data
62
74
if (userEntries.length > 0) {
···
110
122
111
123
return (
112
124
<div className={styles.container}>
113
113
-
<header className={styles.header}>
114
114
-
<div className={styles.headerContent}>
115
115
-
<h1 className={styles.title}>Flushes 🧻</h1>
116
116
-
<p className={styles.subtitle}>https://flushes.app 🚽</p>
117
117
-
<p className={styles.description}>
118
118
-
The world's first decentralized social media app for sharing when you're on the toilet. Connect with other bathroom enjoyers all over the world by posting "flushes"! Powered by the AT Protocol. Your status updates are saved to your PDS with the im.flushing lexicon.
119
119
-
</p>
120
120
-
</div>
121
121
-
<div className={styles.headerActions}>
122
122
-
<Link href="/" className={styles.backButton}>
123
123
-
← Back to Feed
124
124
-
</Link>
125
125
-
</div>
126
126
-
</header>
127
125
128
126
<div className={styles.profileHeader}>
129
127
<div className={styles.profileInfo}>
130
130
-
<h2 className={styles.profileTitle}>@{handle}</h2>
128
128
+
{profileData?.displayName ? (
129
129
+
<>
130
130
+
<h2 className={`${styles.profileTitle} font-bold`}>{profileData.displayName}</h2>
131
131
+
<h3 className={`${styles.profileHandle} font-medium`}>@{handle}</h3>
132
132
+
</>
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
139
+
)}
140
140
+
131
141
<a
132
142
href={`https://bsky.app/profile/${handle}`}
133
143
target="_blank"
···
34
34
}
35
35
36
36
.description {
37
37
-
font-size: 1.1rem;
37
37
+
font-size: 1rem;
38
38
color: var(--text-color);
39
39
margin: 0;
40
40
line-height: 1.5;
41
41
word-wrap: break-word;
42
42
+
white-space: pre-wrap;
43
43
+
overflow-wrap: break-word;
42
44
}
43
45
44
46
.profileHeader {
···
50
52
.profileInfo {
51
53
display: flex;
52
54
flex-direction: column;
53
53
-
gap: 0.5rem;
55
55
+
gap: 0.8rem;
54
56
}
55
57
56
58
.profileTitle {
57
59
font-size: 1.8rem;
58
60
margin: 0;
61
61
+
color: var(--title-color);
62
62
+
line-height: 1.2;
63
63
+
}
64
64
+
65
65
+
.profileHandle {
66
66
+
font-size: 1.1rem;
67
67
+
margin: -0.4rem 0 0 0;
59
68
color: var(--primary-color);
69
69
+
font-weight: 500;
60
70
line-height: 1.2;
61
71
}
62
72
···
152
162
cursor: pointer;
153
163
margin-top: 1.5rem;
154
164
transition: all 0.2s;
165
165
+
padding-top: 1rem;
155
166
}
156
167
157
168
.shareStatsButton:hover {