Scrapboard.org client
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}