This repository has no description
0

Configure Feed

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

add bulk revoke

+203 -30
+203 -30
src/components/Verifier/Verifier.js
··· 150 150 const [bulkVerifyStatus, setBulkVerifyStatus] = useState(''); // Status message for bulk operations 151 151 const [bulkVerifyProgress, setBulkVerifyProgress] = useState(''); // Progress indicator (e.g., "10/50") 152 152 153 + // State for list revocation 154 + const [revokeMode, setRevokeMode] = useState('single'); // 'single' or 'list' 155 + const [selectedListUriForRevoke, setSelectedListUriForRevoke] = useState(''); 156 + const [bulkRevokeStatus, setBulkRevokeStatus] = useState(''); // Status message for bulk revoke 157 + const [bulkRevokeProgress, setBulkRevokeProgress] = useState(''); // Progress for bulk revoke 158 + 153 159 useEffect(() => { 154 160 if (session) { 155 161 const agentInstance = new Agent(session); ··· 771 777 } 772 778 }; 773 779 780 + // Handler for revoking a list 781 + const handleRevokeList = async (e) => { 782 + e.preventDefault(); 783 + if (!agent || !session || !selectedListUriForRevoke) { 784 + setBulkRevokeStatus('Please select a list to revoke.'); 785 + return; 786 + } 787 + 788 + const selectedList = userLists.find(list => list.uri === selectedListUriForRevoke); 789 + if (!selectedList) { 790 + setBulkRevokeStatus('Selected list not found.'); 791 + return; 792 + } 793 + 794 + // Confirmation dialog 795 + if (!window.confirm(`Are you sure you want to revoke verifications for all users found in the list "${selectedList.name}"? This cannot be undone.`)) { 796 + return; 797 + } 798 + 799 + setIsRevoking(true); 800 + setBulkRevokeStatus(`Fetching members of list: ${selectedList.name}...`); 801 + setBulkRevokeProgress(''); 802 + setRevokeStatusMessage(''); // Clear single revoke status 803 + 804 + let successCount = 0; 805 + let failureCount = 0; 806 + let totalToRevoke = 0; 807 + const errors = []; 808 + 809 + try { 810 + // Fetch all items from the selected list 811 + const listItems = await fetchAllPaginated( 812 + agent.api.app.bsky.graph, 813 + 'getList', 814 + { list: selectedListUriForRevoke, limit: 100 }, 815 + false 816 + ); 817 + 818 + if (listItems.length === 0) { 819 + setBulkRevokeStatus(`List "${selectedList.name}" is empty. No users to check for revocation.`); 820 + setIsRevoking(false); 821 + return; 822 + } 823 + 824 + const listMemberDids = new Set(listItems.map(item => item.subject.did)); 825 + 826 + // Filter existing verifications to find those matching list members 827 + const verificationsToRevoke = verifications.filter(verification => 828 + listMemberDids.has(verification.subject) 829 + ); 830 + 831 + totalToRevoke = verificationsToRevoke.length; 832 + setBulkRevokeStatus(`Found ${totalToRevoke} existing verification(s) matching users in "${selectedList.name}". Starting revocation...`); 833 + 834 + if (totalToRevoke === 0) { 835 + setBulkRevokeStatus(`No existing verifications match users in the list "${selectedList.name}".`); 836 + setIsRevoking(false); 837 + return; 838 + } 839 + 840 + // Iterate and revoke each matching verification 841 + for (let i = 0; i < verificationsToRevoke.length; i++) { 842 + const verification = verificationsToRevoke[i]; 843 + const handle = verification.handle || verification.subject; // Use handle if available 844 + setBulkRevokeProgress(`Revoking ${i + 1} of ${totalToRevoke}: @${handle}`); 845 + 846 + try { 847 + const parts = verification.uri.split('/'); 848 + const rkey = parts[parts.length - 1]; 849 + 850 + await agent.api.com.atproto.repo.deleteRecord({ 851 + repo: session.did, 852 + collection: 'app.bsky.graph.verification', 853 + rkey: rkey 854 + }); 855 + successCount++; 856 + } catch (error) { 857 + console.error(`Failed to revoke @${handle} (URI: ${verification.uri}):`, error); 858 + failureCount++; 859 + errors.push(`@${handle}: ${error.message || 'Unknown error'}`); 860 + } 861 + } 862 + 863 + // Final status message 864 + let finalMessage = `Bulk revocation complete for list "${selectedList.name}". \n`; 865 + finalMessage += `Successfully revoked: ${successCount}. \n`; 866 + if (failureCount > 0) { 867 + finalMessage += `Failed: ${failureCount}. \n`; 868 + console.log("Bulk revocation errors:", errors); 869 + finalMessage += `Check console for details on failures.`; 870 + } 871 + setBulkRevokeStatus(finalMessage); 872 + fetchVerifications(); // Refresh the list of verified accounts 873 + setSelectedListUriForRevoke(''); // Reset selection 874 + 875 + } catch (error) { 876 + console.error('Failed to fetch or process list items for revocation:', error); 877 + setBulkRevokeStatus(`Error during bulk revocation for "${selectedList.name}": ${error.message || 'Unknown error'}`); 878 + } finally { 879 + setIsRevoking(false); 880 + setBulkRevokeProgress(''); 881 + } 882 + }; 883 + 774 884 // Handler to hide suggestions when clicking outside 775 885 useEffect(() => { 776 886 const handleClickOutside = (event) => { ··· 998 1108 <h2>Accounts You've Verified</h2> 999 1109 </div> 1000 1110 1001 - {revokeStatusMessage && ( 1002 - <div className={`verifier-status-box ${revokeStatusMessage.includes('failed') ? 'verifier-status-box-error' : 'verifier-status-box-success'}`}> 1003 - <p>{revokeStatusMessage}</p> 1111 + {/* Revoke Mode Toggle */} 1112 + <div className="verifier-mode-toggle"> 1113 + <label> 1114 + <input 1115 + type="radio" 1116 + name="revokeMode" 1117 + value="single" 1118 + checked={revokeMode === 'single'} 1119 + onChange={() => setRevokeMode('single')} 1120 + disabled={isRevoking || isFetchingLists} 1121 + /> 1122 + Manage Individual 1123 + </label> 1124 + <label> 1125 + <input 1126 + type="radio" 1127 + name="revokeMode" 1128 + value="list" 1129 + checked={revokeMode === 'list'} 1130 + onChange={() => setRevokeMode('list')} 1131 + disabled={isRevoking || isFetchingLists} 1132 + /> 1133 + Revoke by List 1134 + </label> 1135 + </div> 1136 + 1137 + {/* Combined Status Area for Revocation */} 1138 + {(revokeStatusMessage || bulkRevokeStatus || bulkRevokeProgress) && ( 1139 + <div className={`verifier-status-box 1140 + ${(revokeStatusMessage && revokeStatusMessage.includes('failed')) || 1141 + (bulkRevokeStatus && (bulkRevokeStatus.includes('failed') || bulkRevokeStatus.includes('Error'))) 1142 + ? 'verifier-status-box-error' 1143 + : 'verifier-status-box-success'} 1144 + ${bulkRevokeProgress ? ' verifier-status-box-progress' : ''} 1145 + `}> 1146 + {/* Show single status OR bulk status, prioritizing bulk status if active */} 1147 + {bulkRevokeStatus ? <p>{bulkRevokeStatus}</p> : revokeStatusMessage ? <p>{revokeStatusMessage}</p> : null} 1148 + {/* Show bulk progress if available */} 1149 + {bulkRevokeProgress && <p className="verifier-bulk-progress">{bulkRevokeProgress}</p>} 1004 1150 </div> 1005 1151 )} 1006 1152 1007 - {isLoadingVerifications ? (<p>Loading...</p>) : verifications.length === 0 ? (<p>You haven't verified any accounts.</p>) : ( 1008 - <ul className="verifier-list"> 1009 - {verifications.map((verification) => ( 1010 - <li key={verification.uri} className={`verifier-list-item ${verification.validityChecked && !verification.isValid ? 'verifier-list-item-invalid' : ''}`}> 1011 - <div className="verifier-list-item-content"> 1012 - <a href={`https://bsky.app/profile/${verification.handle}`} target="_blank" rel="noopener noreferrer" className="verifier-profile-link"> 1013 - <span className="verifier-display-name">{verification.displayName}</span> 1014 - <span className="verifier-list-item-handle">@{verification.handle}</span> 1015 - </a> 1016 - {verification.validityChecked && ( 1017 - <span className={`verifier-validity-status ${verification.isValid ? 'valid' : 'invalid'}`}> 1018 - {verification.isValid ? '✅ Valid' : '❌ Changed'} 1019 - </span> 1020 - )} 1021 - {!verification.validityChecked && isCheckingValidity && ( 1022 - <span className="verifier-validity-status checking">⏳ Checking...</span> 1023 - )} 1024 - <div className="verifier-list-item-date">Verified: {new Date(verification.createdAt).toLocaleString()}</div> 1025 - </div> 1026 - <div className="verifier-list-item-actions"> 1027 - <button onClick={() => handleRevoke(verification)} disabled={isRevoking || isLoadingVerifications} className="verifier-revoke-button"> 1028 - {(isRevoking && revokeStatusMessage.includes(verification.handle)) ? 'Revoking...' : 'Revoke'} 1029 - </button> 1030 - </div> 1031 - </li> 1032 - ))} 1033 - </ul> 1153 + {/* Conditional Revoke Area */} 1154 + {revokeMode === 'single' ? ( 1155 + <> {/* Use Fragment to avoid unnecessary divs */} 1156 + {isLoadingVerifications ? (<p>Loading...</p>) : verifications.length === 0 ? (<p>You haven't verified any accounts.</p>) : ( 1157 + <ul className="verifier-list"> 1158 + {verifications.map((verification) => ( 1159 + <li key={verification.uri} className={`verifier-list-item ${verification.validityChecked && !verification.isValid ? 'verifier-list-item-invalid' : ''}`}> 1160 + <div className="verifier-list-item-content"> 1161 + <a href={`https://bsky.app/profile/${verification.handle}`} target="_blank" rel="noopener noreferrer" className="verifier-profile-link"> 1162 + <span className="verifier-display-name">{verification.displayName}</span> 1163 + <span className="verifier-list-item-handle">@{verification.handle}</span> 1164 + </a> 1165 + {verification.validityChecked && ( 1166 + <span className={`verifier-validity-status ${verification.isValid ? 'valid' : 'invalid'}`}> 1167 + {verification.isValid ? '✅ Valid' : '❌ Changed'} 1168 + </span> 1169 + )} 1170 + {!verification.validityChecked && isCheckingValidity && ( 1171 + <span className="verifier-validity-status checking">⏳ Checking...</span> 1172 + )} 1173 + <div className="verifier-list-item-date">Verified: {new Date(verification.createdAt).toLocaleString()}</div> 1174 + </div> 1175 + <div className="verifier-list-item-actions"> 1176 + <button onClick={() => handleRevoke(verification)} disabled={isRevoking || isLoadingVerifications} className="verifier-revoke-button"> 1177 + {(isRevoking && revokeStatusMessage.includes(verification.handle)) ? 'Revoking...' : 'Revoke'} 1178 + </button> 1179 + </div> 1180 + </li> 1181 + ))} 1182 + </ul> 1183 + )} 1184 + </> 1185 + ) : ( 1186 + <div className="verifier-input-wrapper"> {/* Reuse wrapper for consistent spacing */} 1187 + <form onSubmit={handleRevokeList} className="verifier-form-container" style={{ marginBottom: 0 }}> 1188 + <select 1189 + value={selectedListUriForRevoke} 1190 + onChange={(e) => setSelectedListUriForRevoke(e.target.value)} 1191 + disabled={isRevoking || isFetchingLists || userLists.length === 0} 1192 + required 1193 + className="verifier-list-select" 1194 + > 1195 + <option value="" disabled>{isFetchingLists ? "Loading lists..." : userLists.length === 0 ? "No lists found" : "-- Select list to revoke --"}</option> 1196 + {userLists.map(list => ( 1197 + <option key={list.uri} value={list.uri}> 1198 + {list.name} ({list.listItemCount || 0} members) 1199 + </option> 1200 + ))} 1201 + </select> 1202 + <button type="submit" disabled={isRevoking || !selectedListUriForRevoke || isFetchingLists} className="verifier-revoke-button"> {/* Reuse revoke button style */} 1203 + {isRevoking ? 'Revoking List...' : 'Revoke Selected List'} 1204 + </button> 1205 + </form> 1206 + </div> 1034 1207 )} 1035 1208 </div> 1036 1209 </div>