From 9ede2f2ec5a89ca92d50cf61fa2196574964415b Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Fri, 12 Dec 2025 13:01:05 +0400 Subject: [PATCH] render profile sub things as pages --- .../Tabs.tsx => (profile)/PostsContent.tsx} | 65 +------- .../(profile)/ProfileDashboardLayout.tsx | 42 +++++ .../[didOrHandle]/(profile)/ProfileHeader.tsx | 79 +++++++++ app/p/[didOrHandle]/(profile)/ProfileTabs.tsx | 53 ++++++ .../comments/page.tsx} | 8 +- app/p/[didOrHandle]/(profile)/layout.tsx | 73 +++++++++ app/p/[didOrHandle]/(profile)/page.tsx | 24 +++ .../(profile)/subscriptions/page.tsx | 3 + app/p/[didOrHandle]/ProfilePageLayout.tsx | 151 ------------------ .../ProfileTabs/TabContent.tsx/Post.tsx | 0 .../TabContent.tsx/Subscription.tsx | 0 app/p/[didOrHandle]/page.tsx | 55 ------- 12 files changed, 282 insertions(+), 271 deletions(-) rename app/p/[didOrHandle]/{ProfileTabs/Tabs.tsx => (profile)/PostsContent.tsx} (61%) create mode 100644 app/p/[didOrHandle]/(profile)/ProfileDashboardLayout.tsx create mode 100644 app/p/[didOrHandle]/(profile)/ProfileHeader.tsx create mode 100644 app/p/[didOrHandle]/(profile)/ProfileTabs.tsx rename app/p/[didOrHandle]/{ProfileTabs/TabContent.tsx/Comment.tsx => (profile)/comments/page.tsx} (93%) create mode 100644 app/p/[didOrHandle]/(profile)/layout.tsx create mode 100644 app/p/[didOrHandle]/(profile)/page.tsx create mode 100644 app/p/[didOrHandle]/(profile)/subscriptions/page.tsx delete mode 100644 app/p/[didOrHandle]/ProfilePageLayout.tsx delete mode 100644 app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Post.tsx delete mode 100644 app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Subscription.tsx delete mode 100644 app/p/[didOrHandle]/page.tsx diff --git a/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx b/app/p/[didOrHandle]/(profile)/PostsContent.tsx similarity index 61% rename from app/p/[didOrHandle]/ProfileTabs/Tabs.tsx rename to app/p/[didOrHandle]/(profile)/PostsContent.tsx index 59c354e0..2242b938 100644 --- a/app/p/[didOrHandle]/ProfileTabs/Tabs.tsx +++ b/app/p/[didOrHandle]/(profile)/PostsContent.tsx @@ -1,72 +1,13 @@ -import { Tab } from "components/Tab"; -import { profileTabsType } from "../ProfilePageLayout"; +"use client"; + import { PostListing } from "components/PostListing"; import type { Post } from "app/(home-pages)/reader/getReaderFeed"; import type { Cursor } from "../getProfilePosts"; import { getProfilePosts } from "../getProfilePosts"; import useSWRInfinite from "swr/infinite"; import { useEffect, useRef } from "react"; -import { CommentTabContent } from "./TabContent.tsx/Comment"; - -export const ProfileTabs = (props: { - tab: profileTabsType; - setTab: (t: profileTabsType) => void; -}) => { - return ( -
-
-
- { - props.setTab("posts"); - }} - /> - { - props.setTab("comments"); - }} - /> -
- { - props.setTab("subscriptions"); - }} - /> -
-
-
- ); -}; - -export const TabContent = (props: { - tab: profileTabsType; - did: string; - posts: Post[]; - nextCursor: Cursor | null; -}) => { - switch (props.tab) { - case "posts": - return ( - - ); - case "comments": - return ; - case "subscriptions": - return
subscriptions here!
; - } -}; -const ProfilePostsContent = (props: { +export const ProfilePostsContent = (props: { did: string; posts: Post[]; nextCursor: Cursor | null; diff --git a/app/p/[didOrHandle]/(profile)/ProfileDashboardLayout.tsx b/app/p/[didOrHandle]/(profile)/ProfileDashboardLayout.tsx new file mode 100644 index 00000000..dd2d846c --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/ProfileDashboardLayout.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Actions } from "app/(home-pages)/home/Actions/Actions"; +import { Footer } from "components/ActionBar/Footer"; +import { Sidebar } from "components/ActionBar/Sidebar"; +import { + DesktopNavigation, + MobileNavigation, +} from "components/ActionBar/Navigation"; +import { MediaContents } from "components/Media"; +import { Separator } from "components/Layout"; + +export function ProfileDashboardLayout(props: { + did: string; + children: React.ReactNode; +}) { + return ( +
+ +
+ + + + +
+
+
+ {props.children} +
+
+ + + +
+
+ ); +} diff --git a/app/p/[didOrHandle]/(profile)/ProfileHeader.tsx b/app/p/[didOrHandle]/(profile)/ProfileHeader.tsx new file mode 100644 index 00000000..295aa981 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/ProfileHeader.tsx @@ -0,0 +1,79 @@ +"use client"; +import { Avatar } from "components/Avatar"; +import { AppBskyActorProfile, PubLeafletPublication } from "lexicons/api"; +import { blobRefToSrc } from "src/utils/blobRefToSrc"; +import type { ProfileData } from "./layout"; +import { usePubTheme } from "components/ThemeManager/PublicationThemeProvider"; +import { colorToString } from "components/ThemeManager/useColorAttribute"; +import { PubIcon } from "components/ActionBar/Publications"; +import { Json } from "supabase/database.types"; + +export const ProfileHeader = (props: { + profile: ProfileData; + publications: { record: Json; uri: string }[]; +}) => { + let profileRecord = props.profile.record as AppBskyActorProfile.Record; + + return ( + <> + +
+

+ {profileRecord.displayName + ? profileRecord.displayName + : `@${props.profile.handle}`} +

+ {profileRecord.displayName && ( +
+ @{props.profile.handle} +
+ )} +
{profileRecord.description}
+
+ {props.publications.map((p) => ( + + ))} +
+
+ + ); +}; + +const PublicationCard = (props: { + record: PubLeafletPublication.Record; + uri: string; +}) => { + const { record, uri } = props; + const { bgLeaflet, bgPage } = usePubTheme(record.theme); + + return ( + +
+ +

{record.name}

+
+
+ ); +}; diff --git a/app/p/[didOrHandle]/(profile)/ProfileTabs.tsx b/app/p/[didOrHandle]/(profile)/ProfileTabs.tsx new file mode 100644 index 00000000..62e175a2 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/ProfileTabs.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { SpeedyLink } from "components/SpeedyLink"; +import { useSelectedLayoutSegment } from "next/navigation"; + +export type ProfileTabType = "posts" | "comments" | "subscriptions"; + +export const ProfileTabs = (props: { didOrHandle: string }) => { + const segment = useSelectedLayoutSegment(); + const currentTab = (segment || "posts") as ProfileTabType; + + const baseUrl = `/p/${props.didOrHandle}`; + + return ( +
+
+
+ + +
+ +
+
+
+ ); +}; + +const TabLink = (props: { href: string; name: string; selected: boolean }) => { + return ( + + {props.name} + + ); +}; diff --git a/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Comment.tsx b/app/p/[didOrHandle]/(profile)/comments/page.tsx similarity index 93% rename from app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Comment.tsx rename to app/p/[didOrHandle]/(profile)/comments/page.tsx index 49964840..fc76cbb9 100644 --- a/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Comment.tsx +++ b/app/p/[didOrHandle]/(profile)/comments/page.tsx @@ -1,8 +1,10 @@ -import Post from "app/lish/[did]/[publication]/[rkey]/l-quote/[quote]/page"; -import { CommentTiny } from "components/Icons/CommentTiny"; import { ReplyTiny } from "components/Icons/ReplyTiny"; -export const CommentTabContent = () => { +export default function ProfileCommentsPage() { + return ; +} + +const CommentsContent = () => { let isReply = true; return ( <> diff --git a/app/p/[didOrHandle]/(profile)/layout.tsx b/app/p/[didOrHandle]/(profile)/layout.tsx new file mode 100644 index 00000000..0d7adfd1 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/layout.tsx @@ -0,0 +1,73 @@ +import { idResolver } from "app/(home-pages)/reader/idResolver"; +import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; +import { supabaseServerClient } from "supabase/serverClient"; +import { Json } from "supabase/database.types"; +import { ProfileHeader } from "./ProfileHeader"; +import { ProfileTabs } from "./ProfileTabs"; +import { ProfileDashboardLayout } from "./ProfileDashboardLayout"; + +export default async function ProfileLayout(props: { + params: Promise<{ didOrHandle: string }>; + children: React.ReactNode; +}) { + let params = await props.params; + let didOrHandle = decodeURIComponent(params.didOrHandle); + + // Resolve handle to DID if necessary + let did = didOrHandle; + + if (!didOrHandle.startsWith("did:")) { + let resolved = await idResolver.handle.resolve(didOrHandle); + if (!resolved) { + return ( + +

Sorry, can't resolve handle!

+

+ This may be a glitch on our end. If the issue persists please{" "} + send us a note. +

+
+ ); + } + did = resolved; + } + + let { data: profile } = await supabaseServerClient + .from("bsky_profiles") + .select(`*`) + .eq("did", did) + .single(); + let { data: publications } = await supabaseServerClient + .from("publications") + .select("*") + .eq("identity_did", did); + + if (!profile) return null; + + return ( + +
+
+ + +
+ {props.children} +
+
+
+
+ ); +} + +export type ProfileData = { + did: string; + handle: string | null; + indexed_at: string; + record: Json; +}; diff --git a/app/p/[didOrHandle]/(profile)/page.tsx b/app/p/[didOrHandle]/(profile)/page.tsx new file mode 100644 index 00000000..d608d5b2 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/page.tsx @@ -0,0 +1,24 @@ +import { idResolver } from "app/(home-pages)/reader/idResolver"; +import { getProfilePosts } from "../getProfilePosts"; +import { ProfilePostsContent } from "./PostsContent"; + +export default async function ProfilePostsPage(props: { + params: Promise<{ didOrHandle: string }>; +}) { + let params = await props.params; + let didOrHandle = decodeURIComponent(params.didOrHandle); + + // Resolve handle to DID if necessary + let did = didOrHandle; + if (!didOrHandle.startsWith("did:")) { + let resolved = await idResolver.handle.resolve(didOrHandle); + if (!resolved) return null; + did = resolved; + } + + const { posts, nextCursor } = await getProfilePosts(did); + + return ( + + ); +} diff --git a/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx b/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx new file mode 100644 index 00000000..d9c79f04 --- /dev/null +++ b/app/p/[didOrHandle]/(profile)/subscriptions/page.tsx @@ -0,0 +1,3 @@ +export default function ProfileSubscriptionsPage() { + return
subscriptions here!
; +} diff --git a/app/p/[didOrHandle]/ProfilePageLayout.tsx b/app/p/[didOrHandle]/ProfilePageLayout.tsx deleted file mode 100644 index 9eefdfeb..00000000 --- a/app/p/[didOrHandle]/ProfilePageLayout.tsx +++ /dev/null @@ -1,151 +0,0 @@ -"use client"; -import { Actions } from "app/(home-pages)/home/Actions/Actions"; -import { DashboardLayout } from "components/PageLayouts/DashboardLayout"; -import { useState } from "react"; -import { ProfileTabs, TabContent } from "./ProfileTabs/Tabs"; -import { Json } from "supabase/database.types"; -import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs"; -import { Avatar } from "components/Avatar"; -import { AppBskyActorProfile, PubLeafletPublication } from "lexicons/api"; -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"; -import type { Cursor } from "./getProfilePosts"; - -export const ProfilePageLayout = (props: { - publications: { record: Json; uri: string }[]; - posts: Post[]; - nextCursor: Cursor | null; - profile: { - did: string; - handle: string | null; - indexed_at: string; - record: Json; - } | null; -}) => { - if (!props.profile) return null; - - return ( - - ), - controls: null, - }, - }} - actions={} - currentPage="home" - /> - ); -}; - -export type profileTabsType = "posts" | "comments" | "subscriptions"; -const ProfilePageContent = (props: { - publications: { record: Json; uri: string }[]; - posts: Post[]; - nextCursor: Cursor | null; - profile: { - did: string; - handle: string | null; - indexed_at: string; - record: Json; - } | null; -}) => { - let [tab, setTab] = useState("posts"); - - let profileRecord = props.profile?.record as AppBskyActorProfile.Record; - - if (!props.profile) return; - return ( -
-
- -
-

- {profileRecord.displayName - ? profileRecord.displayName - : `@${props.profile?.handle}`} -

- {profileRecord.displayName && ( -
- @{props.profile?.handle} -
- )} -
{profileRecord.description}
-
-
pub 1
-
pub 2
-
-
- - -
- -
-
-
- ); -}; - -const PubListingCompact = () => { - return
; -}; - -const PublicationCard = (props: { - record: PubLeafletPublication.Record; - uri: string; -}) => { - const { record, uri } = props; - const { bgLeaflet, bgPage } = usePubTheme(record.theme); - - return ( - -
- -

{record.name}

-
-
- ); -}; diff --git a/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Post.tsx b/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Post.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Subscription.tsx b/app/p/[didOrHandle]/ProfileTabs/TabContent.tsx/Subscription.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/app/p/[didOrHandle]/page.tsx b/app/p/[didOrHandle]/page.tsx deleted file mode 100644 index db3798a9..00000000 --- a/app/p/[didOrHandle]/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { idResolver } from "app/(home-pages)/reader/idResolver"; -import { NotFoundLayout } from "components/PageLayouts/NotFoundLayout"; -import { ProfilePageLayout } from "./ProfilePageLayout"; -import { supabaseServerClient } from "supabase/serverClient"; -import { getProfilePosts } from "./getProfilePosts"; - -export default async function ProfilePage(props: { - params: Promise<{ didOrHandle: string }>; -}) { - let params = await props.params; - let didOrHandle = decodeURIComponent(params.didOrHandle); - - // Resolve handle to DID if necessary - let did = didOrHandle; - - if (!didOrHandle.startsWith("did:")) { - let resolved = await idResolver.handle.resolve(didOrHandle); - if (!resolved) { - return ( - -

Sorry, can't resolve handle!

-

- This may be a glitch on our end. If the issue persists please{" "} - send us a note. -

-
- ); - } - did = resolved; - } - - // Fetch profile, publications, and initial posts in parallel - let [{ data: profile }, { data: pubs }, { posts, nextCursor }] = - await Promise.all([ - supabaseServerClient - .from("bsky_profiles") - .select(`*`) - .eq("did", did) - .single(), - supabaseServerClient - .from("publications") - .select("*") - .eq("identity_did", did), - getProfilePosts(did), - ]); - - return ( - - ); -} -- 2.51.2