alpha
Login
or
Join now
atpota.to
/
flushes.app
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
add emoji usage to profiles
author
damedotblog
date
1 year ago
(Mar 15, 2025, 8:05 PM -0400)
commit
46b97986
46b97986312234b4a2982c8c2c5499959cc7e4c4
parent
954474bd
954474bdd23d2bff4511f05d9dbfbd3522c01381
+206
-13
6 changed files
Expand all
Collapse all
Unified
Split
app
src
app
api
bluesky
profile
route.ts
stats
route.ts
profile
[handle]
page.tsx
profile.module.css
stats
stats.module.css
components
ProfileSearch.tsx
+106
-3
app/src/app/api/bluesky/profile/route.ts
Reviewed
···
16
16
created_at: string;
17
17
}
18
18
19
19
+
// Define type for emoji statistics
20
20
+
interface EmojiStat {
21
21
+
emoji: string;
22
22
+
count: number;
23
23
+
}
24
24
+
25
25
+
// Define interface for profile response
26
26
+
interface ProfileResponse {
27
27
+
entries: ProfileEntry[];
28
28
+
count: number;
29
29
+
cursor?: string;
30
30
+
profile?: any;
31
31
+
emojiStats?: EmojiStat[];
32
32
+
}
33
33
+
19
34
const DEFAULT_API_URL = 'https://bsky.social/xrpc';
20
35
const MAX_ENTRIES = 50;
36
36
+
37
37
+
// Define approved emojis list - keep in sync with stats route
38
38
+
const APPROVED_EMOJIS = [
39
39
+
'🚽', '🧻', '💩', '💨', '🚾', '🧼', '🪠', '🚻', '🩸', '💧', '💦', '😌',
40
40
+
'😣', '🤢', '🤮', '🥴', '😮💨', '😳', '😵', '🌾', '🍦', '📱', '📖', '💭',
41
41
+
'1️⃣', '2️⃣', '🟡', '🟤'
42
42
+
];
21
43
const FLUSHING_STATUS_NSID = 'im.flushing.right.now';
22
44
23
45
// Supabase client - using environment variables
···
161
183
})
162
184
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null); // Remove filtered entries
163
185
186
186
+
// Calculate emoji statistics
187
187
+
const emojiCounts = new Map<string, number>();
188
188
+
189
189
+
// Process entries to count emojis
190
190
+
transformedEntries.forEach(entry => {
191
191
+
if (entry.emoji) {
192
192
+
// Default to toilet emoji if empty
193
193
+
const emoji = entry.emoji.trim() || '🚽';
194
194
+
// Only count approved emojis
195
195
+
if (APPROVED_EMOJIS.includes(emoji)) {
196
196
+
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
197
197
+
} else {
198
198
+
// Count as default toilet emoji if not approved
199
199
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
200
200
+
}
201
201
+
} else {
202
202
+
// Count default toilet emoji if no emoji specified
203
203
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
204
204
+
}
205
205
+
});
206
206
+
207
207
+
// Convert to array and sort by count (most popular first)
208
208
+
const emojiStats = Array.from(emojiCounts.entries())
209
209
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
210
210
+
.sort((a, b) => b.count - a.count);
211
211
+
164
212
return NextResponse.json({
165
213
entries: transformedEntries,
166
214
count: transformedEntries.length,
167
215
cursor: fallbackData.cursor,
168
168
-
profile: userProfile
216
216
+
profile: userProfile,
217
217
+
emojiStats
169
218
});
170
219
}
171
220
···
199
248
})
200
249
.filter((entry: ProfileEntry | null): entry is ProfileEntry => entry !== null); // Remove filtered entries
201
250
251
251
+
// Calculate emoji statistics
252
252
+
const emojiCounts = new Map<string, number>();
253
253
+
254
254
+
// Process entries to count emojis
255
255
+
transformedEntries.forEach(entry => {
256
256
+
if (entry.emoji) {
257
257
+
// Default to toilet emoji if empty
258
258
+
const emoji = entry.emoji.trim() || '🚽';
259
259
+
// Only count approved emojis
260
260
+
if (APPROVED_EMOJIS.includes(emoji)) {
261
261
+
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
262
262
+
} else {
263
263
+
// Count as default toilet emoji if not approved
264
264
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
265
265
+
}
266
266
+
} else {
267
267
+
// Count default toilet emoji if no emoji specified
268
268
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
269
269
+
}
270
270
+
});
271
271
+
272
272
+
// Convert to array and sort by count (most popular first)
273
273
+
const emojiStats = Array.from(emojiCounts.entries())
274
274
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
275
275
+
.sort((a, b) => b.count - a.count);
276
276
+
202
277
return NextResponse.json({
203
278
entries: transformedEntries,
204
279
count: transformedEntries.length,
205
280
cursor: recordsData.cursor,
206
206
-
profile: userProfile
281
281
+
profile: userProfile,
282
282
+
emojiStats
207
283
});
208
284
} catch (error: any) {
209
285
console.error('Error fetching records:', error);
···
243
319
})
244
320
.filter((entry: any): entry is any => entry !== null);
245
321
322
322
+
// Calculate emoji statistics for Supabase fallback entries
323
323
+
const emojiCounts = new Map<string, number>();
324
324
+
325
325
+
// Process entries to count emojis
326
326
+
filteredEntries.forEach((entry: any) => {
327
327
+
if (entry.emoji) {
328
328
+
// Default to toilet emoji if empty
329
329
+
const emoji = entry.emoji.trim() || '🚽';
330
330
+
// Only count approved emojis
331
331
+
if (APPROVED_EMOJIS.includes(emoji)) {
332
332
+
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
333
333
+
} else {
334
334
+
// Count as default toilet emoji if not approved
335
335
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
336
336
+
}
337
337
+
} else {
338
338
+
// Count default toilet emoji if no emoji specified
339
339
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
340
340
+
}
341
341
+
});
342
342
+
343
343
+
// Convert to array and sort by count (most popular first)
344
344
+
const emojiStats = Array.from(emojiCounts.entries())
345
345
+
.map(([emoji, count]): EmojiStat => ({ emoji, count }))
346
346
+
.sort((a, b) => b.count - a.count);
347
347
+
246
348
return NextResponse.json({
247
349
entries: filteredEntries,
248
350
count: filteredEntries.length, // Update count to reflect filtered entries
249
249
-
profile: userProfile
351
351
+
profile: userProfile,
352
352
+
emojiStats
250
353
});
251
354
}
252
355
+16
-5
app/src/app/api/bluesky/stats/route.ts
Reviewed
···
34
34
count: number;
35
35
};
36
36
37
37
+
// Define approved emojis list
38
38
+
const APPROVED_EMOJIS = [
39
39
+
'🚽', '🧻', '💩', '💨', '🚾', '🧼', '🪠', '🚻', '🩸', '💧', '💦', '😌',
40
40
+
'😣', '🤢', '🤮', '🥴', '😮💨', '😳', '😵', '🌾', '🍦', '📱', '📖', '💭',
41
41
+
'1️⃣', '2️⃣', '🟡', '🟤'
42
42
+
];
43
43
+
37
44
// If we have Supabase credentials, fetch stats
38
45
if (supabaseUrl && supabaseKey) {
39
46
const supabase = createClient(supabaseUrl, supabaseKey);
···
280
287
if (entry.emoji) {
281
288
// Default to toilet emoji if empty
282
289
const emoji = entry.emoji.trim() || '🚽';
283
283
-
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
290
290
+
// Only count approved emojis
291
291
+
if (APPROVED_EMOJIS.includes(emoji)) {
292
292
+
emojiCounts.set(emoji, (emojiCounts.get(emoji) || 0) + 1);
293
293
+
} else {
294
294
+
// Count as default toilet emoji if not approved
295
295
+
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
296
296
+
}
284
297
} else {
285
298
// Count default toilet emoji if no emoji specified
286
299
emojiCounts.set('🚽', (emojiCounts.get('🚽') || 0) + 1);
···
371
384
372
385
// Generate mock emoji stats
373
386
function generateMockEmojiStats() {
374
374
-
const popularEmojis = [
375
375
-
'🚽', '💩', '🧻', '💦', '🧼', '🪠', '🚿', '🛁', '🧴', '🌊',
376
376
-
'💨', '🔥', '🚫', '⚠️', '🚪', '🧫', '📱', '🎮', '📖', '😌'
377
377
-
];
387
387
+
// Use the first 20 approved emojis for mock data
388
388
+
const popularEmojis = APPROVED_EMOJIS.slice(0, 20);
378
389
379
390
return popularEmojis.map((emoji, index) => {
380
391
// Generate counts with descending values
+23
app/src/app/profile/[handle]/page.tsx
Reviewed
···
18
18
created_at: string;
19
19
}
20
20
21
21
+
// Interface for emoji statistics
22
22
+
interface EmojiStat {
23
23
+
emoji: string;
24
24
+
count: number;
25
25
+
}
26
26
+
21
27
export default function ProfilePage() {
22
28
const params = useParams();
23
29
const handle = params.handle as string;
···
30
36
const [profileError, setProfileError] = useState<string | null>(null);
31
37
const [flushesPerDay, setFlushesPerDay] = useState<number>(0);
32
38
const [chartData, setChartData] = useState<{date: string, count: number}[]>([]);
39
39
+
const [emojiStats, setEmojiStats] = useState<EmojiStat[]>([]);
33
40
// Match Bluesky's API response format
34
41
interface ProfileData {
35
42
did: string;
···
106
113
const userEntries = data.entries || [];
107
114
setEntries(userEntries);
108
115
setTotalCount(data.count || 0);
116
116
+
setEmojiStats(data.emojiStats || []);
109
117
110
118
// We now fetch profile data separately
111
119
···
255
263
</>
256
264
) : (
257
265
<p className={styles.noDataMessage}>Not enough data to display activity chart</p>
266
266
+
)}
267
267
+
268
268
+
{/* Emoji Statistics Section */}
269
269
+
{emojiStats.length > 0 && (
270
270
+
<div className={styles.emojiStatsSection}>
271
271
+
<h4 className={styles.emojiStatsHeader}>Favorite Emoji</h4>
272
272
+
<div className={styles.emojiGrid}>
273
273
+
{emojiStats.slice(0, 8).map((stat, index) => (
274
274
+
<div key={index} className={styles.emojiCard}>
275
275
+
<div className={styles.emojiDisplay}>{stat.emoji}</div>
276
276
+
<div className={styles.emojiCount}>{stat.count}</div>
277
277
+
</div>
278
278
+
))}
279
279
+
</div>
280
280
+
</div>
258
281
)}
259
282
</section>
260
283
)}
+56
app/src/app/profile/[handle]/profile.module.css
Reviewed
···
177
177
font-style: italic;
178
178
}
179
179
180
180
+
/* Emoji Statistics Section */
181
181
+
.emojiStatsSection {
182
182
+
margin-top: 2rem;
183
183
+
padding-top: 1.5rem;
184
184
+
border-top: 1px solid var(--tile-border);
185
185
+
}
186
186
+
187
187
+
.emojiStatsHeader {
188
188
+
font-size: 1.1rem;
189
189
+
font-weight: 500;
190
190
+
margin: 0 0 1rem 0;
191
191
+
color: var(--title-color);
192
192
+
}
193
193
+
194
194
+
.emojiGrid {
195
195
+
display: grid;
196
196
+
grid-template-columns: repeat(4, 1fr);
197
197
+
gap: 0.75rem;
198
198
+
}
199
199
+
200
200
+
.emojiCard {
201
201
+
display: flex;
202
202
+
flex-direction: column;
203
203
+
align-items: center;
204
204
+
background-color: var(--input-background);
205
205
+
border-radius: 8px;
206
206
+
padding: 0.75rem 0.5rem;
207
207
+
border: 1px solid var(--tile-border);
208
208
+
}
209
209
+
210
210
+
.emojiDisplay {
211
211
+
font-size: 1.5rem;
212
212
+
margin-bottom: 0.5rem;
213
213
+
}
214
214
+
215
215
+
.emojiCount {
216
216
+
font-weight: bold;
217
217
+
color: var(--primary-color);
218
218
+
font-size: 0.9rem;
219
219
+
}
220
220
+
221
221
+
@media (max-width: 600px) {
222
222
+
.emojiGrid {
223
223
+
grid-template-columns: repeat(3, 1fr);
224
224
+
gap: 0.5rem;
225
225
+
}
226
226
+
227
227
+
.emojiDisplay {
228
228
+
font-size: 1.3rem;
229
229
+
}
230
230
+
231
231
+
.emojiCount {
232
232
+
font-size: 0.85rem;
233
233
+
}
234
234
+
}
235
235
+
180
236
.headerActions {
181
237
display: flex;
182
238
gap: 1rem;
+3
-3
app/src/app/stats/stats.module.css
Reviewed
···
413
413
/* Emoji Grid */
414
414
.emojiGrid {
415
415
display: grid;
416
416
-
grid-template-columns: repeat(5, 1fr);
416
416
+
grid-template-columns: repeat(7, 1fr);
417
417
gap: 1rem;
418
418
margin-top: 1.5rem;
419
419
}
···
431
431
}
432
432
433
433
.emojiCard .emoji {
434
434
-
font-size: 2rem;
434
434
+
font-size: 1.5rem;
435
435
margin-bottom: 0.5rem;
436
436
}
437
437
438
438
.emojiCard .emojiCount {
439
439
font-weight: bold;
440
440
color: var(--primary-color);
441
441
-
font-size: 1.2rem;
441
441
+
font-size: 1rem;
442
442
}
443
443
444
444
/* Share Button */
+2
-2
app/src/components/ProfileSearch.tsx
Reviewed
···
26
26
useEffect(() => {
27
27
const updatePlaceholder = () => {
28
28
if (window.innerWidth <= 480) {
29
29
-
setPlaceholder('@handle or DID');
29
29
+
setPlaceholder('Search handle or DID');
30
30
} else {
31
31
-
setPlaceholder('Search user @handle or did:plc:...');
31
31
+
setPlaceholder('Search handle or DID');
32
32
}
33
33
};
34
34