Scrapboard.org client
at labels 110 lines 3.2 kB view raw
1import { create } from "zustand"; 2import { persist } from "zustand/middleware"; 3 4interface ActorProfilesState { 5 profiles: Record<string, ProfileViewDetailed>; 6 loadingProfiles: Set<string>; 7 errors: Record<string, string>; 8 setProfile: (did: string, profile: ProfileViewDetailed) => void; 9 setLoading: (did: string, isLoading: boolean) => void; 10 setError: (did: string, error: string | null) => void; 11 getProfile: (did: string) => ProfileViewDetailed | null; 12 isLoading: (did: string) => boolean; 13 getError: (did: string) => string | null; 14} 15 16export const useActorProfilesStore = create<ActorProfilesState>()( 17 persist( 18 (set, get) => ({ 19 profiles: {}, 20 loadingProfiles: new Set<string>(), 21 errors: {}, 22 23 setProfile: (did, profile) => 24 set((state) => ({ 25 profiles: { ...state.profiles, [did]: profile }, 26 })), 27 28 setLoading: (did, isLoading) => 29 set((state) => { 30 const newLoadingProfiles = new Set(state.loadingProfiles); 31 if (isLoading) { 32 newLoadingProfiles.add(did); 33 } else { 34 newLoadingProfiles.delete(did); 35 } 36 return { loadingProfiles: newLoadingProfiles }; 37 }), 38 39 setError: (did, error) => 40 set((state) => { 41 const newErrors = { ...state.errors }; 42 if (error) { 43 newErrors[did] = error; 44 } else { 45 delete newErrors[did]; 46 } 47 return { errors: newErrors }; 48 }), 49 50 getProfile: (did) => get().profiles[did] || null, 51 isLoading: (did) => get().loadingProfiles.has(did), 52 getError: (did) => get().errors[did] || null, 53 }), 54 { 55 name: "actor-profiles-storage", 56 partialize: (state) => ({ profiles: state.profiles }), 57 } 58 ) 59); 60 61// Hook to fetch and use actor profiles 62import { useEffect } from "react"; 63import { useAuth } from "@/lib/hooks/useAuth"; 64import { 65 ProfileView, 66 ProfileViewDetailed, 67} from "@atproto/api/dist/client/types/app/bsky/actor/defs"; 68 69export function useActorProfile(did: string | null) { 70 const { agent } = useAuth(); 71 const { getProfile, setProfile, isLoading, setLoading, getError, setError } = 72 useActorProfilesStore(); 73 74 useEffect(() => { 75 if (!did || !agent) return; 76 77 // Check if we already have the profile 78 if (getProfile(did)) return; 79 80 // Check if already loading 81 if (isLoading(did)) return; 82 83 const fetchProfile = async () => { 84 setLoading(did, true); 85 setError(did, null); 86 87 try { 88 const response = await agent.getProfile({ actor: did }); 89 if (response.success) { 90 setProfile(did, response.data); 91 } else { 92 throw new Error("Failed to fetch profile"); 93 } 94 } catch (err) { 95 console.error("Error fetching profile:", err); 96 setError(did, err instanceof Error ? err.message : String(err)); 97 } finally { 98 setLoading(did, false); 99 } 100 }; 101 102 fetchProfile(); 103 }, [did, agent, getProfile, setProfile, isLoading, setLoading, setError]); 104 105 return { 106 profile: did ? getProfile(did) : null, 107 isLoading: did ? isLoading(did) : false, 108 error: did ? getError(did) : null, 109 }; 110}