- Posts
+ {/* this should not be rendered until its ready (the top profile layout is stable) */}
+ {isReady ? (
+
,
+ Reposts:
,
+ Feeds:
,
+ Lists:
,
+ ...(identity?.did === agent?.did
+ ? { Likes:
}
+ : {}),
+ }}
+ />
+ ) : isIdentityLoading ? (
+
+ Resolving profile...
+
+ ) : identityError ? (
+
+ Error: {identityError.message}
+
+ ) : !resolvedDid ? (
+
Profile not found.
+ ) : (
+
+ Loading profile content...
-
- {posts.map((post) => (
+ )}
+
+ );
+}
+
+function PostsTab({ did }: { did: string }) {
+ useReusableTabScrollRestore(`Profile` + did);
+ const queryClient = useQueryClient();
+ const {
+ data: identity,
+ isLoading: isIdentityLoading,
+ error: identityError,
+ } = useQueryIdentity(did);
+
+ const resolvedDid = did.startsWith("did:") ? did : identity?.did;
+
+ const {
+ data: postsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading: arePostsLoading,
+ } = useInfiniteQueryAuthorFeed(resolvedDid, identity?.pds);
+
+ React.useEffect(() => {
+ if (postsData) {
+ postsData.pages.forEach((page) => {
+ page.records.forEach((record) => {
+ if (!queryClient.getQueryData(["post", record.uri])) {
+ queryClient.setQueryData(["post", record.uri], record);
+ }
+ });
+ });
+ }
+ }, [postsData, queryClient]);
+
+ const posts = React.useMemo(
+ () => postsData?.pages.flatMap((page) => page.records) ?? [],
+ [postsData]
+ );
+
+ return (
+ <>
+
+ Posts
+
+
+ {posts.map((post) => (
+
+ ))}
+
+
+ {/* Loading and "Load More" states */}
+ {arePostsLoading && posts.length === 0 && (
+
Loading posts...
+ )}
+ {isFetchingNextPage && (
+
Loading more...
+ )}
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+ {posts.length === 0 && !arePostsLoading && (
+
No posts found.
+ )}
+ >
+ );
+}
+
+function RepostsTab({ did }: { did: string }) {
+ useReusableTabScrollRestore(`Profile` + did);
+ const {
+ data: identity,
+ isLoading: isIdentityLoading,
+ error: identityError,
+ } = useQueryIdentity(did);
+
+ const resolvedDid = did.startsWith("did:") ? did : identity?.did;
+
+ const {
+ data: repostsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading: arePostsLoading,
+ } = useInfiniteQueryAuthorFeed(
+ resolvedDid,
+ identity?.pds,
+ "app.bsky.feed.repost"
+ );
+
+ const reposts = React.useMemo(
+ () => repostsData?.pages.flatMap((page) => page.records) ?? [],
+ [repostsData]
+ );
+
+ return (
+ <>
+
+ Reposts
+
+
+ {reposts.map((repost) => {
+ if (
+ !repost ||
+ !repost?.value ||
+ !repost?.value?.subject ||
+ // @ts-expect-error blehhhhh
+ !repost?.value?.subject?.uri
+ )
+ return;
+ const repostRecord =
+ repost.value as unknown as ATPAPI.AppBskyFeedRepost.Record;
+ return (
- ))}
+ );
+ })}
+
+
+ {/* Loading and "Load More" states */}
+ {arePostsLoading && reposts.length === 0 && (
+
Loading posts...
+ )}
+ {isFetchingNextPage && (
+
Loading more...
+ )}
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+ {reposts.length === 0 && !arePostsLoading && (
+
No posts found.
+ )}
+ >
+ );
+}
+
+function FeedsTab({ did }: { did: string }) {
+ useReusableTabScrollRestore(`Profile` + did);
+ const {
+ data: identity,
+ isLoading: isIdentityLoading,
+ error: identityError,
+ } = useQueryIdentity(did);
+
+ const resolvedDid = did.startsWith("did:") ? did : identity?.did;
+
+ const {
+ data: feedsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading: arePostsLoading,
+ } = useInfiniteQueryAuthorFeed(
+ resolvedDid,
+ identity?.pds,
+ "app.bsky.feed.generator"
+ );
+
+ const feeds = React.useMemo(
+ () => feedsData?.pages.flatMap((page) => page.records) ?? [],
+ [feedsData]
+ );
+
+ return (
+ <>
+
+ Feeds
+
+
+ {feeds.map((feed) => {
+ if (!feed || !feed?.value) return;
+ const feedGenRecord =
+ feed.value as unknown as ATPAPI.AppBskyFeedGenerator.Record;
+ return ;
+ })}
+
+
+ {/* Loading and "Load More" states */}
+ {arePostsLoading && feeds.length === 0 && (
+
Loading feeds...
+ )}
+ {isFetchingNextPage && (
+
Loading more...
+ )}
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+ {feeds.length === 0 && !arePostsLoading && (
+
No feeds found.
+ )}
+ >
+ );
+}
+
+function FeedItemRender({
+ feed,
+ listmode
+}: {
+ feed: { uri: string; cid: string; value: ATPAPI.AppBskyFeedGenerator.Record };
+ listmode?: boolean;
+}) {
+ const name = listmode ? feed.value?.name as string : feed.value?.displayName as string;
+ const aturi = new ATPAPI.AtUri(feed.uri);
+ const {data: identity} = useQueryIdentity(aturi.host);
+ const resolvedDid = identity?.did;
+ const [imgcdn] = useAtom(imgCDNAtom);
+
+ function getAvatarThumbnailUrl(f: typeof feed) {
+ const link = f?.value.avatar?.ref?.["$link"];
+ if (!link || !resolvedDid) return null;
+ return `https://${imgcdn}/img/avatar/plain/${resolvedDid}/${link}@jpeg`;
+ }
+
+ // @ts-expect-error overloads sucks
+ const {data: likes} = useQueryConstellation(!listmode ? {
+ target: feed.uri,
+ method: "/links/count",
+ collection: "app.bsky.feed.like",
+ path: ".subject.uri"
+ } : undefined)
+
+ return (
+
+
+
+
)
+
+
+ {name}
+ {feed.value.did || aturi.rkey}
+
+ {/*
*/}
+
+
{feed.value?.description}
+ {!listmode && (
Liked by {(likes as unknown as any)?.total as number || 0} users)}
+
+ );
+}
- {/* Loading and "Load More" states */}
- {arePostsLoading && posts.length === 0 && (
-
Loading posts...
- )}
- {isFetchingNextPage && (
-
Loading more...
- )}
- {hasNextPage && !isFetchingNextPage && (
-
- )}
- {posts.length === 0 && !arePostsLoading && (
-
No posts found.
- )}
+
+function ListsTab({ did }: { did: string }) {
+ useReusableTabScrollRestore(`Profile` + did);
+ const {
+ data: identity,
+ isLoading: isIdentityLoading,
+ error: identityError,
+ } = useQueryIdentity(did);
+
+ const resolvedDid = did.startsWith("did:") ? did : identity?.did;
+
+ const {
+ data: feedsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading: arePostsLoading,
+ } = useInfiniteQueryAuthorFeed(
+ resolvedDid,
+ identity?.pds,
+ "app.bsky.graph.list"
+ );
+
+ const feeds = React.useMemo(
+ () => feedsData?.pages.flatMap((page) => page.records) ?? [],
+ [feedsData]
+ );
+
+ return (
+ <>
+
+ Feeds
+
+
+ {feeds.map((feed) => {
+ if (!feed || !feed?.value) return;
+ const feedGenRecord =
+ feed.value as unknown as ATPAPI.AppBskyFeedGenerator.Record;
+ return ;
+ })}
+
+
+ {/* Loading and "Load More" states */}
+ {arePostsLoading && feeds.length === 0 && (
+
Loading lists...
+ )}
+ {isFetchingNextPage && (
+
Loading more...
+ )}
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+ {feeds.length === 0 && !arePostsLoading && (
+
No lists found.
+ )}
+ >
+ );
+}
+
+function SelfLikesTab({ did }: { did: string }) {
+ useReusableTabScrollRestore(`Profile` + did);
+ const {
+ data: identity,
+ isLoading: isIdentityLoading,
+ error: identityError,
+ } = useQueryIdentity(did);
+
+ const resolvedDid = did.startsWith("did:") ? did : identity?.did;
+
+ const {
+ data: repostsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading: arePostsLoading,
+ } = useInfiniteQueryAuthorFeed(
+ resolvedDid,
+ identity?.pds,
+ "app.bsky.feed.like"
+ );
+
+ const reposts = React.useMemo(
+ () => repostsData?.pages.flatMap((page) => page.records) ?? [],
+ [repostsData]
+ );
+
+ return (
+ <>
+
+ Likes
+
+
+ {reposts.map((repost) => {
+ if (
+ !repost ||
+ !repost?.value ||
+ !repost?.value?.subject ||
+ // @ts-expect-error blehhhhh
+ !repost?.value?.subject?.uri
+ )
+ return;
+ const repostRecord =
+ repost.value as unknown as ATPAPI.AppBskyFeedLike.Record;
+ return (
+
+ );
+ })}
+
+ {/* Loading and "Load More" states */}
+ {arePostsLoading && reposts.length === 0 && (
+
Loading posts...
+ )}
+ {isFetchingNextPage && (
+
Loading more...
+ )}
+ {hasNextPage && !isFetchingNextPage && (
+
+ )}
+ {reposts.length === 0 && !arePostsLoading && (
+
No posts found.
+ )}
>
);
}
diff --git a/src/utils/useQuery.ts b/src/utils/useQuery.ts
index 7e2f4f7..eb0836d 100644
--- a/src/utils/useQuery.ts
+++ b/src/utils/useQuery.ts
@@ -534,16 +534,16 @@ type ListRecordsResponse = {
}[];
};
-export function constructAuthorFeedQuery(did: string, pdsUrl: string) {
+export function constructAuthorFeedQuery(did: string, pdsUrl: string, collection: string = "app.bsky.feed.post") {
return queryOptions({
- queryKey: ['authorFeed', did],
+ queryKey: ['authorFeed', did, collection],
queryFn: async ({ pageParam }: QueryFunctionContext) => {
const limit = 25;
const cursor = pageParam as string | undefined;
const cursorParam = cursor ? `&cursor=${cursor}` : '';
- const url = `${pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=app.bsky.feed.post&limit=${limit}${cursorParam}`;
+ const url = `${pdsUrl}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=${collection}&limit=${limit}${cursorParam}`;
const res = await fetch(url);
if (!res.ok) throw new Error("Failed to fetch author's posts");
@@ -553,8 +553,8 @@ export function constructAuthorFeedQuery(did: string, pdsUrl: string) {
});
}
-export function useInfiniteQueryAuthorFeed(did: string | undefined, pdsUrl: string | undefined) {
- const { queryKey, queryFn } = constructAuthorFeedQuery(did!, pdsUrl!);
+export function useInfiniteQueryAuthorFeed(did: string | undefined, pdsUrl: string | undefined, collection?: string) {
+ const { queryKey, queryFn } = constructAuthorFeedQuery(did!, pdsUrl!, collection);
return useInfiniteQuery({
queryKey,
--
2.51.2