ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

bunch of tiny changes

authored by byarielm.fyi and committed by byarielm.fyi 702ca8db 6382e985

verified
+7 -4
netlify/functions/db-helpers.ts
··· 150 150 return idMap; 151 151 } 152 152 153 - // ==================== THIS FUNCTION IS NOW FIXED ==================== 154 153 export async function bulkLinkUserToSourceAccounts( 155 154 uploadId: string, 156 155 did: string, ··· 190 189 atprotoHandle: string; 191 190 atprotoDisplayName?: string; 192 191 atprotoAvatar?: string; 192 + atprotoDescription?: string; 193 193 matchScore: number; 194 194 postCount?: number; 195 195 followerCount?: number; ··· 203 203 const atprotoDid = matches.map(m => m.atprotoDid) 204 204 const atprotoHandle = matches.map(m => m.atprotoHandle) 205 205 const atprotoDisplayName = matches.map(m => m.atprotoDisplayName || null) 206 - const atprotoAvatar = matches.map(m => m.atprotoAvatar || null) 206 + const atprotoAvatar = matches.map(m => m.atprotoAvatar || null) 207 + const atprotoDescription = matches.map(m => m.atprotoDescription || null) 207 208 const matchScore = matches.map(m => m.matchScore) 208 209 const postCount = matches.map(m => m.postCount || 0) 209 210 const followerCount = matches.map(m => m.followerCount || 0) ··· 211 212 const result = await sql` 212 213 INSERT INTO atproto_matches ( 213 214 source_account_id, atproto_did, atproto_handle, 214 - atproto_display_name, atproto_avatar, match_score, 215 - post_count, follower_count 215 + atproto_display_name, atproto_avatar, atproto_description, 216 + match_score, post_count, follower_count 216 217 ) 217 218 SELECT * FROM UNNEST( 218 219 ${sourceAccountId}::integer[], ··· 220 221 ${atprotoHandle}::text[], 221 222 ${atprotoDisplayName}::text[], 222 223 ${atprotoAvatar}::text[], 224 + ${atprotoDescription}::text[], 223 225 ${matchScore}::integer[], 224 226 ${postCount}::integer[], 225 227 ${followerCount}::integer[] ··· 232 234 atproto_handle = EXCLUDED.atproto_handle, 233 235 atproto_display_name = EXCLUDED.atproto_display_name, 234 236 atproto_avatar = EXCLUDED.atproto_avatar, 237 + atproto_description = EXCLUDED.atproto_description, 235 238 match_score = EXCLUDED.match_score, 236 239 post_count = EXCLUDED.post_count, 237 240 follower_count = EXCLUDED.follower_count,
+1
netlify/functions/db.ts
··· 100 100 atproto_handle TEXT NOT NULL, 101 101 atproto_display_name TEXT, 102 102 atproto_avatar TEXT, 103 + atproto_description TEXT, 103 104 post_count INTEGER, 104 105 follower_count INTEGER, 105 106 match_score INTEGER NOT NULL,
+2
netlify/functions/get-upload-details.ts
··· 83 83 am.atproto_handle, 84 84 am.atproto_display_name, 85 85 am.atproto_avatar, 86 + am.atproto_description, 86 87 am.match_score, 87 88 am.post_count, 88 89 am.follower_count, ··· 139 140 handle: row.atproto_handle, 140 141 displayName: row.atproto_display_name, 141 142 avatar: row.atproto_avatar, 143 + description: row.atproto_description, 142 144 matchScore: row.match_score, 143 145 postCount: row.post_count, 144 146 followerCount: row.follower_count,
+15 -2
netlify/functions/oauth-start.ts
··· 106 106 }; 107 107 } catch (error) { 108 108 console.error('OAuth start error:', error); 109 + 110 + // Provide user-friendly error messages 111 + let userMessage = 'Failed to start authentication'; 112 + 113 + if (error instanceof Error) { 114 + if (error.message.includes('resolve') || error.message.includes('not found')) { 115 + userMessage = 'Account not found. Please check your handle and try again.'; 116 + } else if (error.message.includes('network') || error.message.includes('timeout')) { 117 + userMessage = 'Network error. Please check your connection and try again.'; 118 + } else if (error.message.includes('Invalid identifier')) { 119 + userMessage = 'Invalid handle format. Please use the format: username.bsky.social'; 120 + } 121 + } 122 + 109 123 return { 110 124 statusCode: 500, 111 125 headers: { 'Content-Type': 'application/json' }, 112 126 body: JSON.stringify({ 113 - error: 'Failed to start OAuth flow', 127 + error: userMessage, 114 128 details: error instanceof Error ? error.message : 'Unknown error', 115 - stack: error instanceof Error ? error.stack : undefined 116 129 }), 117 130 }; 118 131 }
+3
netlify/functions/save-results.ts
··· 21 21 handle: string; 22 22 displayName?: string; 23 23 avatar?: string; 24 + description?: string; 24 25 matchScore: number; 25 26 postCount: number; 26 27 followerCount: number; ··· 134 135 atprotoHandle: string; 135 136 atprotoDisplayName?: string; 136 137 atprotoAvatar?: string; 138 + atprotoDescription?: string; 137 139 matchScore: number; 138 140 postCount: number; 139 141 followerCount: number; ··· 156 158 atprotoHandle: match.handle, 157 159 atprotoDisplayName: match.displayName, 158 160 atprotoAvatar: match.avatar, 161 + atprotoDescription: (match as any).description, 159 162 matchScore: match.matchScore, 160 163 postCount: match.postCount || 0, 161 164 followerCount: match.followerCount || 0,
+15 -14
src/components/SearchResultCard.tsx
··· 84 84 {match.displayName} 85 85 </div> 86 86 )} 87 - <div className="text-sm text-gray-800 dark:text-gray-200"> 87 + <a 88 + href={`https://bsky.app/profile/${match.handle}`} 89 + target="_blank" 90 + rel="noopener noreferrer" 91 + className="text-sm text-blue-600 dark:text-blue-400 hover:underline" 92 + > 88 93 @{match.handle} 89 - </div> 94 + </a> 90 95 {match.description && ( 91 96 <div className="text-sm text-gray-700 dark:text-gray-300 mt-1 line-clamp-2">{match.description}</div> 92 97 )} 93 - {(match.postCount || match.followerCount) && ( 94 - <div className="flex items-center space-x-3 mt-2 text-xs text-gray-700 dark:text-gray-300"> 95 - {match.postCount && match.postCount > 0 && ( 96 - <span>{match.postCount.toLocaleString()} posts</span> 97 - )} 98 - {match.followerCount && match.followerCount > 0 && ( 99 - <span>{match.followerCount.toLocaleString()} followers</span> 100 - )} 101 - </div> 102 - )} 103 - <div className="flex items-center space-x-3 mt-2"> 104 - <span className="text-xs bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300 px-2 py-1 rounded-full font-medium"> 98 + <div className="flex items-center flex-wrap gap-x-3 gap-y-1 mt-2 text-xs text-gray-700 dark:text-gray-300"> 99 + {match.postCount && match.postCount > 0 && ( 100 + <span>{match.postCount.toLocaleString()} posts</span> 101 + )} 102 + {match.followerCount && match.followerCount > 0 && ( 103 + <span>{match.followerCount.toLocaleString()} followers</span> 104 + )} 105 + <span className="bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-300 px-2 py-1 rounded-full font-medium"> 105 106 {match.matchScore}% match 106 107 </span> 107 108 </div>
+1 -1
src/lib/parserLogic.ts
··· 15 15 const matches = [...content.matchAll(pattern)]; 16 16 17 17 // We map the results to the first captured group (match[1]), filtering out empty results. 18 - return matches.map(match => match[1]).filter(name => !!name); 18 + return matches.map(match => match[1].trim()).filter(name => !!name); 19 19 20 20 } catch (e) { 21 21 console.error(`ERROR: Invalid regex pattern '${regexPattern}':`, e);
+63 -65
src/pages/Home.tsx
··· 228 228 229 229 {/* History Tab */} 230 230 {activeTab === 'history' && ( 231 - <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 232 - <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 233 - <div className="flex items-center space-x-3 mb-6"> 234 - <Sparkles className="w-6 h-6 text-firefly-amber" /> 235 - <h2 className="text-xl font-bold text-slate-900 dark:text-slate-100"> 236 - Your Light Trail 237 - </h2> 238 - </div> 231 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 232 + <div className="flex items-center space-x-3 mb-6"> 233 + <Sparkles className="w-6 h-6 text-firefly-amber" /> 234 + <h2 className="text-xl font-bold text-slate-900 dark:text-slate-100"> 235 + Your Light Trail 236 + </h2> 237 + </div> 239 238 240 - {isLoading ? ( 241 - <div className="space-y-3"> 242 - {[...Array(3)].map((_, i) => ( 243 - <div key={i} className="animate-pulse flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-700 rounded-xl"> 244 - <div className="w-12 h-12 bg-slate-200 dark:bg-slate-600 rounded-xl" /> 245 - <div className="flex-1 space-y-2"> 246 - <div className="h-4 bg-slate-200 dark:bg-slate-600 rounded w-3/4" /> 247 - <div className="h-3 bg-slate-200 dark:bg-slate-600 rounded w-1/2" /> 248 - </div> 239 + {isLoading ? ( 240 + <div className="space-y-3"> 241 + {[...Array(3)].map((_, i) => ( 242 + <div key={i} className="animate-pulse flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-700 rounded-xl"> 243 + <div className="w-12 h-12 bg-slate-200 dark:bg-slate-600 rounded-xl" /> 244 + <div className="flex-1 space-y-2"> 245 + <div className="h-4 bg-slate-200 dark:bg-slate-600 rounded w-3/4" /> 246 + <div className="h-3 bg-slate-200 dark:bg-slate-600 rounded w-1/2" /> 249 247 </div> 250 - ))} 251 - </div> 252 - ) : uploads.length === 0 ? ( 253 - <div className="text-center py-12"> 254 - <Upload className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-4" /> 255 - <p className="text-slate-600 dark:text-slate-400 font-medium">No previous uploads yet</p> 256 - <p className="text-sm text-slate-500 dark:text-slate-500 mt-2"> 257 - Upload your first file to get started 258 - </p> 259 - </div> 260 - ) : ( 261 - <div className="space-y-3"> 262 - {uploads.map((upload) => { 263 - const destApp = ATPROTO_APPS[userSettings.platformDestinations[upload.sourcePlatform as keyof typeof userSettings.platformDestinations]]; 264 - return ( 265 - <button 266 - key={upload.uploadId} 267 - onClick={() => onLoadUpload(upload.uploadId)} 268 - className="w-full flex items-start space-x-4 p-4 bg-slate-50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-900/70 rounded-xl transition-all text-left border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange shadow-md hover:shadow-lg" 269 - > 270 - <div className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0 shadow-md`}> 271 - <Sparkles className="w-6 h-6 text-white" /> 272 - </div> 273 - <div className="flex-1 min-w-0"> 274 - <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2 mb-1"> 275 - <div className="font-semibold text-slate-900 dark:text-slate-100 capitalize"> 276 - {upload.sourcePlatform} 277 - </div> 278 - <div className="flex items-center gap-2 flex-shrink-0"> 279 - <span className="text-xs px-2 py-0.5 bg-firefly-amber/20 dark:bg-firefly-amber/30 text-amber-900 dark:text-firefly-glow rounded-full font-medium border border-firefly-amber/20 dark:border-firefly-amber/50 whitespace-nowrap"> 280 - {upload.matchedUsers} {upload.matchedUsers === 1 ? 'firefly' : 'fireflies'} 281 - </span> 282 - <div className="text-sm text-slate-600 dark:text-slate-400 font-medium whitespace-nowrap"> 283 - {Math.round((upload.matchedUsers / upload.totalUsers) * 100)}% 284 - </div> 285 - </div> 286 - </div> 287 - <div className="text-sm text-slate-700 dark:text-slate-300"> 288 - {upload.totalUsers} users • {formatDate(upload.createdAt)} 248 + </div> 249 + ))} 250 + </div> 251 + ) : uploads.length === 0 ? ( 252 + <div className="text-center py-12"> 253 + <Upload className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-4" /> 254 + <p className="text-slate-600 dark:text-slate-400 font-medium">No previous uploads yet</p> 255 + <p className="text-sm text-slate-500 dark:text-slate-500 mt-2"> 256 + Upload your first file to get started 257 + </p> 258 + </div> 259 + ) : ( 260 + <div className="space-y-3"> 261 + {uploads.map((upload) => { 262 + const destApp = ATPROTO_APPS[userSettings.platformDestinations[upload.sourcePlatform as keyof typeof userSettings.platformDestinations]]; 263 + return ( 264 + <button 265 + key={upload.uploadId} 266 + onClick={() => onLoadUpload(upload.uploadId)} 267 + className="w-full flex items-start space-x-4 p-4 bg-slate-50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-900/70 rounded-xl transition-all text-left border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange shadow-md hover:shadow-lg" 268 + > 269 + <div className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0 shadow-md`}> 270 + <Sparkles className="w-6 h-6 text-white" /> 271 + </div> 272 + <div className="flex-1 min-w-0"> 273 + <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2 mb-1"> 274 + <div className="font-semibold text-slate-900 dark:text-slate-100 capitalize"> 275 + {upload.sourcePlatform} 289 276 </div> 290 - {destApp && ( 291 - <div className="text-xs text-gray-500 dark:text-gray-400 mt-1"> 292 - Sent to {destApp.icon} {destApp.name} 277 + <div className="flex items-center gap-2 flex-shrink-0"> 278 + <span className="text-xs px-2 py-0.5 bg-firefly-amber/20 dark:bg-firefly-amber/30 text-amber-900 dark:text-firefly-glow rounded-full font-medium border border-firefly-amber/20 dark:border-firefly-amber/50 whitespace-nowrap"> 279 + {upload.matchedUsers} {upload.matchedUsers === 1 ? 'firefly' : 'fireflies'} 280 + </span> 281 + <div className="text-sm text-slate-600 dark:text-slate-400 font-medium whitespace-nowrap"> 282 + {Math.round((upload.matchedUsers / upload.totalUsers) * 100)}% 293 283 </div> 294 - )} 284 + </div> 295 285 </div> 296 - </button> 297 - ); 298 - })} 299 - </div> 300 - )} 286 + <div className="text-sm text-slate-700 dark:text-slate-300"> 287 + {upload.totalUsers} users • {formatDate(upload.createdAt)} 288 + </div> 289 + {destApp && ( 290 + <div className="text-xs text-gray-500 dark:text-gray-400 mt-1"> 291 + Sent to {destApp.icon} {destApp.name} 292 + </div> 293 + )} 294 + </div> 295 + </button> 296 + ); 297 + })} 301 298 </div> 299 + )} 302 300 </div> 303 301 )} 304 302
+4 -2
src/pages/Loading.tsx
··· 62 62 <div className="max-w-3xl mx-auto px-4 py-6"> 63 63 <div className="flex items-center justify-between"> 64 64 <div className="flex items-center space-x-4"> 65 - <div className="relative"> 65 + <div className="relative w-14 h-14"> 66 66 <PlatformIcon className="w-12 h-12" /> 67 - <Search className="w-6 h-6 absolute -bottom-1 -right-1 animate-pulse" aria-hidden="true" /> 67 + <div className="absolute -bottom-1 -right-1 w-7 h-7 bg-white dark:bg-slate-800 rounded-full flex items-center justify-center"> 68 + <Search className="w-4 h-4 animate-pulse" aria-hidden="true" /> 69 + </div> 68 70 </div> 69 71 <div> 70 72 <h2 className="text-xl font-bold">Finding Your Fireflies</h2>