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.1 kB View raw
1import { useState } from 'react'; 2import { useAuth } from '../lib/auth/useAuth'; 3import { displayNameFor, authorPath } from '../lib/auth/profile'; 4import { appBarNav, type AppBarContext } from '../lib/auth/nav'; 5import { skypressMark } from '../lib/brand/skypress-mark'; 6 7/** SkyPress mark — shares the inline-SVG source with Logo.astro (Astro components 8 * can't render in a React island). `currentColor` lets it follow the --sun token. */ 9function LogoMark() { 10 return ( 11 <span 12 className="app-bar__mark" 13 // eslint-disable-next-line react/no-danger -- static, app-authored SVG markup; no user input 14 dangerouslySetInnerHTML={ { __html: skypressMark( 24 ) } } 15 /> 16 ); 17} 18 19function NavIcon( { name }: { name: 'feather' | 'publications' } ) { 20 if ( name === 'feather' ) { 21 return ( 22 <svg className="app-bar__navicon" viewBox="0 0 24 24" width={ 18 } height={ 18 } fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> 23 <path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z" /> 24 <line x1="16" y1="8" x2="2" y2="22" /> 25 <line x1="17.5" y1="15" x2="9" y2="15" /> 26 </svg> 27 ); 28 } 29 return ( 30 <svg className="app-bar__navicon" viewBox="0 0 24 24" width={ 18 } height={ 18 } fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"> 31 <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" /> 32 <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" /> 33 </svg> 34 ); 35} 36 37/** 38 * The shared top bar for the editor + dashboard islands. Logo on the left; 39 * contextual nav + account + sign-out on the right. Rendered inside AuthProvider 40 * in every auth state: logo-only while loading or signed out, + contextual nav, 41 * account, and sign-out once signed in. The cross-link to Publications / Write is 42 * gated on auth so signed-out visitors aren't pointed at editor-only routes. 43 */ 44export default function AppBar( { current }: { current: AppBarContext } ) { 45 const { status, handle, displayName, avatar, did, signOut } = useAuth(); 46 const [ avatarOk, setAvatarOk ] = useState( true ); 47 const nav = appBarNav( current ); 48 const signedIn = status === 'signed-in' && Boolean( did ); 49 50 const viewerName = did 51 ? displayNameFor( { did, handle, displayName, avatar } ) 52 : ''; 53 const profileHref = authorPath( handle ); 54 55 return ( 56 <header className="app-bar"> 57 <a className="app-bar__home" href="/" aria-label="SkyPress home"> 58 <LogoMark /> 59 <span className="app-bar__word">SkyPress</span> 60 </a> 61 62 <span className="app-bar__spacer" /> 63 64 { signedIn && ( 65 <a className="app-bar__nav" href={ nav.href }> 66 <NavIcon name={ nav.icon } /> 67 { nav.label } 68 </a> 69 ) } 70 71 { signedIn && ( 72 <> 73 <IdentityBlock 74 href={ profileHref } 75 name={ viewerName } 76 handle={ handle } 77 avatar={ avatar } 78 avatarOk={ avatarOk } 79 onAvatarError={ () => setAvatarOk( false ) } 80 /> 81 <button type="button" className="app-bar__signout" onClick={ () => void signOut() }> 82 Sign out 83 </button> 84 </> 85 ) } 86 </header> 87 ); 88} 89 90/** Avatar + name + @handle. The whole block links to the public author page when a handle is known. */ 91function IdentityBlock( { 92 href, 93 name, 94 handle, 95 avatar, 96 avatarOk, 97 onAvatarError, 98}: { 99 href: string | null; 100 name: string; 101 handle: string | null; 102 avatar: string | null; 103 avatarOk: boolean; 104 onAvatarError: () => void; 105} ) { 106 const inner = ( 107 <> 108 { avatar && avatarOk ? ( 109 <img 110 className="app-bar__avatar" 111 src={ avatar } 112 alt="" 113 width={ 30 } 114 height={ 30 } 115 onError={ onAvatarError } 116 /> 117 ) : ( 118 <span className="app-bar__avatar app-bar__avatar--fallback" aria-hidden="true"> 119 { name.charAt( 0 ).toUpperCase() } 120 </span> 121 ) } 122 <span className="app-bar__who"> 123 <strong className="app-bar__name">{ name }</strong> 124 { handle && <span className="app-bar__handle">@{ handle }</span> } 125 </span> 126 </> 127 ); 128 129 return href ? ( 130 <a className="app-bar__identity" href={ href }> 131 { inner } 132 </a> 133 ) : ( 134 <span className="app-bar__identity">{ inner }</span> 135 ); 136}