This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix api calls

+162 -526
+162 -526
src/components/Verifier/Verifier.js
··· 1 1 import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 - import { useAuth } from '../../contexts/AuthContext'; // Updated import path 2 + import { useAuth } from '../../contexts/AuthContext'; 3 3 import { Agent } from '@atproto/api'; 4 - import './Verifier.css'; // Updated CSS import 4 + import './Verifier.css'; 5 5 6 6 // Define trusted verifiers (updated list) 7 7 const TRUSTED_VERIFIERS = [ ··· 16 16 let results = []; 17 17 let cursor = initialParams.cursor; 18 18 const params = { ...initialParams }; 19 - let operationName = apiMethod.name; // Get the name for logging 20 - 21 - // Attempt to determine a more specific name if bound 22 - if (apiMethod.name === 'bound dispatch') { 23 - const boundFnString = apiMethod.toString(); 24 - // This is hacky, relies on internal representation which might change 25 - const match = boundFnString.match(/Target function: (\w+)/); 26 - if (match && match[1]) operationName = match[1]; 27 - } 19 + let operationName = apiMethod.name.replace('bound ', ''); 28 20 29 21 do { 30 22 try { 31 23 if (cursor) { 32 24 params.cursor = cursor; 33 25 } 34 - // Call the method bound to the correct agent context 35 26 const response = await apiMethod(params); 36 - const listKey = Object.keys(response.data).find(key => Array.isArray(response.data[key])); 37 - if (listKey && response.data[listKey]) { 38 - results = results.concat(response.data[listKey]); 27 + const listKey = Object.keys(response.data || response).find(key => Array.isArray((response.data || response)[key])); 28 + if (listKey && (response.data || response)[listKey]) { 29 + results = results.concat((response.data || response)[listKey]); 39 30 } 40 - cursor = response.data.cursor; 31 + cursor = (response.data || response).cursor; 41 32 } catch (error) { 42 - // Use the determined operation name in the error message 43 33 console.error(`Error during paginated fetch for ${operationName}:`, error); 44 34 cursor = undefined; 45 35 } ··· 55 45 didDocUrl = `https://plc.directory/${did}`; 56 46 } else if (did.startsWith('did:web:')) { 57 47 const domain = did.substring(8); // Extract domain after 'did:web:' 58 - // Decode percent-encoded characters in domain (e.g., for ports) 59 48 const decodedDomain = decodeURIComponent(domain); 60 49 didDocUrl = `https://${decodedDomain}/.well-known/did.json`; 61 50 } else { ··· 64 53 } 65 54 66 55 try { 67 - console.log(`Fetching DID document from: ${didDocUrl}`); // Log the URL being fetched 68 56 const response = await fetch(didDocUrl); 69 57 if (!response.ok) { 70 58 console.warn(`Could not resolve DID document for ${did} at ${didDocUrl}: ${response.status}`); ··· 91 79 const handler = setTimeout(() => { 92 80 setDebouncedValue(value); 93 81 }, delay); 94 - 95 - // Cancel the timeout if value changes (also on delay change or unmount) 96 82 return () => { 97 83 clearTimeout(handler); 98 84 }; ··· 126 112 const [networkChecked, setNetworkChecked] = useState(false); 127 113 const [isCheckingValidity, setIsCheckingValidity] = useState(false); 128 114 const [networkStatusMessage, setNetworkStatusMessage] = useState(''); 129 - const [officialVerifiersStatus, setOfficialVerifiersStatus] = useState({}); // Stores status per verifier identifier 115 + const [officialVerifiersStatus, setOfficialVerifiersStatus] = useState({}); 130 116 131 - // --- Autocomplete State --- (Keep as is) 117 + // --- Autocomplete State --- 132 118 const [suggestions, setSuggestions] = useState([]); 133 119 const [isFetchingSuggestions, setIsFetchingSuggestions] = useState(false); 134 120 const [showSuggestions, setShowSuggestions] = useState(false); 135 - const debouncedSearchTerm = useDebounce(targetHandle, 300); // 300ms debounce 136 - const suggestionsRef = useRef(null); // Ref for suggestions container 137 - const inputRef = useRef(null); // Ref for input field 121 + const debouncedSearchTerm = useDebounce(targetHandle, 300); 122 + const suggestionsRef = useRef(null); 123 + const inputRef = useRef(null); 138 124 // --- End Autocomplete State --- 139 125 140 126 useEffect(() => { 141 - // If session exists, create an Agent instance 142 127 if (session) { 143 - // The session object from AuthContext IS the session manager 144 - // Pass it directly to the Agent constructor. 145 128 const agentInstance = new Agent(session); 146 129 setAgent(agentInstance); 147 130 148 - // Fetch logged-in user's profile info using the authenticated API 149 - // Use the DID directly from the session object from useAuth() 150 131 agentInstance.getProfile({ actor: session.did }) 151 132 .then(res => { 152 133 console.log('Logged-in user profile fetched successfully:', res.data); ··· 154 135 }) 155 136 .catch(err => { 156 137 console.error("Failed to fetch user profile:", err); 157 - // Attempt to use basic info from session as fallback 158 138 setUserInfo({ handle: session.handle, displayName: session.displayName || session.handle, did: session.did }); 159 139 }); 160 140 } else { 161 141 setAgent(null); 162 142 setUserInfo(null); 163 143 } 164 - // No redirection here, handled by main app routing if needed 165 144 }, [session]); 166 145 167 - // Fetch all verification records created by the current user 168 - const fetchVerifications = async () => { 146 + const fetchVerifications = useCallback(async () => { 169 147 if (!agent || !session) return; 170 - 171 148 setIsLoadingVerifications(true); 172 149 try { 173 - // Use direct agent method 174 150 const response = await agent.listRecords({ 175 151 repo: session.did, 176 152 collection: 'app.bsky.graph.verification', 177 153 limit: 100, 178 154 }); 179 - 180 155 console.log('Fetched verifications:', response.data); 181 - 182 - // If we have records, set them in state 183 156 if (response.data.records) { 184 - const formattedVerifications = response.data.records.map(record => ({ 157 + const formatted = response.data.records.map(record => ({ 185 158 uri: record.uri, 186 159 cid: record.cid, 187 160 handle: record.value.handle, 188 161 displayName: record.value.displayName, 189 162 subject: record.value.subject, 190 163 createdAt: record.value.createdAt, 191 - isValid: true, // Default, will be checked later 164 + isValid: true, 192 165 validityChecked: false 193 166 })); 194 - setVerifications(formattedVerifications); 195 - 196 - // Check validity of each verification 197 - checkVerificationsValidity(formattedVerifications); 167 + setVerifications(formatted); 168 + checkVerificationsValidity(formatted); 198 169 } else { 199 170 setVerifications([]); 200 171 } ··· 204 175 } finally { 205 176 setIsLoadingVerifications(false); 206 177 } 207 - }; 178 + }, [agent, session]); 208 179 209 - // Check if verifications are still valid (handle/displayName still match) 210 - const checkVerificationsValidity = async (verificationsList) => { 180 + const checkVerificationsValidity = useCallback(async (verificationsList) => { 211 181 if (!agent || verificationsList.length === 0) return; 212 - 213 182 setIsCheckingValidity(true); 214 183 const updatedVerifications = [...verificationsList]; 215 - 216 184 try { 217 - // Process in batches to avoid too many concurrent requests 218 185 const batchSize = 5; 219 186 for (let i = 0; i < updatedVerifications.length; i += batchSize) { 220 187 const batch = updatedVerifications.slice(i, i + batchSize); 221 - 222 188 await Promise.all(batch.map(async (verification, index) => { 223 189 try { 224 - // Use direct agent method 225 - const profileRes = await agent.getProfile({ 226 - actor: verification.handle 227 - }); 228 - 229 - // Check if handle and displayName still match 190 + const profileRes = await agent.getProfile({ actor: verification.handle }); 230 191 const currentHandle = profileRes.data.handle; 231 192 const currentDisplayName = profileRes.data.displayName || profileRes.data.handle; 232 - 233 - // Update verification validity 234 193 const batchIndex = i + index; 235 194 updatedVerifications[batchIndex].validityChecked = true; 236 195 updatedVerifications[batchIndex].isValid = 237 196 currentHandle === verification.handle && 238 197 currentDisplayName === verification.displayName; 239 - 240 - // If not valid, store current values for reference 241 198 if (!updatedVerifications[batchIndex].isValid) { 242 199 updatedVerifications[batchIndex].currentHandle = currentHandle; 243 200 updatedVerifications[batchIndex].currentDisplayName = currentDisplayName; 244 201 } 245 - 246 - // Update state as we go to show progress 247 202 setVerifications([...updatedVerifications]); 248 203 } catch (err) { 249 204 console.error(`Failed to check validity for ${verification.handle}:`, err); 250 - // Mark as could not check 251 205 const batchIndex = i + index; 252 206 updatedVerifications[batchIndex].validityChecked = true; 253 207 updatedVerifications[batchIndex].isValid = false; 254 208 updatedVerifications[batchIndex].validityError = true; 209 + setVerifications([...updatedVerifications]); 255 210 } 256 211 })); 257 212 } 258 - 259 213 console.log('Verified all records validity:', updatedVerifications); 260 214 } catch (error) { 261 215 console.error('Failed to check verifications validity:', error); 262 216 } finally { 263 217 setIsCheckingValidity(false); 264 218 } 265 - }; 219 + }, [agent]); 266 220 267 - // Updated function: Check mutuals (authenticated) and all follows (public) 268 - const checkNetworkVerifications = async () => { 269 - // Ensure authenticated agent is available for mutuals check 221 + const checkNetworkVerifications = useCallback(async () => { 270 222 if (!agent || !session || !userInfo) return; 271 - 272 223 setIsLoadingNetwork(true); 273 224 setNetworkChecked(false); 274 - // Reset state 275 - setNetworkVerifications({ 276 - mutualsVerifiedMe: [], followsVerifiedMe: [], 277 - mutualsVerifiedAnyone: 0, followsVerifiedAnyone: 0, 278 - fetchedMutualsCount: 0, fetchedFollowsCount: 0 279 - }); 225 + setNetworkVerifications({ mutualsVerifiedMe: [], followsVerifiedMe: [], mutualsVerifiedAnyone: 0, followsVerifiedAnyone: 0, fetchedMutualsCount: 0, fetchedFollowsCount: 0 }); 280 226 setNetworkStatusMessage("Fetching network lists (mutuals, follows)..."); 281 227 282 228 const publicAgent = new Agent({ service: 'https://public.api.bsky.app' }); 283 229 284 230 try { 285 - // Fetch follows (public) and known followers/mutuals (authenticated) 286 231 const [follows, mutuals] = await Promise.all([ 287 - // Use direct agent method (even for public agent) 288 - fetchAllPaginated(publicAgent, publicAgent.getFollows.bind(publicAgent), { actor: session.did, limit: 100 }), 289 - // Use direct agent method 232 + fetchAllPaginated(publicAgent, publicAgent.api.app.bsky.graph.getFollows.bind(publicAgent.api.app.bsky.graph), { actor: session.did, limit: 100 }), 290 233 fetchAllPaginated(agent, agent.getKnownFollowers.bind(agent), { actor: session.did, limit: 100 }) 291 234 ]); 292 235 293 - console.log(`Fetched ${follows.length} follows (public), ${mutuals.length} mutuals (authenticated).`); // Updated log 294 - setNetworkStatusMessage(`Fetched ${follows.length} follows, ${mutuals.length} mutuals. Discovering PDS and checking verifications...`); 295 - 296 - // Update fetched counts 297 - setNetworkVerifications(prev => ({ 298 - ...prev, 299 - fetchedMutualsCount: mutuals.length, 300 - fetchedFollowsCount: follows.length, 301 - })); 236 + console.log(`Fetched ${follows.length} follows, ${mutuals.length} mutuals.`); 237 + setNetworkStatusMessage(`Fetched ${follows.length} follows, ${mutuals.length} mutuals. Checking verifications...`); 238 + setNetworkVerifications(prev => ({ ...prev, fetchedMutualsCount: mutuals.length, fetchedFollowsCount: follows.length })); 302 239 303 240 const followsSet = new Set(follows.map(f => f.did)); 304 241 const mutualsSet = new Set(mutuals.map(m => m.did)); 305 - 306 242 const allProfilesMap = new Map(); 307 - [...follows, ...mutuals].forEach(user => { 308 - if (!allProfilesMap.has(user.did)) { 309 - allProfilesMap.set(user.did, user); 310 - } 311 - }); 312 - 243 + [...follows, ...mutuals].forEach(user => { if (!allProfilesMap.has(user.did)) allProfilesMap.set(user.did, user); }); 313 244 const uniqueUserDids = Array.from(allProfilesMap.keys()); 314 245 315 246 if (uniqueUserDids.length === 0) { 316 - setNetworkStatusMessage("No mutuals or follows found to check."); 247 + setNetworkStatusMessage("No mutuals or follows found."); 317 248 setIsLoadingNetwork(false); 318 249 setNetworkChecked(true); 319 250 return; 320 251 } 321 252 322 - let results = { 323 - mutualsVerifiedMe: [], followsVerifiedMe: [], 324 - mutualsVerifiedAnyone: 0, followsVerifiedAnyone: 0 325 - }; 326 - 327 - const batchSize = 5; 253 + let results = { mutualsVerifiedMe: [], followsVerifiedMe: [], mutualsVerifiedAnyone: 0, followsVerifiedAnyone: 0 }; 254 + const batchSize = 10; 328 255 for (let i = 0; i < uniqueUserDids.length; i += batchSize) { 329 256 const batchDids = uniqueUserDids.slice(i, i + batchSize); 330 257 setNetworkStatusMessage(`Checking network... (${i + batchDids.length}/${uniqueUserDids.length})`); ··· 332 259 await Promise.all(batchDids.map(async (did) => { 333 260 const profile = allProfilesMap.get(did); 334 261 if (!profile) return; 335 - 336 262 const isMutual = mutualsSet.has(did); 337 263 const isFollow = followsSet.has(did); 338 - 339 264 const pdsEndpoint = await getPdsEndpoint(did); 340 - if (!pdsEndpoint) { 341 - console.warn(`Skipping verification check for ${profile.handle} (no PDS found).`); 342 - return; 343 - } 265 + if (!pdsEndpoint) return; 344 266 345 267 let foundVerificationForMe = null; 346 268 let hasVerifiedAnyone = false; 347 269 let listRecordsCursor = undefined; 270 + const tempPublicAgent = new Agent({ service: pdsEndpoint }); 348 271 349 272 do { 350 273 try { 351 - const listParams = new URLSearchParams({ repo: did, collection: 'app.bsky.graph.verification', limit: '100' }); 352 - if (listRecordsCursor) listParams.set('cursor', listRecordsCursor); 353 - const listRecordsUrl = `${pdsEndpoint}/xrpc/com.atproto.repo.listRecords?${listParams.toString()}`; 354 - const listResponse = await fetch(listRecordsUrl); 355 - if (!listResponse.ok) { break; } 356 - const listData = await listResponse.json(); 357 - const records = listData.records || []; 274 + const response = await tempPublicAgent.listRecords({ 275 + repo: did, 276 + collection: 'app.bsky.graph.verification', 277 + limit: 100, 278 + cursor: listRecordsCursor 279 + }); 280 + const records = response.data.records || []; 358 281 if (records.length > 0) { 359 - hasVerifiedAnyone = true; 360 - const matchingRecord = records.find(record => record.value?.subject === session.did); // Use session.did 361 - if (matchingRecord) { foundVerificationForMe = matchingRecord; break; } 282 + hasVerifiedAnyone = true; 283 + const matchingRecord = records.find(record => record.value?.subject === session.did); 284 + if (matchingRecord) { foundVerificationForMe = matchingRecord; break; } 362 285 } 363 - listRecordsCursor = listData.cursor; 286 + listRecordsCursor = response.data.cursor; 364 287 } catch (err) { 365 - console.error(`Network error fetching listRecords for ${did} from ${pdsEndpoint}:`, err); 366 - listRecordsCursor = undefined; 288 + console.warn(`Could not listRecords for ${did} on ${pdsEndpoint}:`, err.message); 289 + listRecordsCursor = undefined; 290 + break; 367 291 } 368 292 } while (listRecordsCursor); 369 293 ··· 372 296 if (isFollow) results.followsVerifiedAnyone++; 373 297 } 374 298 if (foundVerificationForMe) { 375 - const accountInfo = { ...profile, verification: foundVerificationForMe }; 376 - if (isMutual) results.mutualsVerifiedMe.push(accountInfo); 377 - if (isFollow) results.followsVerifiedMe.push(accountInfo); 299 + const accountInfo = { ...profile, verification: foundVerificationForMe }; 300 + if (isMutual) results.mutualsVerifiedMe.push(accountInfo); 301 + if (isFollow) results.followsVerifiedMe.push(accountInfo); 378 302 } 379 - 380 303 })); 381 - 382 - setNetworkVerifications(prev => ({ 383 - ...prev, 384 - mutualsVerifiedMe: [...results.mutualsVerifiedMe], 385 - followsVerifiedMe: [...results.followsVerifiedMe], 386 - mutualsVerifiedAnyone: results.mutualsVerifiedAnyone, 387 - followsVerifiedAnyone: results.followsVerifiedAnyone, 388 - })); 304 + setNetworkVerifications(prev => ({ ...prev, ...results })); 389 305 } 390 - 391 - console.log('Network check complete. Results:', results); 392 306 setNetworkStatusMessage("Network verification check complete."); 393 - 394 307 } catch (error) { 395 - // Catch errors from initial Promise.all or other setup issues 396 - console.error('Fatal error during network verification check:', error); 397 - setStatusMessage(`Fatal error checking network: ${error.message || 'Unknown error'}`); 308 + console.error('Error during network verification check:', error); 309 + setStatusMessage(`Error checking network: ${error.message || 'Unknown error'}`); 398 310 setNetworkStatusMessage(""); 399 311 } finally { 400 312 setIsLoadingNetwork(false); 401 313 setNetworkChecked(true); 402 314 } 403 - }; 315 + }, [agent, session, userInfo]); 404 316 405 - // Call fetchVerifications when agent is available 406 317 useEffect(() => { 407 318 if (agent) { 408 319 fetchVerifications(); 409 320 } 410 - }, [agent]); 321 + }, [agent, fetchVerifications]); 411 322 412 - // Updated function to check each official verifier individually 413 - const checkOfficialVerification = async () => { 414 - if (!agent || !session) return; 415 - 416 - // Initialize status for all verifiers to 'checking' 323 + const checkOfficialVerification = useCallback(async () => { 324 + if (!session?.did) return; 417 325 const initialStatuses = {}; 418 326 TRUSTED_VERIFIERS.forEach(id => { initialStatuses[id] = 'checking'; }); 419 327 setOfficialVerifiersStatus(initialStatuses); 420 - 421 328 const publicAgent = new Agent({ service: 'https://public.api.bsky.app' }); 422 329 423 - // Use Promise.all to run checks concurrently (optional, but can be faster) 424 330 await Promise.all(TRUSTED_VERIFIERS.map(async (verifierIdentifier) => { 425 331 let verifierDid = null; 426 332 let verifierHandle = verifierIdentifier; 427 - let currentStatus = 'checking'; // Status for this specific verifier 428 - 333 + let currentStatus = 'checking'; 429 334 try { 430 - // Resolve handle/DID 431 335 if (!verifierIdentifier.startsWith('did:')) { 432 336 const resolveResult = await publicAgent.resolveHandle({ handle: verifierIdentifier }); 433 337 verifierDid = resolveResult.data.did; 434 338 } else { 435 339 verifierDid = verifierIdentifier; 436 340 try { 437 - const profileRes = await publicAgent.getProfile({ actor: verifierDid }); 438 - verifierHandle = profileRes.data.handle; 439 - } catch (profileError) { /* ignore */ } 341 + const profileRes = await publicAgent.api.app.bsky.actor.getProfile({ actor: verifierDid }); 342 + verifierHandle = profileRes.data.handle; 343 + } catch { /* ignore */ } 440 344 } 441 345 if (!verifierDid) throw new Error('Could not resolve identifier'); 442 - 443 - // Discover PDS 444 346 const pdsEndpoint = await getPdsEndpoint(verifierDid); 445 347 if (!pdsEndpoint) throw new Error('Could not find PDS'); 446 348 447 - // Paginate through their listRecords 448 349 let listRecordsCursor = undefined; 449 350 let foundMatch = false; 351 + const tempPublicAgent = new Agent({ service: pdsEndpoint }); 352 + 450 353 do { 451 - const listParams = new URLSearchParams({ repo: verifierDid, collection: 'app.bsky.graph.verification', limit: '100' }); 452 - if (listRecordsCursor) listParams.set('cursor', listRecordsCursor); 453 - const listRecordsUrl = `${pdsEndpoint}/xrpc/com.atproto.repo.listRecords?${listParams.toString()}`; 454 - const listResponse = await fetch(listRecordsUrl); 455 - 456 - if (!listResponse.ok) { 457 - // Treat 400 (repo/collection not found) as simply not verified by this one 458 - if (listResponse.status !== 400) { 459 - console.warn(`Failed fetch for ${verifierHandle}: ${listResponse.status}`); 460 - throw new Error(`Fetch failed with status ${listResponse.status}`); // Throw for other errors 461 - } 462 - break; // Stop checking this verifier on 400 or other errors 463 - } 464 - 465 - const listData = await listResponse.json(); 466 - const records = listData.records || []; 467 - const matchingRecord = records.find(record => record.value?.subject === session.did); // Use session.did 468 - 469 - if (matchingRecord) { 470 - console.log(`Found official verification by ${verifierHandle}`); 471 - currentStatus = 'verified'; 472 - foundMatch = true; 473 - break; // Exit pagination loop for THIS verifier 354 + try { 355 + const response = await tempPublicAgent.listRecords({ 356 + repo: verifierDid, 357 + collection: 'app.bsky.graph.verification', 358 + limit: 100, 359 + cursor: listRecordsCursor 360 + }); 361 + const records = response.data.records || []; 362 + const matchingRecord = records.find(record => record.value?.subject === session.did); 363 + if (matchingRecord) { 364 + currentStatus = 'verified'; 365 + foundMatch = true; 366 + break; 367 + } 368 + listRecordsCursor = response.data.cursor; 369 + } catch (err) { 370 + console.warn(`Could not listRecords for ${verifierDid} on ${pdsEndpoint}:`, err.message); 371 + listRecordsCursor = undefined; 372 + break; 474 373 } 475 - listRecordsCursor = listData.cursor; 476 374 } while (listRecordsCursor); 477 - 478 - // If loop completed without finding a match for this verifier 479 - if (!foundMatch) { 375 + if (!foundMatch && currentStatus === 'checking') { 480 376 currentStatus = 'not_verified'; 481 377 } 482 - 483 378 } catch (error) { 484 379 console.error(`Error checking official verifier ${verifierIdentifier}:`, error); 485 - currentStatus = 'error'; // Set status to error for this specific verifier 380 + currentStatus = 'error'; 486 381 } 487 - 488 - // Update the state for this specific verifier 489 382 setOfficialVerifiersStatus(prev => ({ ...prev, [verifierIdentifier]: currentStatus })); 490 - 491 - })); // End Promise.all map 492 - 383 + })); 493 384 console.log("Finished checking all official verifiers."); 494 - 495 - }; // End checkOfficialVerification 385 + }, [session]); 496 386 497 - // Effect to check official verification status on load 498 387 useEffect(() => { 499 - // Run check when agent/session are ready 500 - if (agent && session?.did) { // Changed from session.sub 388 + if (session?.did) { 501 389 checkOfficialVerification(); 502 390 } 503 - // Run once when agent/session become available 504 - }, [agent, session]); 391 + }, [session, checkOfficialVerification]); 505 392 506 - // --- Fetch Autocomplete Suggestions --- (Keep as is) 507 393 const fetchSuggestions = useCallback(async (query) => { 508 - if (!query || query.trim().length < 2) { // Minimum 2 chars to search 394 + if (!query || query.trim().length < 2) { 509 395 setSuggestions([]); 510 - // Don't explicitly setShowSuggestions(false) here, let onChange handle it 511 396 return; 512 397 } 513 - 514 398 setIsFetchingSuggestions(true); 515 - // Don't set showSuggestions(true) here either, should be true already if we got here 516 - 517 399 try { 518 - const url = new URL('https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead'); 519 - url.searchParams.append('q', query); 520 - url.searchParams.append('limit', '5'); // Fetch 5 suggestions 521 - 522 - const response = await fetch(url.toString()); 523 - if (!response.ok) { 524 - throw new Error(`HTTP error! status: ${response.status}`); 525 - } 526 - const data = await response.json(); 527 - console.log('Suggestions fetched:', data.actors); 528 - setSuggestions(data.actors || []); 400 + const publicAgent = new Agent({ service: 'https://public.api.bsky.app' }); 401 + const response = await publicAgent.api.app.bsky.actor.searchActorsTypeahead({ 402 + q: query, 403 + limit: 5 404 + }); 405 + setSuggestions(response.data.actors || []); 529 406 } catch (error) { 530 407 console.error('Failed to fetch suggestions:', error); 531 - setSuggestions([]); // Clear suggestions on error 408 + setSuggestions([]); 532 409 } finally { 533 410 setIsFetchingSuggestions(false); 534 411 } 535 412 }, []); 536 413 537 - // Effect to fetch suggestions based on debounced search term AND if suggestions should be shown 538 414 useEffect(() => { 539 - // Only fetch if the user is likely typing (suggestions are meant to be shown) 540 - // and the term is long enough. 541 415 if (debouncedSearchTerm && showSuggestions) { 542 416 fetchSuggestions(debouncedSearchTerm); 543 - } else if (!debouncedSearchTerm) { // Always clear if term is empty 417 + } else if (!debouncedSearchTerm) { 544 418 setSuggestions([]); 545 - // setShowSuggestions(false); // Let onChange handle hiding when empty 546 419 } 547 - // If showSuggestions is false (e.g., after a click), this effect won't trigger a fetch. 548 - }, [debouncedSearchTerm, fetchSuggestions, showSuggestions]); // Add showSuggestions dependency 420 + }, [debouncedSearchTerm, fetchSuggestions, showSuggestions]); 549 421 550 - // --- Click Outside Handler for Suggestions --- (Keep as is) 551 422 useEffect(() => { 552 423 function handleClickOutside(event) { 553 424 if (suggestionsRef.current && !suggestionsRef.current.contains(event.target) && ··· 555 426 setShowSuggestions(false); 556 427 } 557 428 } 558 - // Bind the event listener 559 429 document.addEventListener("mousedown", handleClickOutside); 560 - return () => { 561 - // Unbind the event listener on clean up 562 - document.removeEventListener("mousedown", handleClickOutside); 563 - }; 564 - }, [suggestionsRef, inputRef]); // Add inputRef dependency 565 - // --- End Click Outside Handler --- 430 + return () => document.removeEventListener("mousedown", handleClickOutside); 431 + }, [suggestionsRef, inputRef]); 566 432 567 - // --- handleVerify --- (Keep as is) 568 433 const handleVerify = async (e) => { 569 434 e.preventDefault(); 570 - if (!agent || !session) { 571 - setStatusMessage('Error: Not logged in or agent not initialized.'); 572 - return; 573 - } 574 - if (!targetHandle) { 575 - setStatusMessage('Please enter a handle to verify.'); 576 - return; 577 - } 435 + if (!agent || !session) return; 436 + if (!targetHandle) return; 578 437 setIsVerifying(true); 579 438 setStatusMessage(`Verifying ${targetHandle}...`); 580 - setShowSuggestions(false); // Hide suggestions when submitting 581 - 439 + setShowSuggestions(false); 582 440 try { 583 - // 1. Get profile of targetHandle (resolve handle to DID and get display name) 584 - setStatusMessage(`Fetching profile for ${targetHandle}...`); 585 - // Use the proper API namespace method 586 441 const profileRes = await agent.getProfile({ actor: targetHandle }); 587 442 const targetDid = profileRes.data.did; 588 443 const targetDisplayName = profileRes.data.displayName || profileRes.data.handle; 589 - console.log('Target Profile:', profileRes.data); 590 - 591 - // 2. Construct the verification record object 592 444 const verificationRecord = { 593 - $type: 'app.bsky.graph.verification', // Using the type you provided 445 + $type: 'app.bsky.graph.verification', 594 446 subject: targetDid, 595 - handle: targetHandle, // Include handle for context 596 - displayName: targetDisplayName, // Include display name 447 + handle: targetHandle, 448 + displayName: targetDisplayName, 597 449 createdAt: new Date().toISOString(), 598 450 }; 599 - console.log('Verification Record to Create:', verificationRecord); 600 - 601 - // 3. Create the record using the agent's com.atproto.repo.createRecord method 602 - setStatusMessage(`Creating verification record for ${targetHandle} on your profile...`); 603 - 604 - // The correct method is repo.createRecord, not createRecord 605 - const createRes = await agent.createRecord({ 606 - repo: session.did, // Use session.did 607 - collection: 'app.bsky.graph.verification', // The NSID of the record type 451 + await agent.createRecord({ 452 + repo: session.did, 453 + collection: 'app.bsky.graph.verification', 608 454 record: verificationRecord, 609 455 }); 610 - 611 - console.log('Create Record Response:', createRes); 612 - 613 - // --- Construct Success Message with Intent Link --- (Keep as is) 614 - const verifiedHandle = targetHandle; // Capture handle for this success 615 - const postText = `I just verified @${verifiedHandle} using Bluesky's new decentralized verification system. Try verifying someone yourself using @cred.blue's new verification tool: https://cred.blue/verify`; 456 + const postText = `I just verified @${targetHandle} using Bluesky's new decentralized verification system. Try verifying someone yourself using @cred.blue's new verification tool: https://cred.blue/verify`; 616 457 const encodedText = encodeURIComponent(postText); 617 458 const intentUrl = `https://bsky.app/intent/compose?text=${encodedText}`; 618 - 619 459 const successMessageJSX = ( 620 - <> 621 - Successfully created verification record for {verifiedHandle}!{' '} 622 - <a href={intentUrl} target="_blank" rel="noopener noreferrer" className="verifier-intent-link"> {/* Use plain class */} 623 - Post on Bluesky to let them know. 624 - </a> 625 - </> 460 + <>Successfully created verification for {targetHandle}! <a href={intentUrl} target="_blank" rel="noopener noreferrer" className="verifier-intent-link">Post on Bluesky?</a></> 626 461 ); 627 - setStatusMessage(successMessageJSX); // Set JSX as status message 628 - // --- End Intent Link Construction --- 629 - 630 - setTargetHandle(''); // Clear input on success 631 - 632 - // Refresh the list of verifications 462 + setStatusMessage(successMessageJSX); 463 + setTargetHandle(''); 633 464 fetchVerifications(); 634 - 635 465 } catch (error) { 636 466 console.error('Verification failed:', error); 637 467 setStatusMessage(`Verification failed: ${error.message || 'Unknown error'}`); ··· 639 469 setIsVerifying(false); 640 470 } 641 471 }; 642 - // --- End handleVerify --- 643 472 644 - // Function to revoke (delete) a verification - (Keep as is) 645 473 const handleRevoke = async (verification) => { 646 - if (!agent || !session) { 647 - setStatusMessage('Error: Not logged in or agent not initialized.'); 648 - return; 649 - } 650 - 474 + if (!agent || !session) return; 651 475 setIsRevoking(true); 652 476 setStatusMessage(`Revoking verification for ${verification.handle}...`); 653 - 654 477 try { 655 - // Extract rkey from URI 656 - // URI format: at://did:plc:xxx/app.bsky.graph.verification/rkey 657 478 const parts = verification.uri.split('/'); 658 479 const rkey = parts[parts.length - 1]; 659 - 660 480 await agent.deleteRecord({ 661 - repo: session.did, // Use session.did 481 + repo: session.did, 662 482 collection: 'app.bsky.graph.verification', 663 483 rkey: rkey 664 484 }); 665 - 666 - console.log('Revoked verification for:', verification.handle); 667 485 setStatusMessage(`Successfully revoked verification for ${verification.handle}`); 668 - 669 - // Refresh the list of verifications 670 486 fetchVerifications(); 671 487 } catch (error) { 672 488 console.error('Revocation failed:', error); ··· 676 492 } 677 493 }; 678 494 679 - // --- handleSuggestionClick --- (Keep as is) 680 495 const handleSuggestionClick = (handle) => { 681 496 setTargetHandle(handle); 682 497 setSuggestions([]); 683 498 setShowSuggestions(false); 684 - inputRef.current?.focus(); // Keep focus on input after selection 499 + inputRef.current?.focus(); 685 500 }; 686 - // --- End handleSuggestionClick --- 687 501 688 - // AuthProvider handles redirection if not logged in during its initial load 689 - if (isAuthLoading) { 690 - return <p>Loading authentication...</p>; 691 - } 692 - 693 - if (authError) { 694 - return <p>Authentication Error: {authError}. <a href="/login">Please login</a>.</p>; 695 - } 696 - 697 - // Display message if session is not available but not loading/erroring 502 + if (isAuthLoading) return <p>Loading authentication...</p>; 503 + if (authError) return <p>Authentication Error: {authError}. <a href="/login">Please login</a>.</p>; 698 504 if (!session && !isAuthLoading && !authError) { 699 - return ( 700 - <div className="verifier-container"> 701 - <h1>Bluesky Verifier Tool</h1> 702 - <p>Please <a href="/login">login with Bluesky</a> to use the verifier tool.</p> 703 - </div> 704 - ); 505 + return (<div className="verifier-container"><h1>Bluesky Verifier Tool</h1><p>Please <a href="/login">login with Bluesky</a> to use the verifier tool.</p></div>); 705 506 } 706 507 707 - // Update combined loading state 708 508 const isAnyOperationInProgress = isVerifying || isRevoking || isLoadingVerifications || isLoadingNetwork || isCheckingValidity; 709 - 710 - // Update tooltip construction to use the (potentially resolved) handles 711 509 const trustedVerifiersTooltip = `Checking if any of these Trusted Verifiers have created a verification record for your DID: ${TRUSTED_VERIFIERS.join(', ')}.`; 712 510 713 - // Use standard class names, not styles object 714 511 return ( 715 512 <div className="verifier-container"> 716 513 <h1>Bluesky Verifier Tool</h1> ··· 718 515 With Bluesky's new decentralized verification system, anyone can verify anyone else and any Bluesky client can choose which accounts to treat as "Trusted Verifiers". It's a first-of-its-kind verification system for a mainstream social platform of this size. Try verifying an account for yourself or check to see who has verified you! 719 516 </p> 720 517 <div className="verifier-page-header"> 721 - <p className="verifier-user-info">Logged in as: {userInfo ? `${userInfo.displayName} (@${userInfo.handle})` : session?.did}</p> {/* Safely access session.did */} 722 - <button 723 - onClick={signOut} 724 - disabled={isAnyOperationInProgress} 725 - className="verifier-sign-out-button" 726 - > 727 - Sign Out 728 - </button> 518 + <p className="verifier-user-info">Logged in as: {userInfo ? `${userInfo.displayName} (@${userInfo.handle})` : session?.did}</p> 519 + <button onClick={signOut} disabled={isAnyOperationInProgress} className="verifier-sign-out-button">Sign Out</button> 729 520 </div> 730 521 <hr /> 731 522 732 - {/* Verification form */} 733 523 <div className="verifier-section"> 734 524 <h2>Verify a Bluesky User</h2> 735 525 <p>Enter the handle of the user you want to verify (e.g., targetuser.bsky.social):</p> 736 - {/* --- Input Container for Autocomplete Positioning --- */} 737 526 <div className="verifier-input-container"> 738 527 <form onSubmit={handleVerify} className="verifier-form-container" style={{ marginBottom: 0 }}> 739 528 <input 740 - ref={inputRef} // Assign ref 529 + ref={inputRef} 741 530 type="text" 742 531 value={targetHandle} 743 532 onChange={(e) => { 744 533 const newValue = e.target.value; 745 534 setTargetHandle(newValue); 746 - if (newValue.length >= 2) { 747 - setShowSuggestions(true); 748 - } else { 749 - setShowSuggestions(false); 750 - setSuggestions([]); 751 - } 535 + setShowSuggestions(newValue.length >= 2); 536 + if(newValue.length < 2) setSuggestions([]); 752 537 }} 753 - onFocus={() => { 754 - if (targetHandle.length >= 2) { 755 - setShowSuggestions(true); 756 - } 757 - }} 538 + onFocus={() => { if (targetHandle.length >= 2) setShowSuggestions(true); }} 758 539 placeholder="targetuser.bsky.social" 759 540 disabled={isAnyOperationInProgress} 760 541 required 761 542 className="verifier-input-field" 762 543 autoComplete="off" 763 544 /> 764 - <button 765 - type="submit" 766 - disabled={isVerifying || isRevoking || isLoadingVerifications || isLoadingNetwork || isCheckingValidity} 767 - className="verifier-submit-button" 768 - > 545 + <button type="submit" disabled={isVerifying || !targetHandle} className="verifier-submit-button"> 769 546 {isVerifying ? 'Verifying...' : 'Create Verification Record'} 770 547 </button> 771 548 </form> 772 - {/* --- Suggestions Dropdown --- */} 773 549 {showSuggestions && (suggestions.length > 0 || isFetchingSuggestions) && ( 774 550 <ul className="verifier-suggestions-list" ref={suggestionsRef}> 775 551 {isFetchingSuggestions && suggestions.length === 0 ? ( 776 552 <li className="verifier-suggestion-item">Loading...</li> 777 553 ) : ( 778 554 suggestions.map((actor) => ( 779 - <li 780 - key={actor.did} 781 - className="verifier-suggestion-item" 782 - onMouseDown={(e) => { 783 - e.preventDefault(); 784 - handleSuggestionClick(actor.handle); 785 - }} 786 - > 555 + <li key={actor.did} className="verifier-suggestion-item" onMouseDown={(e) => { e.preventDefault(); handleSuggestionClick(actor.handle); }}> 787 556 <img src={actor.avatar} alt="" className="verifier-suggestion-avatar" /> 788 557 <div className="verifier-suggestion-text"> 789 558 <span className="verifier-suggestion-display-name">{actor.displayName || actor.handle}</span> ··· 800 569 </div> 801 570 </div> 802 571 803 - {/* Global Status message */} 804 572 {statusMessage && ( 805 - <div className={` 806 - verifier-status-box 807 - ${ typeof statusMessage === 'string' && (statusMessage.includes('failed') || statusMessage.includes('Error')) 808 - ? 'verifier-status-box-error' 809 - : 'verifier-status-box-success' 810 - } 811 - `}> 573 + <div className={`verifier-status-box ${typeof statusMessage === 'string' && (statusMessage.includes('failed') || statusMessage.includes('Error')) ? 'verifier-status-box-error' : 'verifier-status-box-success'}`}> 812 574 <p>{statusMessage}</p> 813 575 </div> 814 576 )} 815 577 816 - {/* Updated Official Verifiers section */} 817 578 <div className="verifier-section"> 818 579 <div style={{display: 'flex', alignItems: 'center', marginBottom: '10px'}}> 819 580 <h2 style={{ display: 'inline-block', marginRight: '8px', marginBottom: 0, border: 'none', padding: 0 }}>Your Verification Status</h2> 820 - <span 821 - title={trustedVerifiersTooltip} 822 - className="verifier-official-verifier-tooltip" 823 - style={{ fontSize: '1.2em' }} 824 - > 825 - (?) 826 - </span> 581 + <span title={trustedVerifiersTooltip} className="verifier-official-verifier-tooltip" style={{ fontSize: '1.2em' }}>(?)</span> 827 582 </div> 828 - 829 - {/* Map over trusted verifiers and display individual status */} 830 583 <div> 831 584 {TRUSTED_VERIFIERS.map(verifierId => { 832 585 const status = officialVerifiersStatus[verifierId] || 'idle'; 833 - let message = '...'; 834 - let icon = '⏳'; 835 - let statusClass = ''; 836 - 586 + let message = '...'; let icon = '⏳'; let statusClass = 'verifier-idle-status'; 837 587 switch (status) { 838 - case 'checking': 839 - message = `Checking ${verifierId}...`; 840 - icon = '⏳'; 841 - statusClass = 'verifier-checking-status'; 842 - break; 843 - case 'verified': 844 - message = `Verified by ${verifierId}.`; 845 - icon = '✅'; 846 - statusClass = 'verifier-verified-status'; 847 - break; 848 - case 'not_verified': 849 - message = `Not verified by ${verifierId}.`; 850 - icon = '❌'; 851 - statusClass = 'verifier-not-verified-status'; 852 - break; 853 - case 'error': 854 - message = `Error checking ${verifierId}.`; 855 - icon = '⚠️'; 856 - statusClass = 'verifier-error-status'; 857 - break; 858 - default: // idle 859 - message = `Pending check for ${verifierId}.`; 860 - icon = '⏳'; 861 - statusClass = 'verifier-idle-status'; 588 + case 'checking': message = `Checking ${verifierId}...`; icon = '⏳'; statusClass = 'verifier-checking-status'; break; 589 + case 'verified': message = `Verified by ${verifierId}.`; icon = '✅'; statusClass = 'verifier-verified-status'; break; 590 + case 'not_verified': message = `Not verified by ${verifierId}.`; icon = '❌'; statusClass = 'verifier-not-verified-status'; break; 591 + case 'error': message = `Error checking ${verifierId}.`; icon = '⚠️'; statusClass = 'verifier-error-status'; break; 592 + default: message = `Pending check for ${verifierId}.`; 862 593 } 863 - 864 - return ( 865 - <p key={verifierId} className={`verifier-official-verifier-note ${statusClass}`}> 866 - {icon} {message} 867 - </p> 868 - ); 594 + return (<p key={verifierId} className={`verifier-official-verifier-note ${statusClass}`}>{icon} {message}</p>); 869 595 })} 870 596 </div> 871 597 </div> 872 598 873 - {/* Updated section for Network Verifications */} 874 599 <div className="verifier-section"> 875 600 <div className="verifier-list-header"> 876 601 <h2>Who's Verified You?</h2> 877 - <button 878 - onClick={checkNetworkVerifications} 879 - disabled={isAnyOperationInProgress} 880 - className="verifier-action-button verifier-check-network-button" 881 - > 602 + <button onClick={checkNetworkVerifications} disabled={isAnyOperationInProgress} className="verifier-action-button verifier-check-network-button"> 882 603 {isLoadingNetwork ? 'Checking Network...' : 'Check Network Now'} 883 604 </button> 884 605 </div> 885 - 886 - {/* Display local status message */} 887 - {(isLoadingNetwork || networkStatusMessage) && ( 888 - <p className="verifier-network-status">{networkStatusMessage}</p> 889 - )} 890 - 606 + {(isLoadingNetwork || networkStatusMessage) && (<p className="verifier-network-status">{networkStatusMessage}</p>)} 891 607 {!isLoadingNetwork && networkChecked && ( 892 608 <div className="verifier-network-results"> 893 - {/* --- Mutuals Verified Me --- */} 894 - <p> 895 - {networkVerifications.mutualsVerifiedMe.length > 0 896 - ? `${networkVerifications.mutualsVerifiedMe.length} mutual(s) have verified you:` 897 - : "None of your mutuals have verified you yet."} 898 - </p> 899 - {networkVerifications.mutualsVerifiedMe.length > 0 && ( 900 - <ul className="verifier-verifier-list"> 901 - {networkVerifications.mutualsVerifiedMe.map(account => ( 902 - <li key={account.did}> 903 - {account.displayName} (@{account.handle}) 904 - </li> 905 - ))} 906 - </ul> 907 - )} 908 - 909 - {/* --- Follows Verified Me --- */} 910 - <p style={{marginTop: '15px'}}> 911 - {networkVerifications.followsVerifiedMe.length > 0 912 - ? `${networkVerifications.followsVerifiedMe.length} account(s) you follow have verified you:` 913 - : "None of the accounts you follow have verified you yet."} 914 - </p> 915 - {networkVerifications.followsVerifiedMe.length > 0 && ( 916 - <ul className="verifier-verifier-list"> 917 - {networkVerifications.followsVerifiedMe.map(account => ( 918 - <li key={account.did}> 919 - {account.displayName} (@{account.handle}) 920 - </li> 921 - ))} 922 - </ul> 923 - )} 924 - 925 - {/* --- Additional Context - Verified Others --- */} 609 + <p>{networkVerifications.mutualsVerifiedMe.length > 0 ? `${networkVerifications.mutualsVerifiedMe.length} mutual(s) have verified you:` : "None of your mutuals have verified you yet."}</p> 610 + {networkVerifications.mutualsVerifiedMe.length > 0 && (<ul className="verifier-verifier-list">{networkVerifications.mutualsVerifiedMe.map(account => (<li key={account.did}>{account.displayName} (@{account.handle})</li>))}</ul>)} 611 + <p style={{marginTop: '15px'}}>{networkVerifications.followsVerifiedMe.length > 0 ? `${networkVerifications.followsVerifiedMe.length} account(s) you follow have verified you:` : "None of the accounts you follow have verified you yet."}</p> 612 + {networkVerifications.followsVerifiedMe.length > 0 && (<ul className="verifier-verifier-list">{networkVerifications.followsVerifiedMe.map(account => (<li key={account.did}>{account.displayName} (@{account.handle})</li>))}</ul>)} 926 613 <div className="verifier-additional-context"> 927 - <p> 928 - {networkVerifications.mutualsVerifiedAnyone} of your {networkVerifications.fetchedMutualsCount} fetched mutuals have verified others. 929 - </p> 930 - <p> 931 - {networkVerifications.followsVerifiedAnyone} of the {networkVerifications.fetchedFollowsCount} accounts you follow have verified others. 932 - </p> 614 + <p>{networkVerifications.mutualsVerifiedAnyone} of your {networkVerifications.fetchedMutualsCount} fetched mutuals have verified others.</p> 615 + <p>{networkVerifications.followsVerifiedAnyone} of the {networkVerifications.fetchedFollowsCount} accounts you follow have verified others.</p> 933 616 </div> 617 + {(() => { 618 + const statsText = `My verification stats: 934 619 935 - {/* --- Network Stats Share Link --- */} 936 - {(() => { // IIFE to encapsulate logic 937 - const statsText = `Here are my expanded verification stats:\n\n` + 938 - `${networkVerifications.mutualsVerifiedMe.length} of my mutuals have verified me\n` + 939 - `${networkVerifications.followsVerifiedMe.length} account(s) that I follow have verified me\n` + 940 - `${networkVerifications.mutualsVerifiedAnyone} of my ${networkVerifications.fetchedMutualsCount} mutuals have verified others\n` + 941 - `${networkVerifications.followsVerifiedAnyone} of the ${networkVerifications.fetchedFollowsCount} accounts I follow have verified others\n\n` + 942 - `See who in your network has verified you here: https://cred.blue/verify`; 620 + ${networkVerifications.mutualsVerifiedMe.length} mutuals verified me 621 + ${networkVerifications.followsVerifiedMe.length} follows verified me 622 + ${networkVerifications.mutualsVerifiedAnyone}/${networkVerifications.fetchedMutualsCount} mutuals verified others 623 + ${networkVerifications.followsVerifiedAnyone}/${networkVerifications.fetchedFollowsCount} follows verified others 624 + 625 + Check yours: https://cred.blue/verify`; 943 626 const encodedStatsText = encodeURIComponent(statsText); 944 627 const statsIntentUrl = `https://bsky.app/intent/compose?text=${encodedStatsText}`; 945 - 946 - return ( 947 - <a 948 - href={statsIntentUrl} 949 - target="_blank" 950 - rel="noopener noreferrer" 951 - className="verifier-share-stats-link" 952 - > 953 - Share your verification stats on Bluesky! 954 - </a> 955 - ); 628 + return (<a href={statsIntentUrl} target="_blank" rel="noopener noreferrer" className="verifier-share-stats-link">Share your stats!</a>); 956 629 })()} 957 - 958 630 </div> 959 631 )} 960 - {!isLoadingNetwork && !networkChecked && ( 961 - <p>Click "Check Network Now" to see verifications from your network.</p> 962 - )} 632 + {!isLoadingNetwork && !networkChecked && (<p>Click "Check Network Now" to see verifications from your network.</p>)} 963 633 </div> 964 634 965 - {/* List of verified accounts */} 966 635 <div className="verifier-section"> 967 636 <div className="verifier-list-header"> 968 637 <h2>Accounts You've Verified</h2> 969 - <button 970 - onClick={() => fetchVerifications()} 971 - disabled={isAnyOperationInProgress} 972 - className="verifier-action-button verifier-refresh-button" 973 - > 974 - Refresh List 975 - </button> 638 + <button onClick={fetchVerifications} disabled={isAnyOperationInProgress} className="verifier-action-button verifier-refresh-button">Refresh List</button> 976 639 </div> 977 - {isLoadingVerifications ? ( 978 - <p>Loading verifications...</p> 979 - ) : verifications.length === 0 ? ( 980 - <p>You haven't verified any accounts yet.</p> 981 - ) : ( 640 + {isLoadingVerifications ? (<p>Loading...</p>) : verifications.length === 0 ? (<p>You haven't verified any accounts.</p>) : ( 982 641 <ul className="verifier-list"> 983 642 {verifications.map((verification) => ( 984 - <li 985 - key={verification.uri} 986 - className={` 987 - verifier-list-item 988 - ${verification.validityChecked && !verification.isValid ? 'verifier-list-item-invalid' : ''} 989 - `} 990 - > 643 + <li key={verification.uri} className={`verifier-list-item ${verification.validityChecked && !verification.isValid ? 'verifier-list-item-invalid' : ''}`}> 991 644 <div className="verifier-list-item-content"> 992 645 <div style={{ fontWeight: 'bold' }}>{verification.displayName}</div> 993 646 <div className="verifier-list-item-handle">@{verification.handle}</div> 994 - <div className="verifier-list-item-date"> 995 - Verified: {new Date(verification.createdAt).toLocaleString()} 996 - </div> 997 - 647 + <div className="verifier-list-item-date">Verified: {new Date(verification.createdAt).toLocaleString()}</div> 998 648 {verification.validityChecked && !verification.isValid && ( 999 649 <div className="verifier-validity-warning"> 1000 - {verification.validityError ? ( 1001 - <p>⚠️ Could not verify current profile data</p> 1002 - ) : ( 1003 - <> 1004 - <p><strong>⚠️ Profile has changed since verification</strong></p> 1005 - <p> 1006 - <span>Current handle: @{verification.currentHandle}</span><br /> 1007 - <span>Current display name: {verification.currentDisplayName}</span> 1008 - </p> 1009 - </> 1010 - )} 650 + {verification.validityError ? (<p>⚠️ Couldn't check profile</p>) : (<><p><strong>⚠️ Profile changed</strong></p><p><span>Now: @{verification.currentHandle}</span><br /><span>Name: {verification.currentDisplayName}</span></p></>)} 1011 651 </div> 1012 652 )} 1013 653 </div> 1014 654 <div className="verifier-list-item-actions"> 1015 - <button 1016 - onClick={() => handleRevoke(verification)} 1017 - disabled={isAnyOperationInProgress} 1018 - className="verifier-revoke-button" 1019 - > 1020 - {isRevoking ? 'Revoking...' : 'Revoke Verification'} 655 + <button onClick={() => handleRevoke(verification)} disabled={isRevoking || isLoadingVerifications} className="verifier-revoke-button"> 656 + {isRevoking ? 'Revoking...' : 'Revoke'} 1021 657 </button> 1022 658 </div> 1023 659 </li>