the statusphere demo reworked into a vite/react app in a monorepo

add profile pictures

Changed files
+44 -12
packages
appview
src
client
src
components
+34 -9
packages/appview/src/routes.ts
··· 3 import { TID } from '@atproto/common' 4 import { OAuthResolverError } from '@atproto/oauth-client-node' 5 import { isValidHandle } from '@atproto/syntax' 6 - import { AppBskyActorProfile, XyzStatusphereStatus } from '@statusphere/lexicon' 7 import express from 'express' 8 import { getIronSession, SessionOptions } from 'iron-session' 9 ··· 198 .catch(() => undefined) 199 200 const profileRecord = profileResponse?.data 201 - const profile = 202 profileRecord && 203 - AppBskyActorProfile.isRecord(profileRecord.value) && 204 - AppBskyActorProfile.validateRecord(profileRecord.value).success 205 - ? profileRecord.value 206 - : ({} as AppBskyActorProfile.Record) 207 208 - profile.did = did 209 - profile.handle = await ctx.resolver.resolveDidToHandle(did) 210 211 // Fetch user status 212 const status = await ctx.db ··· 218 219 res.json({ 220 did: agent.assertDid, 221 - profile, 222 status: status ? await statusToStatusView(status, ctx) : undefined, 223 }) 224 } catch (err) {
··· 3 import { TID } from '@atproto/common' 4 import { OAuthResolverError } from '@atproto/oauth-client-node' 5 import { isValidHandle } from '@atproto/syntax' 6 + import { 7 + AppBskyActorDefs, 8 + AppBskyActorProfile, 9 + XyzStatusphereStatus, 10 + } from '@statusphere/lexicon' 11 import express from 'express' 12 import { getIronSession, SessionOptions } from 'iron-session' 13 ··· 202 .catch(() => undefined) 203 204 const profileRecord = profileResponse?.data 205 + let profile: AppBskyActorProfile.Record = 206 + {} as AppBskyActorProfile.Record 207 + 208 + if ( 209 profileRecord && 210 + AppBskyActorProfile.isRecord(profileRecord.value) 211 + ) { 212 + const validated = AppBskyActorProfile.validateRecord( 213 + profileRecord.value, 214 + ) 215 + if (validated.success) { 216 + profile = profileRecord.value 217 + } else { 218 + ctx.logger.error( 219 + { err: validated.error }, 220 + 'Failed to validate user profile', 221 + ) 222 + } 223 + } 224 225 + const profileView: AppBskyActorDefs.ProfileView = { 226 + $type: 'app.bsky.actor.defs#profileView', 227 + did: did, 228 + handle: await ctx.resolver.resolveDidToHandle(did), 229 + avatar: profile.avatar 230 + ? `https://atproto.pictures/img/${did}/${profile.avatar.ref}` 231 + : undefined, 232 + displayName: profile.displayName, 233 + createdAt: profile.createdAt, 234 + } 235 236 // Fetch user status 237 const status = await ctx.db ··· 243 244 res.json({ 245 did: agent.assertDid, 246 + profile: profileView, 247 status: status ? await statusToStatusView(status, ctx) : undefined, 248 }) 249 } catch (err) {
+10 -3
packages/client/src/components/Header.tsx
··· 27 <nav> 28 {user ? ( 29 <div className="flex gap-4 items-center"> 30 <span className="text-gray-700 dark:text-gray-300"> 31 - {user.profile?.displayName || 32 - user.profile?.handle || 33 - user.did.substring(0, 15)} 34 </span> 35 <button 36 onClick={handleLogout}
··· 27 <nav> 28 {user ? ( 29 <div className="flex gap-4 items-center"> 30 + {user.profile.avatar ? ( 31 + <img 32 + src={user.profile.avatar} 33 + alt={user.profile.displayName || user.profile.handle} 34 + className="w-8 h-8 rounded-full" 35 + /> 36 + ) : ( 37 + <div className="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded-full"></div> 38 + )} 39 <span className="text-gray-700 dark:text-gray-300"> 40 + {user.profile.displayName || user.profile.handle} 41 </span> 42 <button 43 onClick={handleLogout}