···
108
108
flex-wrap: wrap; /* Allow wrapping */
109
109
padding: 0px;
110
110
border: 0px;
111
111
-
position: relative; /* Added for suggestion list positioning */
111
111
+
/* position: relative; */ /* Removed - no longer needed */
112
112
}
113
113
114
114
.verifier-input-field {
···
123
123
font-family: inherit; /* Use main font */
124
124
min-width: 200px; /* Ensure minimum width */
125
125
margin: 0px;
126
126
+
text-align: left;
126
127
}
127
128
128
129
.verifier-input-field:hover,
···
185
186
}
186
187
187
188
.verifier-list {
189
189
+
list-style: none;
190
190
+
padding: 0;
191
191
+
margin: 0; /* Reset default ul margins */
188
192
margin-top: 15px;
193
193
+
width: 100%; /* Added */
194
194
+
box-sizing: border-box; /* Added */
189
195
}
190
196
191
197
.verifier-list,
···
205
211
margin-bottom: 10px;
206
212
flex-wrap: wrap; /* Allow actions to wrap */
207
213
gap: 10px;
214
214
+
width: 100%; /* Added */
215
215
+
box-sizing: border-box; /* Added */
208
216
}
209
217
.verifier-list-item-content {
210
218
flex-grow: 1;
···
1
1
-
import React, { useState, useEffect, useRef, useCallback } from 'react';
1
1
+
import React, { useState, useEffect, useCallback } from 'react';
2
2
import { useAuth } from '../../contexts/AuthContext';
3
3
import { Agent } from '@atproto/api';
4
4
import './Verifier.css';
···
108
108
}
109
109
}
110
110
111
111
-
// --- Debounce Hook ---
112
112
-
function useDebounce(value, delay) {
113
113
-
const [debouncedValue, setDebouncedValue] = useState(value);
114
114
-
115
115
-
useEffect(() => {
116
116
-
const handler = setTimeout(() => {
117
117
-
setDebouncedValue(value);
118
118
-
}, delay);
119
119
-
return () => {
120
120
-
clearTimeout(handler);
121
121
-
};
122
122
-
}, [value, delay]);
123
123
-
124
124
-
return debouncedValue;
125
125
-
}
126
126
-
// --- End Debounce Hook ---
127
127
-
128
111
// Renamed component to Verifier
129
112
function Verifier() {
130
113
// Use the main app's AuthContext
···
151
134
const [isCheckingValidity, setIsCheckingValidity] = useState(false);
152
135
const [networkStatusMessage, setNetworkStatusMessage] = useState('');
153
136
const [officialVerifiersStatus, setOfficialVerifiersStatus] = useState({});
154
154
-
155
155
-
// --- Autocomplete State ---
156
156
-
const [suggestions, setSuggestions] = useState([]);
157
157
-
const [isFetchingSuggestions, setIsFetchingSuggestions] = useState(false);
158
158
-
const [showSuggestions, setShowSuggestions] = useState(false);
159
159
-
const debouncedSearchTerm = useDebounce(targetHandle, 300);
160
160
-
const suggestionsRef = useRef(null);
161
161
-
const inputRef = useRef(null);
162
162
-
// --- End Autocomplete State ---
163
137
164
138
useEffect(() => {
165
139
if (session) {
···
527
501
}
528
502
}, [session, checkOfficialVerification]);
529
503
530
530
-
const fetchSuggestions = useCallback(async (query) => {
531
531
-
if (!query || query.trim().length < 2) {
532
532
-
setSuggestions([]);
533
533
-
return;
534
534
-
}
535
535
-
setIsFetchingSuggestions(true);
536
536
-
try {
537
537
-
// *** Use direct fetch ***
538
538
-
const url = new URL('https://public.api.bsky.app/xrpc/app.bsky.actor.searchActorsTypeahead');
539
539
-
url.searchParams.append('q', query);
540
540
-
url.searchParams.append('limit', '5');
541
541
-
const response = await fetch(url.toString());
542
542
-
if (!response.ok) throw new Error(`Suggestions fetch failed: ${response.status}`);
543
543
-
const data = await response.json();
544
544
-
setSuggestions(data.actors || []);
545
545
-
} catch (error) {
546
546
-
console.error('Failed to fetch suggestions:', error);
547
547
-
setSuggestions([]);
548
548
-
} finally {
549
549
-
setIsFetchingSuggestions(false);
550
550
-
}
551
551
-
}, []);
552
552
-
553
553
-
useEffect(() => {
554
554
-
if (debouncedSearchTerm && showSuggestions) {
555
555
-
fetchSuggestions(debouncedSearchTerm);
556
556
-
} else if (!debouncedSearchTerm) {
557
557
-
setSuggestions([]);
558
558
-
}
559
559
-
}, [debouncedSearchTerm, fetchSuggestions, showSuggestions]);
560
560
-
561
561
-
useEffect(() => {
562
562
-
function handleClickOutside(event) {
563
563
-
if (suggestionsRef.current && !suggestionsRef.current.contains(event.target) &&
564
564
-
inputRef.current && !inputRef.current.contains(event.target)) {
565
565
-
setShowSuggestions(false);
566
566
-
}
567
567
-
}
568
568
-
document.addEventListener("mousedown", handleClickOutside);
569
569
-
return () => document.removeEventListener("mousedown", handleClickOutside);
570
570
-
}, [suggestionsRef, inputRef]);
571
571
-
572
504
const handleVerify = async (e) => {
573
505
e.preventDefault();
574
506
if (!agent || !session) return;
···
576
508
setIsVerifying(true);
577
509
setStatusMessage(`Verifying ${targetHandle}...`);
578
510
setRevokeStatusMessage('');
579
579
-
setShowSuggestions(false);
580
511
try {
581
512
const profileRes = await agent.api.app.bsky.actor.getProfile({ actor: targetHandle });
582
513
const targetDid = profileRes.data.did;
···
633
564
}
634
565
};
635
566
636
636
-
const handleSuggestionClick = (handle) => {
637
637
-
setTargetHandle(handle);
638
638
-
setSuggestions([]);
639
639
-
setShowSuggestions(false);
640
640
-
inputRef.current?.focus();
641
641
-
};
642
642
-
643
567
// Handle loading and error states
644
568
if (isAuthLoading) return <p>Loading authentication...</p>;
645
569
if (authError) return <p>Authentication Error: {authError}. <a href="/login">Please login</a>.</p>;
···
674
598
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".
675
599
</p>
676
600
<p className="verifier-intro-text">
677
677
-
Try verifying an account for yourself or check to see who has verified you! It's as simple as creating a verification record in your PDS that points to the account you want to verify.
601
601
+
Try verifying an account for yourself or check to see who has verified you! It's as simple as creating a verification record in your PDS that points to the account you want to verify. The record looks like this:
602
602
+
</p>
603
603
+
<p>
604
604
+
app.bsky.graph.verification
678
605
</p>
679
606
</div>
680
607
···
684
611
<p>Enter the handle of the user you want to verify (e.g., targetuser.bsky.social):</p>
685
612
<form onSubmit={handleVerify} className="verifier-form-container" style={{ marginBottom: 0 }}>
686
613
<input
687
687
-
ref={inputRef}
688
614
type="text"
689
615
value={targetHandle}
690
690
-
onChange={(e) => {
691
691
-
const newValue = e.target.value;
692
692
-
setTargetHandle(newValue);
693
693
-
setShowSuggestions(newValue.length >= 2);
694
694
-
if(newValue.length < 2) setSuggestions([]);
695
695
-
}}
696
696
-
onFocus={() => { if (targetHandle.length >= 2) setShowSuggestions(true); }}
616
616
+
onChange={(e) => setTargetHandle(e.target.value)}
697
617
placeholder="targetuser.bsky.social"
698
618
disabled={isAnyOperationInProgress}
699
619
required
···
704
624
{isVerifying ? 'Verifying...' : 'Verify Account'}
705
625
</button>
706
626
</form>
707
707
-
{showSuggestions && (suggestions.length > 0 || isFetchingSuggestions) && (
708
708
-
<ul className="autocomplete-items" ref={suggestionsRef}>
709
709
-
{isFetchingSuggestions && suggestions.length === 0 ? (
710
710
-
<li className="autocomplete-item">Loading...</li>
711
711
-
) : (
712
712
-
suggestions.map((actor) => (
713
713
-
<li key={actor.did} className="autocomplete-item" onMouseDown={(e) => { e.preventDefault(); handleSuggestionClick(actor.handle); }}>
714
714
-
<img src={actor.avatar} alt="" />
715
715
-
<span className="verifier-suggestion-display-name">{actor.displayName || actor.handle}</span>
716
716
-
<span className="verifier-suggestion-handle">@{actor.handle}</span>
717
717
-
</li>
718
718
-
))
719
719
-
)}
720
720
-
{suggestions.length === 0 && !isFetchingSuggestions && targetHandle.length >= 2 && (
721
721
-
<li className="autocomplete-item">No users found matching "{targetHandle}"</li>
722
722
-
)}
723
723
-
</ul>
724
724
-
)}
725
627
</div>
726
628
727
629
{statusMessage && (