From 2d0665ee6d008039fb8e8d9e0e4cf3388d621512 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Fri, 12 Dec 2025 11:55:49 +0400 Subject: [PATCH] add posts to profile page --- app/(home-pages)/reader/getReaderFeed.ts | 2 +- app/p/[didOrHandle]/ProfilePageLayout.tsx | 10 +-- app/p/[didOrHandle]/ProfileTabs/Tabs.tsx | 16 ++++- app/p/[didOrHandle]/page.tsx | 82 ++++++++++++++++++++--- components/PostListing.tsx | 53 +++++++++------ 5 files changed, 122 insertions(+), 41 deletions(-) diff --git a/app/(home-pages)/reader/getReaderFeed.ts b/app/(home-pages)/reader/getReaderFeed.ts index 9cb7d2b0..c084649d 100644 --- a/app/(home-pages)/reader/getReaderFeed.ts +++ b/app/(home-pages)/reader/getReaderFeed.ts @@ -83,7 +83,7 @@ export async function getReaderFeed( export type Post = { author: string | null; - publication: { + publication?: { href: string; pubRecord: Json; uri: string; diff --git a/app/p/[didOrHandle]/ProfilePageLayout.tsx b/app/p/[didOrHandle]/ProfilePageLayout.tsx index aa7bf38d..6a456947 100644 --- a/app/p/[didOrHandle]/ProfilePageLayout.tsx +++ b/app/p/[didOrHandle]/ProfilePageLayout.tsx @@ -11,9 +11,11 @@ import { blobRefToSrc } from "src/utils/blobRefToSrc"; import { PubIcon } from "components/ActionBar/Publications"; import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; import { colorToString } from "components/ThemeManager/useColorAttribute"; +import type { Post } from "app/(home-pages)/reader/getReaderFeed"; export const ProfilePageLayout = (props: { publications: { record: Json; uri: string }[]; + posts: Post[]; profile: { did: string; handle: string | null; @@ -23,9 +25,6 @@ export const ProfilePageLayout = (props: { }) => { if (!props.profile) return null; - let profileRecord = props.profile.record as unknown as ProfileViewDetailed; - - console.log(profileRecord); return ( ), controls: null, @@ -51,6 +51,7 @@ export const ProfilePageLayout = (props: { export type profileTabsType = "posts" | "comments" | "subscriptions"; const ProfilePageContent = (props: { publications: { record: Json; uri: string }[]; + posts: Post[]; profile: { did: string; handle: string | null; @@ -61,7 +62,6 @@ const ProfilePageContent = (props: { let [tab, setTab] = useState("posts"); let profileRecord = props.profile?.record as AppBskyActorProfile.Record; - console.log(profileRecord); if (!props.profile) return; return ( @@ -102,7 +102,7 @@ const ProfilePageContent = (props: { ))} - + ); }; diff --git a/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx b/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx index e7434b62..80989e60 100644 --- a/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx +++ b/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx @@ -1,5 +1,7 @@ import { Tab } from "components/Tab"; import { profileTabsType } from "../ProfilePageLayout"; +import { PostListing } from "components/PostListing"; +import type { Post } from "app/(home-pages)/reader/getReaderFeed"; export const ProfileTabs = (props: { tab: profileTabsType; @@ -37,10 +39,20 @@ export const ProfileTabs = (props: { ); }; -export const TabContent = (props: { tab: profileTabsType }) => { +export const TabContent = (props: { tab: profileTabsType; posts: Post[] }) => { switch (props.tab) { case "posts": - return
posts here!
; + return ( +
+ {props.posts.length === 0 ? ( +
No posts yet
+ ) : ( + props.posts.map((post) => ( + + )) + )} +
+ ); case "comments": return
comments here!
; case "subscriptions": diff --git a/app/p/[didOrHandle]/page.tsx b/app/p/[didOrHandle]/page.tsx index 4dddf229..d776f7b0 100644 --- a/app/p/[didOrHandle]/page.tsx +++ b/app/p/[didOrHandle]/page.tsx @@ -2,6 +2,9 @@ import { idResolver } from "app/(home-pages)/reader/idResolver"; import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; import { ProfilePageLayout } from "./ProfilePageLayout"; import { supabaseServerClient } from "supabase/serverClient"; +import { getPublicationURL } from "app/lish/createPub/getPublicationURL"; +import type { Post } from "app/(home-pages)/reader/getReaderFeed"; + export default async function ProfilePage(props: { params: Promise<{ didOrHandle: string }>; }) { @@ -26,15 +29,72 @@ export default async function ProfilePage(props: { } did = resolved; } - let { data: profile } = await supabaseServerClient - .from("bsky_profiles") - .select(`*`) - .eq("did", did) - .single(); - let { data: pubs } = await supabaseServerClient - .from("publications") - .select("*") - .eq("identity_did", did); - - return ; + + // Fetch profile, publications, and documents in parallel + let [{ data: profile }, { data: pubs }, { data: docs }] = await Promise.all([ + supabaseServerClient + .from("bsky_profiles") + .select(`*`) + .eq("did", did) + .single(), + supabaseServerClient + .from("publications") + .select("*") + .eq("identity_did", did), + supabaseServerClient + .from("documents") + .select( + `*, + comments_on_documents(count), + document_mentions_in_bsky(count), + documents_in_publications(publications(*))`, + ) + .like("uri", `at://${did}/%`) + .order("indexed_at", { ascending: false }), + ]); + + // Build a map of publications for quick lookup + let pubMap = new Map[number]>(); + for (let pub of pubs || []) { + pubMap.set(pub.uri, pub); + } + + // Transform data to Post[] format + let handle = profile?.handle ? `@${profile.handle}` : null; + let posts: Post[] = []; + + for (let doc of docs || []) { + // Find the publication for this document (if any) + let pubFromDoc = doc.documents_in_publications?.[0]?.publications; + let pub = pubFromDoc ? pubMap.get(pubFromDoc.uri) || pubFromDoc : null; + + let post: Post = { + author: handle, + documents: { + data: doc.data, + uri: doc.uri, + indexed_at: doc.indexed_at, + comments_on_documents: doc.comments_on_documents, + document_mentions_in_bsky: doc.document_mentions_in_bsky, + }, + }; + + if (pub) { + post.publication = { + href: getPublicationURL(pub), + pubRecord: pub.record, + uri: pub.uri, + }; + } + + posts.push(post); + } + + return ( + + ); } diff --git a/components/PostListing.tsx b/components/PostListing.tsx index 9a1f0d6b..6604abf0 100644 --- a/components/PostListing.tsx +++ b/components/PostListing.tsx @@ -15,36 +15,46 @@ import Link from "next/link"; import { InteractionPreview } from "./InteractionsPreview"; export const PostListing = (props: Post) => { - let pubRecord = props.publication.pubRecord as PubLeafletPublication.Record; + let pubRecord = props.publication?.pubRecord as + | PubLeafletPublication.Record + | undefined; let postRecord = props.documents.data as PubLeafletDocument.Record; let postUri = new AtUri(props.documents.uri); - let theme = usePubTheme(pubRecord.theme); - let backgroundImage = pubRecord?.theme?.backgroundImage?.image?.ref - ? blobRefToSrc( - pubRecord?.theme?.backgroundImage?.image?.ref, - new AtUri(props.publication.uri).host, - ) - : null; + let theme = usePubTheme(pubRecord?.theme); + let backgroundImage = + pubRecord?.theme?.backgroundImage?.image?.ref && props.publication + ? blobRefToSrc( + pubRecord.theme.backgroundImage.image.ref, + new AtUri(props.publication.uri).host, + ) + : null; let backgroundImageRepeat = pubRecord?.theme?.backgroundImage?.repeat; let backgroundImageSize = pubRecord?.theme?.backgroundImage?.width || 500; - let showPageBackground = pubRecord.theme?.showPageBackground; + let showPageBackground = pubRecord?.theme?.showPageBackground; let quotes = props.documents.document_mentions_in_bsky?.[0]?.count || 0; let comments = - pubRecord.preferences?.showComments === false + pubRecord?.preferences?.showComments === false ? 0 : props.documents.comments_on_documents?.[0]?.count || 0; let tags = (postRecord?.tags as string[] | undefined) || []; + // For standalone posts, link directly to the document + let postHref = props.publication + ? `${props.publication.href}/${postUri.rkey}` + : `/doc/${postUri.host}/${postUri.rkey}`; + return (
{ hover:outline-accent-contrast hover:border-accent-contrast `} > - +
{

{postRecord.description}

- + {props.publication && pubRecord && ( + + )}
-- 2.51.2