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 (
+
+
+
+ );
+};
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}
-
-
-
-
-
-
-
-
-
- );
-};
-
-const PubListingCompact = () => {
- return ;
-};
-
-const PublicationCard = (props: {
- record: PubLeafletPublication.Record;
- uri: string;
-}) => {
- const { record, uri } = props;
- const { bgLeaflet, bgPage } = usePubTheme(record.theme);
-
- return (
-
-
-
- );
-};
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 (
-
- );
-}