From ad7b3510e4540ec8127039db80451bc8667390ce Mon Sep 17 00:00:00 2001 From: damedotblog Date: Wed, 23 Apr 2025 13:12:41 -0400 Subject: [PATCH] add bulk verification --- src/components/Verifier/Verifier.css | 60 +++++++ src/components/Verifier/Verifier.js | 248 ++++++++++++++++++++++++--- 2 files changed, 287 insertions(+), 21 deletions(-) diff --git a/src/components/Verifier/Verifier.css b/src/components/Verifier/Verifier.css index a20e8b0..b782dac 100644 --- a/src/components/Verifier/Verifier.css +++ b/src/components/Verifier/Verifier.css @@ -452,4 +452,64 @@ color: var(--text-muted); text-overflow: ellipsis; overflow: hidden; +} + +/* Styles for List Verification */ +.verifier-mode-toggle { + display: flex; + gap: 20px; /* Space between radio buttons */ + margin-bottom: 15px; /* Space below the toggle */ + padding-bottom: 15px; /* More space */ + border-bottom: 1px solid var(--card-border); /* Separator line */ +} + +.verifier-mode-toggle label { + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; /* Space between radio and text */ + color: var(--text); +} + +.verifier-mode-toggle input[type="radio"] { + cursor: pointer; + /* Optional: style the radio button itself */ +} + +.verifier-list-select { + flex-grow: 1; /* Take available space */ + border: 2px solid var(--card-border); + border-radius: 6px; + padding: 9px; + font-size: 1em; + background-color: var(--navbar-bg); + color: var(--text); + transition: all 0.3s ease; + font-family: inherit; /* Use main font */ + min-width: 200px; /* Ensure minimum width */ + margin: 0px; +} + +.verifier-list-select:hover, +.verifier-list-select:focus { + border-color: var(--button-bg); + background-color: var(--background); /* Match main app focus */ + outline: none; +} + +.verifier-list-select:disabled { + background-color: var(--button-disabled-bg, #cccccc); + cursor: not-allowed; + opacity: 0.7; +} + +.verifier-status-box-progress p { + margin-top: 5px; /* Space between main status and progress */ + margin-bottom: 0; +} + +.verifier-bulk-progress { + font-style: italic; + font-size: 0.9em; + color: var(--text-muted); } \ No newline at end of file diff --git a/src/components/Verifier/Verifier.js b/src/components/Verifier/Verifier.js index 8b1028c..a468367 100644 --- a/src/components/Verifier/Verifier.js +++ b/src/components/Verifier/Verifier.js @@ -140,6 +140,14 @@ function Verifier() { const debounceTimeoutRef = useRef(null); // Ref for debounce timer const suggestionListRef = useRef(null); // Ref for suggestion list to handle clicks outside + // State for list verification + const [verifyMode, setVerifyMode] = useState('single'); // 'single' or 'list' + const [userLists, setUserLists] = useState([]); + const [selectedListUri, setSelectedListUri] = useState(''); + const [isFetchingLists, setIsFetchingLists] = useState(false); + const [bulkVerifyStatus, setBulkVerifyStatus] = useState(''); // Status message for bulk operations + const [bulkVerifyProgress, setBulkVerifyProgress] = useState(''); // Progress indicator (e.g., "10/50") + useEffect(() => { if (session) { const agentInstance = new Agent(session); @@ -412,11 +420,48 @@ function Verifier() { } }, [agent, session, userInfo]); + // Function to fetch user's lists + const fetchUserLists = useCallback(async () => { + if (!agent || !session?.did) { + console.warn("fetchUserLists: Agent or session.did not available."); + return; + } + setIsFetchingLists(true); + setUserLists([]); // Clear previous lists + setStatusMessage(''); // Clear general status + setBulkVerifyStatus('Fetching your lists...'); // Use bulk status for list fetching message + try { + const lists = await fetchAllPaginated( + agent, // Pass the agent instance + agent.api.app.bsky.graph.getLists, // The method to call + { actor: session.did, limit: 100 }, // Initial parameters + false // Not using direct fetch here + ); + console.log(`Fetched ${lists.length} lists for user ${session.handle}`); + setUserLists(lists || []); + if (lists.length === 0) { + setBulkVerifyStatus('You have not created any lists yet.'); + } else { + setBulkVerifyStatus(''); // Clear status on success if lists were found + } + } catch (error) { + console.error('Failed to fetch user lists:', error); + setBulkVerifyStatus(`Failed to fetch lists: ${error.message || 'Unknown error'}`); + } finally { + setIsFetchingLists(false); + // Clear status if it was just 'Fetching...' and no error occurred but no lists found + if (!bulkVerifyStatus.includes('Failed') && !bulkVerifyStatus.includes('You have not created')) { + setBulkVerifyStatus(''); + } + } + }, [agent, session]); + useEffect(() => { if (agent) { fetchVerifications(); + fetchUserLists(); // Fetch lists when agent is ready } - }, [agent, fetchVerifications]); + }, [agent, fetchVerifications, fetchUserLists]); // Add fetchUserLists dependency const checkOfficialVerification = useCallback(async () => { if (!session?.did) return; @@ -625,6 +670,105 @@ function Verifier() { setShowSuggestions(false); }; + // Handler for verifying a list + const handleVerifyList = async (e) => { + e.preventDefault(); + if (!agent || !session || !selectedListUri) { + setStatusMessage('Please select a list to verify.'); + return; + } + + const selectedList = userLists.find(list => list.uri === selectedListUri); + if (!selectedList) { + setStatusMessage('Selected list not found.'); + return; + } + + setIsVerifying(true); + setBulkVerifyStatus(`Fetching members of list: ${selectedList.name}...`); + setBulkVerifyProgress(''); + setStatusMessage(''); // Clear single verify status + setRevokeStatusMessage(''); // Clear revoke status + + let successCount = 0; + let failureCount = 0; + let totalCount = 0; + const errors = []; + + try { + // Fetch all items from the selected list + const listItems = await fetchAllPaginated( + agent, + agent.api.app.bsky.graph.getList, + { list: selectedListUri, limit: 100 }, + false // Use agent method + ); + + totalCount = listItems.length; + setBulkVerifyStatus(`Found ${totalCount} members in list "${selectedList.name}". Starting verification...`); + + if (totalCount === 0) { + setBulkVerifyStatus(`List "${selectedList.name}" is empty. No users to verify.`); + setIsVerifying(false); + return; + } + + // Iterate and verify each user + for (let i = 0; i < listItems.length; i++) { + const item = listItems[i]; + const targetUser = item.subject; + const targetHandle = targetUser.handle; + const targetDid = targetUser.did; + const targetDisplayName = targetUser.displayName || targetHandle; + + setBulkVerifyProgress(`Verifying ${i + 1} of ${totalCount}: @${targetHandle}`); + + try { + const verificationRecord = { + $type: 'app.bsky.graph.verification', + subject: targetDid, + handle: targetHandle, // Store handle at time of verification + displayName: targetDisplayName, // Store displayName at time of verification + createdAt: new Date().toISOString(), + }; + + await agent.api.com.atproto.repo.createRecord({ + repo: session.did, + collection: 'app.bsky.graph.verification', + record: verificationRecord, + }); + successCount++; + } catch (error) { + console.error(`Failed to verify @${targetHandle} (DID: ${targetDid}):`, error); + failureCount++; + errors.push(`@${targetHandle}: ${error.message || 'Unknown error'}`); + // Decide if you want to stop on first error or continue + // continue; + } + } + + // Final status message + let finalMessage = `Bulk verification complete for list "${selectedList.name}". \n`; + finalMessage += `Successfully verified: ${successCount}. \n`; + if (failureCount > 0) { + finalMessage += `Failed: ${failureCount}. \n`; + // Consider showing detailed errors, maybe in console or a collapsible section + console.log("Bulk verification errors:", errors); + finalMessage += `Check console for details on failures.`; + } + setBulkVerifyStatus(finalMessage); + fetchVerifications(); // Refresh the list of verified accounts + setSelectedListUri(''); // Reset selection + + } catch (error) { + console.error('Failed to fetch or process list items:', error); + setBulkVerifyStatus(`Error during bulk verification for "${selectedList.name}": ${error.message || 'Unknown error'}`); + } finally { + setIsVerifying(false); + setBulkVerifyProgress(''); + } + }; + // Handler to hide suggestions when clicking outside useEffect(() => { const handleClickOutside = (event) => { @@ -672,25 +816,77 @@ function Verifier() {

Verify a Bluesky User

-

Enter the handle of the user you want to verify:

-
-
+

Enter the handle of the user you want to verify, or select a list to verify multiple users:

+ + {/* Mode Toggle */} +
+ + +
+ + {/* Conditional Input Area */} +
+ {verifyMode === 'single' ? ( +
+ + +
+ ) : ( +
+ + +
+ )} + + {/* Suggestions only shown in single mode */} + {verifyMode === 'single' && showSuggestions && (
    {isLoadingSuggestions ? (
  • Loading suggestions...
  • @@ -712,9 +908,19 @@ function Verifier() {
- {statusMessage && ( -
-

{statusMessage}

+ {/* Combined Status Area */} + {(statusMessage || bulkVerifyStatus || bulkVerifyProgress) && ( +
+ {/* Show single status OR bulk status, prioritizing bulk status if active */} + {bulkVerifyStatus ?

{bulkVerifyStatus}

: statusMessage ?

{statusMessage}

: null} + {/* Show bulk progress if available */} + {bulkVerifyProgress &&

{bulkVerifyProgress}

}
)} -- 2.51.2