9 changed files with 476 additions and 41 deletions
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react' |
||||
import { useTranslation } from 'react-i18next' |
||||
import { useNostr } from '@/providers/NostrProvider' |
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||
import { ExtendedKind } from '@/constants' |
||||
import { normalizeUrl } from '@/lib/url' |
||||
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS } from '@/constants' |
||||
import client from '@/services/client.service' |
||||
import logger from '@/lib/logger' |
||||
|
||||
interface GroupListContextType { |
||||
userGroups: string[] |
||||
isUserInGroup: (groupId: string) => boolean |
||||
refreshGroupList: () => Promise<void> |
||||
isLoading: boolean |
||||
} |
||||
|
||||
const GroupListContext = createContext<GroupListContextType | undefined>(undefined) |
||||
|
||||
export const useGroupList = () => { |
||||
const context = useContext(GroupListContext) |
||||
if (context === undefined) { |
||||
throw new Error('useGroupList must be used within a GroupListProvider') |
||||
} |
||||
return context |
||||
} |
||||
|
||||
export function GroupListProvider({ children }: { children: React.ReactNode }) { |
||||
const { t } = useTranslation() |
||||
const { pubkey: accountPubkey, publish, updateGroupListEvent } = useNostr() |
||||
const { favoriteRelays } = useFavoriteRelays() |
||||
const [userGroups, setUserGroups] = useState<string[]>([]) |
||||
const [isLoading, setIsLoading] = useState(false) |
||||
|
||||
// Build comprehensive relay list for fetching group list
|
||||
const buildComprehensiveRelayList = useCallback(async () => { |
||||
const myRelayList = accountPubkey ? await client.fetchRelayList(accountPubkey) : { write: [], read: [] } |
||||
const allRelays = [ |
||||
...(myRelayList.read || []), // User's inboxes (kind 10002)
|
||||
...(myRelayList.write || []), // User's outboxes (kind 10002)
|
||||
...(favoriteRelays || []), // User's favorite relays (kind 10012)
|
||||
...BIG_RELAY_URLS, // Big relays
|
||||
...FAST_READ_RELAY_URLS // Fast read relays
|
||||
] |
||||
|
||||
const normalizedRelays = allRelays |
||||
.map(url => normalizeUrl(url)) |
||||
.filter((url): url is string => !!url) |
||||
|
||||
return Array.from(new Set(normalizedRelays)) |
||||
}, [accountPubkey, favoriteRelays]) |
||||
|
||||
// Fetch user's group list (kind 10009)
|
||||
const fetchGroupList = useCallback(async () => { |
||||
if (!accountPubkey) { |
||||
setUserGroups([]) |
||||
return |
||||
} |
||||
|
||||
try { |
||||
setIsLoading(true) |
||||
logger.debug('[GroupListProvider] Fetching group list for user:', accountPubkey.substring(0, 8)) |
||||
|
||||
// Get comprehensive relay list
|
||||
const allRelays = await buildComprehensiveRelayList() |
||||
|
||||
// Fetch group list event (kind 10009)
|
||||
const groupListEvents = await client.fetchEvents(allRelays, [ |
||||
{ |
||||
kinds: [ExtendedKind.GROUP_LIST], |
||||
authors: [accountPubkey], |
||||
limit: 1 |
||||
} |
||||
]) |
||||
|
||||
if (groupListEvents.length > 0) { |
||||
const groupListEvent = groupListEvents[0] |
||||
logger.debug('[GroupListProvider] Found group list event:', groupListEvent.id.substring(0, 8)) |
||||
|
||||
// Extract groups from a-tags (group coordinates)
|
||||
const groups: string[] = [] |
||||
groupListEvent.tags.forEach(tag => { |
||||
if (tag[0] === 'a' && tag[1]) { |
||||
// Parse group coordinate: kind:pubkey:group-id
|
||||
const coordinate = tag[1] |
||||
const parts = coordinate.split(':') |
||||
if (parts.length >= 3) { |
||||
const groupId = parts[2] |
||||
groups.push(groupId) |
||||
} |
||||
} |
||||
}) |
||||
|
||||
setUserGroups(groups) |
||||
logger.debug('[GroupListProvider] Extracted groups:', groups) |
||||
} else { |
||||
setUserGroups([]) |
||||
logger.debug('[GroupListProvider] No group list found') |
||||
} |
||||
} catch (error) { |
||||
logger.error('[GroupListProvider] Error fetching group list:', error) |
||||
setUserGroups([]) |
||||
} finally { |
||||
setIsLoading(false) |
||||
} |
||||
}, [accountPubkey, buildComprehensiveRelayList]) |
||||
|
||||
// Check if user is in a specific group
|
||||
const isUserInGroup = useCallback((groupId: string): boolean => { |
||||
return userGroups.includes(groupId) |
||||
}, [userGroups]) |
||||
|
||||
// Refresh group list
|
||||
const refreshGroupList = useCallback(async () => { |
||||
await fetchGroupList() |
||||
}, [fetchGroupList]) |
||||
|
||||
// Load group list on mount and when account changes
|
||||
useEffect(() => { |
||||
fetchGroupList() |
||||
}, [fetchGroupList]) |
||||
|
||||
const contextValue = useMemo(() => ({ |
||||
userGroups, |
||||
isUserInGroup, |
||||
refreshGroupList, |
||||
isLoading |
||||
}), [userGroups, isUserInGroup, refreshGroupList, isLoading]) |
||||
|
||||
return ( |
||||
<GroupListContext.Provider value={contextValue}> |
||||
{children} |
||||
</GroupListContext.Provider> |
||||
) |
||||
} |
||||
Loading…
Reference in new issue