PinnedUsersProvider.tsx raw
1 import { Pubkey, PinnedUsersList, fromPinnedUsersListToHexSet } from '@/domain'
2 import { PinnedUsersListRepositoryImpl } from '@/infrastructure/persistence'
3 import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
4 import { useNostr } from './NostrProvider'
5
6 type TPinnedUsersContext = {
7 pinnedPubkeySet: Set<string>
8 isLoading: boolean
9 isPinned: (pubkey: string) => boolean
10 pinUser: (pubkey: string) => Promise<void>
11 unpinUser: (pubkey: string) => Promise<void>
12 togglePin: (pubkey: string) => Promise<void>
13 }
14
15 const PinnedUsersContext = createContext<TPinnedUsersContext | undefined>(undefined)
16
17 export const usePinnedUsers = () => {
18 const context = useContext(PinnedUsersContext)
19 if (!context) {
20 throw new Error('usePinnedUsers must be used within a PinnedUsersProvider')
21 }
22 return context
23 }
24
25 export function PinnedUsersProvider({ children }: { children: React.ReactNode }) {
26 const { pubkey: accountPubkey, publish, nip04Decrypt, nip04Encrypt } = useNostr()
27
28 // State managed by this provider
29 const [pinnedUsersList, setPinnedUsersList] = useState<PinnedUsersList | null>(null)
30 const [isLoading, setIsLoading] = useState(false)
31
32 // Create repository instance
33 const repository = useMemo(() => {
34 if (!publish || !accountPubkey) return null
35 return new PinnedUsersListRepositoryImpl({
36 publish,
37 currentUserPubkey: accountPubkey,
38 decrypt: async (ciphertext, pk) => nip04Decrypt(pk, ciphertext),
39 encrypt: async (plaintext, pk) => nip04Encrypt(pk, plaintext)
40 })
41 }, [publish, accountPubkey, nip04Decrypt, nip04Encrypt])
42
43 // Convert to legacy hex set for backwards compatibility
44 const pinnedPubkeySet = useMemo(() => {
45 if (!pinnedUsersList) return new Set<string>()
46 return fromPinnedUsersListToHexSet(pinnedUsersList)
47 }, [pinnedUsersList])
48
49 // Load pinned users list when account changes
50 useEffect(() => {
51 const loadPinnedUsersList = async () => {
52 if (!accountPubkey || !repository) {
53 setPinnedUsersList(null)
54 return
55 }
56
57 setIsLoading(true)
58 try {
59 const ownerPubkey = Pubkey.tryFromString(accountPubkey)
60 if (!ownerPubkey) {
61 setPinnedUsersList(null)
62 return
63 }
64
65 const list = await repository.findByOwner(ownerPubkey)
66 setPinnedUsersList(list)
67 } catch (error) {
68 console.error('Failed to load pinned users list:', error)
69 setPinnedUsersList(null)
70 } finally {
71 setIsLoading(false)
72 }
73 }
74
75 loadPinnedUsersList()
76 }, [accountPubkey, repository])
77
78 const isPinned = useCallback(
79 (pubkey: string) => {
80 if (!pinnedUsersList) return false
81 const pk = Pubkey.tryFromString(pubkey)
82 return pk ? pinnedUsersList.isPinned(pk) : false
83 },
84 [pinnedUsersList]
85 )
86
87 const pinUser = useCallback(
88 async (pubkey: string) => {
89 if (!accountPubkey || !repository || isPinned(pubkey)) return
90
91 try {
92 const pk = Pubkey.tryFromString(pubkey)
93 if (!pk) return
94
95 const ownerPk = Pubkey.tryFromString(accountPubkey)
96 if (!ownerPk) return
97
98 // Fetch latest to avoid conflicts
99 const currentList = await repository.findByOwner(ownerPk)
100 const list = currentList ?? PinnedUsersList.empty(ownerPk)
101
102 const change = list.pin(pk)
103 if (change.type === 'no_change') return
104
105 await repository.save(list)
106 setPinnedUsersList(list)
107 } catch (error) {
108 console.error('Failed to pin user:', error)
109 }
110 },
111 [accountPubkey, repository, isPinned]
112 )
113
114 const unpinUser = useCallback(
115 async (pubkey: string) => {
116 if (!accountPubkey || !repository || !isPinned(pubkey)) return
117
118 try {
119 const pk = Pubkey.tryFromString(pubkey)
120 if (!pk) return
121
122 const ownerPk = Pubkey.tryFromString(accountPubkey)
123 if (!ownerPk) return
124
125 const currentList = await repository.findByOwner(ownerPk)
126 if (!currentList) return
127
128 const change = currentList.unpin(pk)
129 if (change.type === 'no_change') return
130
131 await repository.save(currentList)
132 setPinnedUsersList(currentList)
133 } catch (error) {
134 console.error('Failed to unpin user:', error)
135 }
136 },
137 [accountPubkey, repository, isPinned]
138 )
139
140 const togglePin = useCallback(
141 async (pubkey: string) => {
142 if (isPinned(pubkey)) {
143 await unpinUser(pubkey)
144 } else {
145 await pinUser(pubkey)
146 }
147 },
148 [isPinned, pinUser, unpinUser]
149 )
150
151 return (
152 <PinnedUsersContext.Provider
153 value={{
154 pinnedPubkeySet,
155 isLoading,
156 isPinned,
157 pinUser,
158 unpinUser,
159 togglePin
160 }}
161 >
162 {children}
163 </PinnedUsersContext.Provider>
164 )
165 }
166