3 changed files with 225 additions and 181 deletions
@ -1,106 +1,155 @@ |
|||||||
import KindFilter from '@/components/KindFilter' |
import { FAST_READ_RELAY_URLS } from '@/constants' |
||||||
import SimpleNoteFeed from '@/components/SimpleNoteFeed' |
|
||||||
import Tabs from '@/components/Tabs' |
|
||||||
import { isTouchDevice } from '@/lib/utils' |
|
||||||
import logger from '@/lib/logger' |
import logger from '@/lib/logger' |
||||||
import { useKindFilter } from '@/providers/KindFilterProvider' |
import { normalizeUrl } from '@/lib/url' |
||||||
import { useNostr } from '@/providers/NostrProvider' |
import client from '@/services/client.service' |
||||||
import { TNoteListMode } from '@/types' |
import { Event } from 'nostr-tools' |
||||||
import { useMemo, useRef, useState } from 'react' |
import { useCallback, useEffect, useState } from 'react' |
||||||
import { RefreshButton } from '../RefreshButton' |
import NoteCard from '@/components/NoteCard' |
||||||
import ProfileBookmarksAndHashtags from './ProfileBookmarksAndHashtags' |
import { Skeleton } from '@/components/ui/skeleton' |
||||||
|
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||||
export default function ProfileFeed({ |
|
||||||
pubkey, |
interface ProfileFeedProps { |
||||||
topSpace = 0 |
|
||||||
}: { |
|
||||||
pubkey: string |
pubkey: string |
||||||
topSpace?: number |
topSpace?: number |
||||||
}) { |
} |
||||||
const { pubkey: myPubkey } = useNostr() |
|
||||||
const { showKinds } = useKindFilter() |
export default function ProfileFeed({ pubkey, topSpace }: ProfileFeedProps) { |
||||||
const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) |
console.log('[ProfileFeed] Component rendered with pubkey:', pubkey) |
||||||
const [listMode, setListMode] = useState<TNoteListMode>('bookmarksAndHashtags') |
const [events, setEvents] = useState<Event[]>([]) |
||||||
const simpleNoteFeedRef = useRef<{ refresh: () => void }>(null) |
const [isLoading, setIsLoading] = useState(true) |
||||||
|
const { favoriteRelays } = useFavoriteRelays() |
||||||
const tabs = useMemo(() => { |
|
||||||
const _tabs = [ |
// Build comprehensive relay list including user's personal relays
|
||||||
{ value: 'bookmarksAndHashtags', label: 'Interests' }, |
const buildComprehensiveRelayList = useCallback(async () => { |
||||||
{ value: 'posts', label: 'Notes' }, |
try { |
||||||
{ value: 'postsAndReplies', label: 'Replies' } |
// Get user's relay list (kind 10002)
|
||||||
] |
const userRelayList = await client.fetchRelayList(pubkey) |
||||||
|
|
||||||
if (myPubkey && myPubkey !== pubkey) { |
// Get all relays: user's + fast read + favorite relays
|
||||||
_tabs.push({ value: 'you', label: 'YouTabName' }) |
const allRelays = [ |
||||||
|
...(userRelayList.read || []), // User's read relays
|
||||||
|
...(userRelayList.write || []), // User's write relays
|
||||||
|
...FAST_READ_RELAY_URLS, // Fast read relays
|
||||||
|
...(favoriteRelays || []) // User's favorite relays
|
||||||
|
] |
||||||
|
|
||||||
|
// Normalize URLs and remove duplicates
|
||||||
|
const normalizedRelays = allRelays |
||||||
|
.map(url => normalizeUrl(url)) |
||||||
|
.filter((url): url is string => !!url) |
||||||
|
|
||||||
|
const uniqueRelays = Array.from(new Set(normalizedRelays)) |
||||||
|
|
||||||
|
console.log('[ProfileFeed] Comprehensive relay list:', uniqueRelays.length, 'relays') |
||||||
|
console.log('[ProfileFeed] User relays (read):', userRelayList.read?.length || 0) |
||||||
|
console.log('[ProfileFeed] User relays (write):', userRelayList.write?.length || 0) |
||||||
|
console.log('[ProfileFeed] Favorite relays:', favoriteRelays?.length || 0) |
||||||
|
|
||||||
|
return uniqueRelays |
||||||
|
} catch (error) { |
||||||
|
console.warn('[ProfileFeed] Error building relay list, using fallback:', error) |
||||||
|
return FAST_READ_RELAY_URLS |
||||||
} |
} |
||||||
|
}, [pubkey, favoriteRelays]) |
||||||
|
|
||||||
return _tabs |
useEffect(() => { |
||||||
}, [myPubkey, pubkey]) |
const fetchPosts = async () => { |
||||||
const supportTouch = useMemo(() => isTouchDevice(), []) |
if (!pubkey) { |
||||||
|
setEvents([]) |
||||||
|
setIsLoading(false) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
const handleListModeChange = (mode: TNoteListMode) => { |
try { |
||||||
setListMode(mode) |
setIsLoading(true) |
||||||
} |
|
||||||
|
|
||||||
const handleShowKindsChange = (newShowKinds: number[]) => { |
console.log('[ProfileFeed] Fetching events for pubkey:', pubkey) |
||||||
setTemporaryShowKinds(newShowKinds) |
|
||||||
} |
// Build comprehensive relay list including user's personal relays
|
||||||
|
const comprehensiveRelays = await buildComprehensiveRelayList() |
||||||
|
console.log('[ProfileFeed] Using comprehensive relay list:', comprehensiveRelays.length, 'relays') |
||||||
|
|
||||||
// Determine the authors filter based on list mode
|
// First, let's try to fetch ANY events from this user to see if they exist
|
||||||
const getAuthorsFilter = () => { |
console.log('[ProfileFeed] Testing: fetching ANY events from this user...') |
||||||
if (listMode === 'you') { |
const anyEvents = await client.fetchEvents(comprehensiveRelays.slice(0, 10), { |
||||||
if (!myPubkey) return [] |
authors: [pubkey], |
||||||
return [myPubkey, pubkey] // Show interactions between current user and profile user
|
limit: 10 |
||||||
|
}) |
||||||
|
console.log('[ProfileFeed] Found ANY events:', anyEvents.length) |
||||||
|
if (anyEvents.length > 0) { |
||||||
|
console.log('[ProfileFeed] Sample ANY events:', anyEvents.map(e => ({ kind: e.kind, id: e.id, content: e.content?.substring(0, 30) + '...' }))) |
||||||
|
} |
||||||
|
|
||||||
|
// Now try to fetch text notes specifically
|
||||||
|
const allEvents = await client.fetchEvents(comprehensiveRelays, { |
||||||
|
authors: [pubkey], |
||||||
|
kinds: [1], // Text notes only
|
||||||
|
limit: 100 |
||||||
|
}) |
||||||
|
|
||||||
|
console.log('[ProfileFeed] Fetched total events:', allEvents.length) |
||||||
|
console.log('[ProfileFeed] Sample events:', allEvents.slice(0, 3).map(e => ({ id: e.id, content: e.content.substring(0, 50) + '...', tags: e.tags.slice(0, 3) }))) |
||||||
|
|
||||||
|
// Show ALL events (both top-level posts and replies)
|
||||||
|
console.log('[ProfileFeed] Showing all events (posts + replies):', allEvents.length) |
||||||
|
console.log('[ProfileFeed] Events sample:', allEvents.slice(0, 2).map(e => ({ id: e.id, content: e.content.substring(0, 50) + '...' }))) |
||||||
|
|
||||||
|
const eventsToShow = allEvents |
||||||
|
|
||||||
|
// Sort by creation time (newest first)
|
||||||
|
eventsToShow.sort((a, b) => b.created_at - a.created_at) |
||||||
|
|
||||||
|
setEvents(eventsToShow) |
||||||
|
} catch (error) { |
||||||
|
console.error('[ProfileFeed] Error fetching events:', error) |
||||||
|
logger.component('ProfileFeed', 'Initialization failed', { pubkey, error: (error as Error).message }) |
||||||
|
setEvents([]) |
||||||
|
} finally { |
||||||
|
setIsLoading(false) |
||||||
|
} |
||||||
} |
} |
||||||
logger.component('ProfileFeed', 'getAuthorsFilter called', { listMode, pubkey, myPubkey }) |
|
||||||
return [pubkey] // Show only profile user's events
|
fetchPosts() |
||||||
|
}, [pubkey]) |
||||||
|
|
||||||
|
if (isLoading) { |
||||||
|
return ( |
||||||
|
<div className="space-y-2"> |
||||||
|
{Array.from({ length: 3 }).map((_, i) => ( |
||||||
|
<Skeleton key={i} className="h-32 w-full" /> |
||||||
|
))} |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if (!pubkey) { |
||||||
|
return ( |
||||||
|
<div className="flex justify-center items-center py-8"> |
||||||
|
<div className="text-sm text-muted-foreground">No profile selected</div> |
||||||
|
</div> |
||||||
|
) |
||||||
} |
} |
||||||
|
|
||||||
// Determine if we should hide replies
|
if (events.length === 0) { |
||||||
const shouldHideReplies = listMode === 'posts' |
return ( |
||||||
|
<div className="flex justify-center items-center py-8"> |
||||||
|
<div className="text-sm text-muted-foreground">No posts found</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
return ( |
return ( |
||||||
<> |
<div style={{ marginTop: topSpace || 0 }}> |
||||||
<Tabs |
<div className="space-y-2"> |
||||||
value={listMode} |
{events.map((event) => ( |
||||||
tabs={tabs} |
<NoteCard |
||||||
onTabChange={(listMode) => { |
key={event.id} |
||||||
handleListModeChange(listMode as TNoteListMode) |
className="w-full" |
||||||
}} |
event={event} |
||||||
threshold={Math.max(800, topSpace)} |
filterMutedNotes={false} |
||||||
options={ |
/> |
||||||
listMode !== 'bookmarksAndHashtags' ? ( |
))} |
||||||
<> |
</div> |
||||||
{!supportTouch && <RefreshButton onClick={() => simpleNoteFeedRef.current?.refresh()} />} |
</div> |
||||||
<KindFilter showKinds={temporaryShowKinds} onShowKindsChange={handleShowKindsChange} /> |
|
||||||
</> |
|
||||||
) : undefined |
|
||||||
} |
|
||||||
/> |
|
||||||
{listMode === 'bookmarksAndHashtags' ? ( |
|
||||||
<ProfileBookmarksAndHashtags pubkey={pubkey} topSpace={topSpace} /> |
|
||||||
) : ( |
|
||||||
(() => { |
|
||||||
const authors = getAuthorsFilter() |
|
||||||
logger.component('ProfileFeed', 'Rendering SimpleNoteFeed', {
|
|
||||||
listMode,
|
|
||||||
authors,
|
|
||||||
kinds: temporaryShowKinds,
|
|
||||||
hideReplies: shouldHideReplies, |
|
||||||
pubkey
|
|
||||||
}) |
|
||||||
return ( |
|
||||||
<SimpleNoteFeed |
|
||||||
ref={simpleNoteFeedRef} |
|
||||||
authors={authors} |
|
||||||
kinds={temporaryShowKinds} |
|
||||||
limit={100} |
|
||||||
hideReplies={shouldHideReplies} |
|
||||||
filterMutedNotes={false} |
|
||||||
/> |
|
||||||
) |
|
||||||
})() |
|
||||||
)} |
|
||||||
</> |
|
||||||
) |
) |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue