A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

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

at trunk 4.0 kB View raw
1import { useEffect, useRef, useState } from 'react'; 2import { createOAuthClient } from '../lib/auth/oauth'; 3import { isValidAccountInput, isValidHandleOrDid, normalizeHandle } from '../lib/auth/config'; 4import { lookupActor, type ActorPreview } from '../lib/landing/actor-lookup'; 5 6/** 7 * The landing page's primary CTA (client-only). As you type a handle it debounces and 8 * looks you up on the public Bluesky AppView, showing a confirmation card. Submitting 9 * hands the raw value to the existing OAuth flow — Start ALWAYS works; the lookup is 10 * delightful reassurance, never a gate (Decision: brainstorm 2026-06-09). 11 */ 12export default function HandleStart() { 13 const [ value, setValue ] = useState( '' ); 14 const [ preview, setPreview ] = useState< ActorPreview | null >( null ); 15 const [ looking, setLooking ] = useState( false ); 16 const [ error, setError ] = useState< string | null >( null ); 17 const [ avatarOk, setAvatarOk ] = useState( true ); 18 const reqId = useRef( 0 ); 19 20 // Debounced live lookup. Each keystroke cancels the pending timer; a request id 21 // guards against out-of-order responses overwriting a newer query. 22 useEffect( () => { 23 const trimmed = value.trim(); 24 if ( ! isValidHandleOrDid( trimmed ) ) { 25 // Invalidate any in-flight lookup so a late response can't repopulate the card. 26 ++reqId.current; 27 setPreview( null ); 28 setLooking( false ); 29 return; 30 } 31 const id = ++reqId.current; 32 setLooking( true ); 33 const timer = setTimeout( async () => { 34 const found = await lookupActor( trimmed ); 35 if ( id === reqId.current ) { 36 setPreview( found ); 37 setAvatarOk( true ); 38 setLooking( false ); 39 } 40 }, 350 ); 41 return () => clearTimeout( timer ); 42 }, [ value ] ); 43 44 async function onSubmit( event: React.FormEvent ) { 45 event.preventDefault(); 46 if ( ! isValidAccountInput( value ) ) { 47 setError( 'Enter a handle (alice.bsky.social), a DID, or a PDS URL.' ); 48 return; 49 } 50 setError( null ); 51 try { 52 const client = await createOAuthClient(); 53 // Redirects to the authorization server; the promise never resolves. 54 await client.signIn( normalizeHandle( value ) ); 55 } catch ( err ) { 56 setError( err instanceof Error ? err.message : String( err ) ); 57 } 58 } 59 60 const name = preview?.displayName ?? preview?.handle ?? null; 61 62 return ( 63 <form className="handlestart" onSubmit={ onSubmit }> 64 <div className="handlestart__row"> 65 <div className="handlestart__field"> 66 <span className="handlestart__at" aria-hidden="true">@</span> 67 <input 68 className="handlestart__input" 69 name="handle" 70 autoComplete="username" 71 autoCapitalize="none" 72 autoCorrect="off" 73 spellCheck={ false } 74 placeholder="you.bsky.social" 75 aria-label="Your handle, DID, or PDS URL" 76 value={ value } 77 onChange={ ( e ) => setValue( e.target.value ) } 78 /> 79 { looking && <span className="handlestart__spinner" aria-hidden="true" /> } 80 </div> 81 <button className="handlestart__go" type="submit">Start &rarr;</button> 82 </div> 83 84 { preview && ( 85 <div className="handlestart__card" aria-live="polite"> 86 { preview.avatar && avatarOk ? ( 87 <img 88 className="handlestart__avatar" 89 src={ preview.avatar } 90 alt="" 91 width={ 36 } 92 height={ 36 } 93 onError={ () => setAvatarOk( false ) } 94 /> 95 ) : ( 96 <span className="handlestart__avatar handlestart__avatar--fallback" aria-hidden="true"> 97 { ( name ?? '?' ).charAt( 0 ).toUpperCase() } 98 </span> 99 ) } 100 <span className="handlestart__who"> 101 <span className="handlestart__name">{ name }</span> 102 <span className="handlestart__handle">@{ preview.handle }</span> 103 </span> 104 <span className="handlestart__check" aria-hidden="true">&#10003;</span> 105 </div> 106 ) } 107 108 { error ? ( 109 <p className="handlestart__hint handlestart__hint--error" role="alert">{ error }</p> 110 ) : ( 111 <p className="handlestart__hint">Your handle, DID, or PDS whatever you&rsquo;ve got.</p> 112 ) } 113 </form> 114 ); 115}