Highly ambitious ATProtocol AppView service and sdks
at main 145 lines 4.1 kB view raw
1import { Link } from "react-router-dom"; 2import { Suspense } from "react"; 3import { graphql, useLazyLoadQuery } from "react-relay"; 4import { Logo } from "./Logo.tsx"; 5import { Avatar } from "./Avatar.tsx"; 6import { Button } from "./Button.tsx"; 7import { logout, useSessionContext } from "../lib/useSession.ts"; 8import type { LayoutQuery } from "../__generated__/LayoutQuery.graphql.ts"; 9 10interface LayoutProps { 11 children: React.ReactNode; 12 headerChart?: React.ReactNode; 13 subNav?: React.ReactNode; 14} 15 16function UserProfile({ handle }: { handle: string }) { 17 const data = useLazyLoadQuery<LayoutQuery>( 18 graphql` 19 query LayoutQuery($where: NetworkSlicesActorProfileWhereInput) { 20 networkSlicesActorProfiles(first: 1, where: $where) { 21 edges { 22 node { 23 actorHandle 24 avatar { 25 url(preset: "avatar") 26 } 27 } 28 } 29 } 30 } 31 `, 32 { 33 where: { 34 actorHandle: { eq: handle }, 35 }, 36 }, 37 ); 38 39 const profile = data.networkSlicesActorProfiles.edges[0]?.node; 40 41 return ( 42 <> 43 <Link 44 to={`/profile/${handle}`} 45 className="flex items-center gap-2 px-2 py-1 text-zinc-400 hover:text-zinc-300 transition-colors" 46 > 47 <Avatar 48 src={profile?.avatar?.url} 49 alt={handle} 50 size="sm" 51 /> 52 <span>@{handle}</span> 53 </Link> 54 <Link 55 to="/docs" 56 className="px-2 py-1 text-zinc-400 hover:text-zinc-300 transition-colors" 57 > 58 Docs 59 </Link> 60 <Button 61 type="button" 62 onClick={logout} 63 variant="link" 64 size="sm" 65 > 66 Sign Out 67 </Button> 68 </> 69 ); 70} 71 72export default function Layout({ 73 children, 74 headerChart, 75 subNav, 76}: LayoutProps) { 77 const { session } = useSessionContext(); 78 const isAuthenticated = session?.authenticated; 79 const userInfo = session?.user; 80 81 return ( 82 <div className="min-h-screen bg-zinc-950 text-zinc-300 font-mono"> 83 <div className="max-w-4xl mx-auto px-6 py-12"> 84 <div className="border-b border-zinc-800 pb-4 relative"> 85 {headerChart && ( 86 <div className="absolute inset-0 pointer-events-none opacity-40"> 87 {headerChart} 88 </div> 89 )} 90 <div className="flex items-end justify-between relative"> 91 <Link 92 to="/" 93 className="flex items-center gap-3 hover:opacity-80 transition-opacity" 94 > 95 <Logo className="w-10 h-10" /> 96 <div> 97 <h1 className="text-xs font-medium uppercase tracking-wider text-zinc-500"> 98 Slices 99 </h1> 100 <p className="text-xs text-zinc-600 mt-1">network.slices</p> 101 </div> 102 </Link> 103 104 <div className="flex gap-4 text-xs items-center"> 105 {isAuthenticated && userInfo 106 ? ( 107 <Suspense 108 fallback={ 109 <span className="px-2 py-1 text-zinc-400"> 110 @{userInfo.handle || userInfo.did} 111 </span> 112 } 113 > 114 <UserProfile handle={userInfo.handle || userInfo.did} /> 115 </Suspense> 116 ) 117 : ( 118 <> 119 <Link 120 to="/waitlist" 121 className="px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors" 122 > 123 Join Waitlist 124 </Link> 125 <Link 126 to="/login" 127 className="px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors" 128 > 129 Sign In 130 </Link> 131 </> 132 )} 133 </div> 134 </div> 135 </div> 136 137 {subNav && <div className="border-b border-zinc-800">{subNav}</div>} 138 139 <div className="mb-4"></div> 140 141 {children} 142 </div> 143 </div> 144 ); 145}