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