3 changed files with 225 additions and 181 deletions
@ -1,106 +1,155 @@
@@ -1,106 +1,155 @@
|
||||
import KindFilter from '@/components/KindFilter' |
||||
import SimpleNoteFeed from '@/components/SimpleNoteFeed' |
||||
import Tabs from '@/components/Tabs' |
||||
import { isTouchDevice } from '@/lib/utils' |
||||
import { FAST_READ_RELAY_URLS } from '@/constants' |
||||
import logger from '@/lib/logger' |
||||
import { useKindFilter } from '@/providers/KindFilterProvider' |
||||
import { useNostr } from '@/providers/NostrProvider' |
||||
import { TNoteListMode } from '@/types' |
||||
import { useMemo, useRef, useState } from 'react' |
||||
import { RefreshButton } from '../RefreshButton' |
||||
import ProfileBookmarksAndHashtags from './ProfileBookmarksAndHashtags' |
||||
import { normalizeUrl } from '@/lib/url' |
||||
import client from '@/services/client.service' |
||||
import { Event } from 'nostr-tools' |
||||
import { useCallback, useEffect, useState } from 'react' |
||||
import NoteCard from '@/components/NoteCard' |
||||
import { Skeleton } from '@/components/ui/skeleton' |
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||
|
||||
export default function ProfileFeed({ |
||||
pubkey, |
||||
topSpace = 0 |
||||
}: { |
||||
interface ProfileFeedProps { |
||||
pubkey: string |
||||
topSpace?: number |
||||
}) { |
||||
const { pubkey: myPubkey } = useNostr() |
||||
const { showKinds } = useKindFilter() |
||||
const [temporaryShowKinds, setTemporaryShowKinds] = useState(showKinds) |
||||
const [listMode, setListMode] = useState<TNoteListMode>('bookmarksAndHashtags') |
||||
const simpleNoteFeedRef = useRef<{ refresh: () => void }>(null) |
||||
|
||||
const tabs = useMemo(() => { |
||||
const _tabs = [ |
||||
{ value: 'bookmarksAndHashtags', label: 'Interests' }, |
||||
{ value: 'posts', label: 'Notes' }, |
||||
{ value: 'postsAndReplies', label: 'Replies' } |
||||
] |
||||
} |
||||
|
||||
export default function ProfileFeed({ pubkey, topSpace }: ProfileFeedProps) { |
||||
console.log('[ProfileFeed] Component rendered with pubkey:', pubkey) |
||||
const [events, setEvents] = useState<Event[]>([]) |
||||
const [isLoading, setIsLoading] = useState(true) |
||||
const { favoriteRelays } = useFavoriteRelays() |
||||
|
||||
if (myPubkey && myPubkey !== pubkey) { |
||||
_tabs.push({ value: 'you', label: 'YouTabName' }) |
||||
// Build comprehensive relay list including user's personal relays
|
||||
const buildComprehensiveRelayList = useCallback(async () => { |
||||
try { |
||||
// Get user's relay list (kind 10002)
|
||||
const userRelayList = await client.fetchRelayList(pubkey) |
||||
|
||||
// Get all relays: user's + fast read + favorite relays
|
||||
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 |
||||
}, [myPubkey, pubkey]) |
||||
const supportTouch = useMemo(() => isTouchDevice(), []) |
||||
useEffect(() => { |
||||
const fetchPosts = async () => { |
||||
if (!pubkey) { |
||||
setEvents([]) |
||||
setIsLoading(false) |
||||
return |
||||
} |
||||
|
||||
const handleListModeChange = (mode: TNoteListMode) => { |
||||
setListMode(mode) |
||||
} |
||||
try { |
||||
setIsLoading(true) |
||||
|
||||
console.log('[ProfileFeed] Fetching events for pubkey:', pubkey) |
||||
|
||||
// Build comprehensive relay list including user's personal relays
|
||||
const comprehensiveRelays = await buildComprehensiveRelayList() |
||||
console.log('[ProfileFeed] Using comprehensive relay list:', comprehensiveRelays.length, 'relays') |
||||
|
||||
// First, let's try to fetch ANY events from this user to see if they exist
|
||||
console.log('[ProfileFeed] Testing: fetching ANY events from this user...') |
||||
const anyEvents = await client.fetchEvents(comprehensiveRelays.slice(0, 10), { |
||||
authors: [pubkey], |
||||
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) |
||||
} |
||||
} |
||||
|
||||
const handleShowKindsChange = (newShowKinds: number[]) => { |
||||
setTemporaryShowKinds(newShowKinds) |
||||
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> |
||||
) |
||||
} |
||||
|
||||
// Determine the authors filter based on list mode
|
||||
const getAuthorsFilter = () => { |
||||
if (listMode === 'you') { |
||||
if (!myPubkey) return [] |
||||
return [myPubkey, pubkey] // Show interactions between current user and profile user
|
||||
} |
||||
logger.component('ProfileFeed', 'getAuthorsFilter called', { listMode, pubkey, myPubkey }) |
||||
return [pubkey] // Show only profile user's events
|
||||
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
|
||||
const shouldHideReplies = listMode === 'posts' |
||||
if (events.length === 0) { |
||||
return ( |
||||
<div className="flex justify-center items-center py-8"> |
||||
<div className="text-sm text-muted-foreground">No posts found</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<Tabs |
||||
value={listMode} |
||||
tabs={tabs} |
||||
onTabChange={(listMode) => { |
||||
handleListModeChange(listMode as TNoteListMode) |
||||
}} |
||||
threshold={Math.max(800, topSpace)} |
||||
options={ |
||||
listMode !== 'bookmarksAndHashtags' ? ( |
||||
<> |
||||
{!supportTouch && <RefreshButton onClick={() => simpleNoteFeedRef.current?.refresh()} />} |
||||
<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} |
||||
/> |
||||
) |
||||
})() |
||||
)} |
||||
</> |
||||
<div style={{ marginTop: topSpace || 0 }}> |
||||
<div className="space-y-2"> |
||||
{events.map((event) => ( |
||||
<NoteCard |
||||
key={event.id} |
||||
className="w-full" |
||||
event={event} |
||||
filterMutedNotes={false} |
||||
/> |
||||
))} |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
Loading…
Reference in new issue