an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm

constellation profilething

rimar1337 b279cb9c 06ea05da

Changed files
+23 -22
src
components
+23 -22
src/components/Login.tsx
··· 1 1 // src/components/Login.tsx 2 - import { Agent } from "@atproto/api"; 2 + import AtpAgent, { Agent } from "@atproto/api"; 3 3 import React, { useEffect, useRef, useState } from "react"; 4 4 5 5 import { useAuth } from "~/providers/UnifiedAuthProvider"; 6 + import { useQueryIdentity, useQueryProfile } from "~/utils/useQuery"; 6 7 7 8 // --- 1. The Main Component (Orchestrator with `compact` prop) --- 8 9 export default function Login({ ··· 110 111 // --- 3. Helper components for layouts, forms, and UI --- 111 112 112 113 // A new component to contain the logic for the compact dropdown 113 - const CompactLoginButton = ({popup}:{popup?: boolean}) => { 114 + const CompactLoginButton = ({ popup }: { popup?: boolean }) => { 114 115 const [showForm, setShowForm] = useState(false); 115 116 const formRef = useRef<HTMLDivElement>(null); 116 117 ··· 137 138 Log in 138 139 </button> 139 140 {showForm && ( 140 - <div className={`absolute ${popup ? `bottom-[calc(100%)]` :`top-full`} right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50`}> 141 + <div 142 + className={`absolute ${popup ? `bottom-[calc(100%)]` : `top-full`} right-0 mt-2 w-80 bg-white dark:bg-gray-900 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 p-4 z-50`} 143 + > 141 144 <UnifiedLoginForm /> 142 145 </div> 143 146 )} ··· 268 271 269 272 // --- Profile Component (now supports a `large` prop for styling) --- 270 273 export const ProfileThing = ({ 271 - agent, 274 + agent: _unused, 272 275 large = false, 273 276 }: { 274 - agent: Agent | null; 277 + agent?: Agent | null; 275 278 large?: boolean; 276 279 }) => { 277 - const [profile, setProfile] = useState<any>(null); 280 + const { agent } = useAuth(); 281 + const did = ((agent as AtpAgent).session?.did ?? (agent as AtpAgent)?.assertDid ?? agent?.did) as 282 + | string 283 + | undefined; 284 + const { data: identity } = useQueryIdentity(did); 285 + const { data: profiledata } = useQueryProfile(`at://${did}/app.bsky.actor.profile/self`); 286 + const profile = profiledata?.value; 278 287 279 - useEffect(() => { 280 - const fetchUser = async () => { 281 - const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid; 282 - if (!did) return; 283 - try { 284 - const res = await agent!.getProfile({ actor: did }); 285 - setProfile(res.data); 286 - } catch (e) { 287 - console.error("Failed to fetch profile", e); 288 - } 289 - }; 290 - if (agent) fetchUser(); 291 - }, [agent]); 288 + function getAvatarUrl(p: typeof profile) { 289 + const link = p?.avatar?.ref?.["$link"]; 290 + if (!link || !did) return null; 291 + return `https://cdn.bsky.app/img/avatar/plain/${did}/${link}@jpeg`; 292 + } 292 293 293 - if (!profile) { 294 + if (!profiledata) { 294 295 return ( 295 296 // Skeleton loader 296 297 <div ··· 316 317 className={`flex flex-row items-center gap-2.5 ${large ? "mb-1" : ""}`} 317 318 > 318 319 <img 319 - src={profile?.avatar} 320 + src={getAvatarUrl(profile) ?? undefined} 320 321 alt="avatar" 321 322 className={`object-cover rounded-full ${large ? "w-10 h-10" : "w-[30px] h-[30px]"}`} 322 323 /> ··· 329 330 <div 330 331 className={` ${large ? "text-gray-500 dark:text-gray-400 text-sm" : "text-gray-500 dark:text-gray-400 text-xs"}`} 331 332 > 332 - @{profile?.handle} 333 + @{identity?.handle} 333 334 </div> 334 335 </div> 335 336 </div>