Add FollowingPage component and related hooks; update routing and BlogHeader

This commit is contained in:
2025-10-05 22:15:54 +02:00
parent b614349a85
commit 3fce6b7e4d
6 changed files with 228 additions and 2 deletions

44
src/hooks/useFollowing.ts Normal file
View File

@@ -0,0 +1,44 @@
import { useQuery } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
import { useCurrentUser } from './useCurrentUser';
/**
* Hook to fetch the list of pubkeys that the current user follows.
* Returns the pubkeys from the user's kind 3 contact list event.
*/
export function useFollowing() {
const { nostr } = useNostr();
const { user } = useCurrentUser();
return useQuery({
queryKey: ['following', user?.pubkey],
queryFn: async (c) => {
if (!user?.pubkey) {
return [];
}
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(1500)]);
// Query kind 3 (contact list) for the current user
const events = await nostr.query(
[{ kinds: [3], authors: [user.pubkey], limit: 1 }],
{ signal }
);
if (events.length === 0) {
return [];
}
// Extract pubkeys from p tags
const contactEvent = events[0];
const followedPubkeys = contactEvent.tags
.filter(([tagName]) => tagName === 'p')
.map(([, pubkey]) => pubkey)
.filter((pubkey): pubkey is string => !!pubkey);
return followedPubkeys;
},
enabled: !!user?.pubkey,
staleTime: 1000 * 60 * 5, // Cache for 5 minutes
});
}

View File

@@ -0,0 +1,47 @@
import { useQuery } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
import type { NostrEvent } from '@nostrify/nostrify';
import { useFollowing } from './useFollowing';
/**
* Hook to fetch long-form blog posts (kind 30023) from authors the user follows.
*/
export function useFollowingBlogPosts() {
const { nostr } = useNostr();
const { data: followedPubkeys = [], isLoading: isLoadingFollowing } = useFollowing();
return useQuery({
queryKey: ['following-blog-posts', followedPubkeys],
queryFn: async (c) => {
if (followedPubkeys.length === 0) {
return [];
}
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(3000)]);
// Query kind 30023 (long-form content) from followed authors
const events = await nostr.query(
[
{
kinds: [30023],
authors: followedPubkeys,
limit: 50,
},
],
{ signal }
);
// Filter out events without required tags
const validEvents = events.filter((event: NostrEvent) => {
const hasTitle = event.tags.some(([name]) => name === 'title');
const hasDTag = event.tags.some(([name]) => name === 'd');
return hasTitle && hasDTag;
});
// Sort by created_at descending (newest first)
return validEvents.sort((a, b) => b.created_at - a.created_at);
},
enabled: followedPubkeys.length > 0 && !isLoadingFollowing,
staleTime: 1000 * 60 * 2, // Cache for 2 minutes
});
}