This repository has no description
0

Configure Feed

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

add bulk verification

+287 -21
+60
src/components/Verifier/Verifier.css
··· 452 452 color: var(--text-muted); 453 453 text-overflow: ellipsis; 454 454 overflow: hidden; 455 + } 456 + 457 + /* Styles for List Verification */ 458 + .verifier-mode-toggle { 459 + display: flex; 460 + gap: 20px; /* Space between radio buttons */ 461 + margin-bottom: 15px; /* Space below the toggle */ 462 + padding-bottom: 15px; /* More space */ 463 + border-bottom: 1px solid var(--card-border); /* Separator line */ 464 + } 465 + 466 + .verifier-mode-toggle label { 467 + cursor: pointer; 468 + display: flex; 469 + align-items: center; 470 + gap: 5px; /* Space between radio and text */ 471 + color: var(--text); 472 + } 473 + 474 + .verifier-mode-toggle input[type="radio"] { 475 + cursor: pointer; 476 + /* Optional: style the radio button itself */ 477 + } 478 + 479 + .verifier-list-select { 480 + flex-grow: 1; /* Take available space */ 481 + border: 2px solid var(--card-border); 482 + border-radius: 6px; 483 + padding: 9px; 484 + font-size: 1em; 485 + background-color: var(--navbar-bg); 486 + color: var(--text); 487 + transition: all 0.3s ease; 488 + font-family: inherit; /* Use main font */ 489 + min-width: 200px; /* Ensure minimum width */ 490 + margin: 0px; 491 + } 492 + 493 + .verifier-list-select:hover, 494 + .verifier-list-select:focus { 495 + border-color: var(--button-bg); 496 + background-color: var(--background); /* Match main app focus */ 497 + outline: none; 498 + } 499 + 500 + .verifier-list-select:disabled { 501 + background-color: var(--button-disabled-bg, #cccccc); 502 + cursor: not-allowed; 503 + opacity: 0.7; 504 + } 505 + 506 + .verifier-status-box-progress p { 507 + margin-top: 5px; /* Space between main status and progress */ 508 + margin-bottom: 0; 509 + } 510 + 511 + .verifier-bulk-progress { 512 + font-style: italic; 513 + font-size: 0.9em; 514 + color: var(--text-muted); 455 515 }
+227 -21
src/components/Verifier/Verifier.js
··· 140 140 const debounceTimeoutRef = useRef(null); // Ref for debounce timer 141 141 const suggestionListRef = useRef(null); // Ref for suggestion list to handle clicks outside 142 142 143 + // State for list verification 144 + const [verifyMode, setVerifyMode] = useState('single'); // 'single' or 'list' 145 + const [userLists, setUserLists] = useState([]); 146 + const [selectedListUri, setSelectedListUri] = useState(''); 147 + const [isFetchingLists, setIsFetchingLists] = useState(false); 148 + const [bulkVerifyStatus, setBulkVerifyStatus] = useState(''); // Status message for bulk operations 149 + const [bulkVerifyProgress, setBulkVerifyProgress] = useState(''); // Progress indicator (e.g., "10/50") 150 + 143 151 useEffect(() => { 144 152 if (session) { 145 153 const agentInstance = new Agent(session); ··· 412 420 } 413 421 }, [agent, session, userInfo]); 414 422 423 + // Function to fetch user's lists 424 + const fetchUserLists = useCallback(async () => { 425 + if (!agent || !session?.did) { 426 + console.warn("fetchUserLists: Agent or session.did not available."); 427 + return; 428 + } 429 + setIsFetchingLists(true); 430 + setUserLists([]); // Clear previous lists 431 + setStatusMessage(''); // Clear general status 432 + setBulkVerifyStatus('Fetching your lists...'); // Use bulk status for list fetching message 433 + try { 434 + const lists = await fetchAllPaginated( 435 + agent, // Pass the agent instance 436 + agent.api.app.bsky.graph.getLists, // The method to call 437 + { actor: session.did, limit: 100 }, // Initial parameters 438 + false // Not using direct fetch here 439 + ); 440 + console.log(`Fetched ${lists.length} lists for user ${session.handle}`); 441 + setUserLists(lists || []); 442 + if (lists.length === 0) { 443 + setBulkVerifyStatus('You have not created any lists yet.'); 444 + } else { 445 + setBulkVerifyStatus(''); // Clear status on success if lists were found 446 + } 447 + } catch (error) { 448 + console.error('Failed to fetch user lists:', error); 449 + setBulkVerifyStatus(`Failed to fetch lists: ${error.message || 'Unknown error'}`); 450 + } finally { 451 + setIsFetchingLists(false); 452 + // Clear status if it was just 'Fetching...' and no error occurred but no lists found 453 + if (!bulkVerifyStatus.includes('Failed') && !bulkVerifyStatus.includes('You have not created')) { 454 + setBulkVerifyStatus(''); 455 + } 456 + } 457 + }, [agent, session]); 458 + 415 459 useEffect(() => { 416 460 if (agent) { 417 461 fetchVerifications(); 462 + fetchUserLists(); // Fetch lists when agent is ready 418 463 } 419 - }, [agent, fetchVerifications]); 464 + }, [agent, fetchVerifications, fetchUserLists]); // Add fetchUserLists dependency 420 465 421 466 const checkOfficialVerification = useCallback(async () => { 422 467 if (!session?.did) return; ··· 625 670 setShowSuggestions(false); 626 671 }; 627 672 673 + // Handler for verifying a list 674 + const handleVerifyList = async (e) => { 675 + e.preventDefault(); 676 + if (!agent || !session || !selectedListUri) { 677 + setStatusMessage('Please select a list to verify.'); 678 + return; 679 + } 680 + 681 + const selectedList = userLists.find(list => list.uri === selectedListUri); 682 + if (!selectedList) { 683 + setStatusMessage('Selected list not found.'); 684 + return; 685 + } 686 + 687 + setIsVerifying(true); 688 + setBulkVerifyStatus(`Fetching members of list: ${selectedList.name}...`); 689 + setBulkVerifyProgress(''); 690 + setStatusMessage(''); // Clear single verify status 691 + setRevokeStatusMessage(''); // Clear revoke status 692 + 693 + let successCount = 0; 694 + let failureCount = 0; 695 + let totalCount = 0; 696 + const errors = []; 697 + 698 + try { 699 + // Fetch all items from the selected list 700 + const listItems = await fetchAllPaginated( 701 + agent, 702 + agent.api.app.bsky.graph.getList, 703 + { list: selectedListUri, limit: 100 }, 704 + false // Use agent method 705 + ); 706 + 707 + totalCount = listItems.length; 708 + setBulkVerifyStatus(`Found ${totalCount} members in list "${selectedList.name}". Starting verification...`); 709 + 710 + if (totalCount === 0) { 711 + setBulkVerifyStatus(`List "${selectedList.name}" is empty. No users to verify.`); 712 + setIsVerifying(false); 713 + return; 714 + } 715 + 716 + // Iterate and verify each user 717 + for (let i = 0; i < listItems.length; i++) { 718 + const item = listItems[i]; 719 + const targetUser = item.subject; 720 + const targetHandle = targetUser.handle; 721 + const targetDid = targetUser.did; 722 + const targetDisplayName = targetUser.displayName || targetHandle; 723 + 724 + setBulkVerifyProgress(`Verifying ${i + 1} of ${totalCount}: @${targetHandle}`); 725 + 726 + try { 727 + const verificationRecord = { 728 + $type: 'app.bsky.graph.verification', 729 + subject: targetDid, 730 + handle: targetHandle, // Store handle at time of verification 731 + displayName: targetDisplayName, // Store displayName at time of verification 732 + createdAt: new Date().toISOString(), 733 + }; 734 + 735 + await agent.api.com.atproto.repo.createRecord({ 736 + repo: session.did, 737 + collection: 'app.bsky.graph.verification', 738 + record: verificationRecord, 739 + }); 740 + successCount++; 741 + } catch (error) { 742 + console.error(`Failed to verify @${targetHandle} (DID: ${targetDid}):`, error); 743 + failureCount++; 744 + errors.push(`@${targetHandle}: ${error.message || 'Unknown error'}`); 745 + // Decide if you want to stop on first error or continue 746 + // continue; 747 + } 748 + } 749 + 750 + // Final status message 751 + let finalMessage = `Bulk verification complete for list "${selectedList.name}". \n`; 752 + finalMessage += `Successfully verified: ${successCount}. \n`; 753 + if (failureCount > 0) { 754 + finalMessage += `Failed: ${failureCount}. \n`; 755 + // Consider showing detailed errors, maybe in console or a collapsible section 756 + console.log("Bulk verification errors:", errors); 757 + finalMessage += `Check console for details on failures.`; 758 + } 759 + setBulkVerifyStatus(finalMessage); 760 + fetchVerifications(); // Refresh the list of verified accounts 761 + setSelectedListUri(''); // Reset selection 762 + 763 + } catch (error) { 764 + console.error('Failed to fetch or process list items:', error); 765 + setBulkVerifyStatus(`Error during bulk verification for "${selectedList.name}": ${error.message || 'Unknown error'}`); 766 + } finally { 767 + setIsVerifying(false); 768 + setBulkVerifyProgress(''); 769 + } 770 + }; 771 + 628 772 // Handler to hide suggestions when clicking outside 629 773 useEffect(() => { 630 774 const handleClickOutside = (event) => { ··· 672 816 673 817 <div className="verifier-section"> 674 818 <h2>Verify a Bluesky User</h2> 675 - <p>Enter the handle of the user you want to verify:</p> 676 - <div className="verifier-input-wrapper"> 677 - <form onSubmit={handleVerify} className="verifier-form-container" style={{ marginBottom: 0 }}> 819 + <p>Enter the handle of the user you want to verify, or select a list to verify multiple users:</p> 820 + 821 + {/* Mode Toggle */} 822 + <div className="verifier-mode-toggle"> 823 + <label> 678 824 <input 679 - type="text" 680 - value={targetHandle} 681 - onChange={handleInputChange} 682 - onFocus={handleInputFocus} 683 - placeholder="username.bsky.social" 684 - disabled={isVerifying || isRevoking || isLoadingVerifications || isLoadingNetwork || isCheckingValidity} 685 - required 686 - className="verifier-input-field" 687 - autoComplete="off" 825 + type="radio" 826 + name="verifyMode" 827 + value="single" 828 + checked={verifyMode === 'single'} 829 + onChange={() => setVerifyMode('single')} 830 + disabled={isVerifying || isFetchingLists} 688 831 /> 689 - <button type="submit" disabled={isVerifying || !targetHandle} className="verifier-submit-button"> 690 - {isVerifying ? 'Verifying...' : 'Verify Account'} 691 - </button> 692 - </form> 693 - {showSuggestions && ( 832 + Verify Single User 833 + </label> 834 + <label> 835 + <input 836 + type="radio" 837 + name="verifyMode" 838 + value="list" 839 + checked={verifyMode === 'list'} 840 + onChange={() => setVerifyMode('list')} 841 + disabled={isVerifying || isFetchingLists} 842 + /> 843 + Verify List 844 + </label> 845 + </div> 846 + 847 + {/* Conditional Input Area */} 848 + <div className="verifier-input-wrapper"> 849 + {verifyMode === 'single' ? ( 850 + <form onSubmit={handleVerify} className="verifier-form-container" style={{ marginBottom: 0 }}> 851 + <input 852 + type="text" 853 + value={targetHandle} 854 + onChange={handleInputChange} 855 + onFocus={handleInputFocus} 856 + placeholder="username.bsky.social" 857 + disabled={isVerifying || isRevoking || isLoadingVerifications || isLoadingNetwork || isCheckingValidity || isFetchingLists} 858 + required 859 + className="verifier-input-field" 860 + autoComplete="off" 861 + /> 862 + <button type="submit" disabled={isVerifying || !targetHandle} className="verifier-submit-button"> 863 + {isVerifying ? 'Verifying...' : 'Verify Account'} 864 + </button> 865 + </form> 866 + ) : ( 867 + <form onSubmit={handleVerifyList} className="verifier-form-container" style={{ marginBottom: 0 }}> 868 + <select 869 + value={selectedListUri} 870 + onChange={(e) => setSelectedListUri(e.target.value)} 871 + disabled={isVerifying || isFetchingLists || userLists.length === 0} 872 + required 873 + className="verifier-list-select" 874 + > 875 + <option value="" disabled>{isFetchingLists ? "Loading lists..." : userLists.length === 0 ? "No lists found" : "-- Select a list --"}</option> 876 + {userLists.map(list => ( 877 + <option key={list.uri} value={list.uri}> 878 + {list.name} ({list.listItemCount || 0} members) 879 + </option> 880 + ))} 881 + </select> 882 + <button type="submit" disabled={isVerifying || !selectedListUri || isFetchingLists} className="verifier-submit-button"> 883 + {isVerifying ? 'Verifying List...' : 'Verify Selected List'} 884 + </button> 885 + </form> 886 + )} 887 + 888 + {/* Suggestions only shown in single mode */} 889 + {verifyMode === 'single' && showSuggestions && ( 694 890 <ul className="verifier-suggestions-list" ref={suggestionListRef}> 695 891 {isLoadingSuggestions ? ( 696 892 <li className="verifier-suggestion-item loading">Loading suggestions...</li> ··· 712 908 </div> 713 909 </div> 714 910 715 - {statusMessage && ( 716 - <div className={`verifier-status-box ${typeof statusMessage === 'string' && (statusMessage.includes('failed') || statusMessage.includes('Error')) ? 'verifier-status-box-error' : 'verifier-status-box-success'}`}> 717 - <p>{statusMessage}</p> 911 + {/* Combined Status Area */} 912 + {(statusMessage || bulkVerifyStatus || bulkVerifyProgress) && ( 913 + <div className={`verifier-status-box 914 + ${(statusMessage && (statusMessage.includes('failed') || statusMessage.includes('Error'))) || 915 + (bulkVerifyStatus && (bulkVerifyStatus.includes('failed') || bulkVerifyStatus.includes('Error'))) 916 + ? 'verifier-status-box-error' 917 + : 'verifier-status-box-success'} 918 + ${bulkVerifyProgress ? ' verifier-status-box-progress' : ''} 919 + `}> 920 + {/* Show single status OR bulk status, prioritizing bulk status if active */} 921 + {bulkVerifyStatus ? <p>{bulkVerifyStatus}</p> : statusMessage ? <p>{statusMessage}</p> : null} 922 + {/* Show bulk progress if available */} 923 + {bulkVerifyProgress && <p className="verifier-bulk-progress">{bulkVerifyProgress}</p>} 718 924 </div> 719 925 )} 720 926