feat: social graph features

This commit is contained in:
gosangam
2026-06-04 19:30:50 +05:30
parent 0f433c5708
commit 684ea2ae97
16 changed files with 2504 additions and 119 deletions
+6
View File
@@ -120,11 +120,17 @@ export default function PublicProfileLayout(props: {
return (
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6">
<ProfileHeader
key={data.handle}
avatarURL={data.avatar_url}
name={data.name}
handle={data.handle}
postCount={data.post_count}
followerCount={data.follower_count}
followingCount={data.following_count}
relationship={data.relationship}
enableActions
joinedAt={data.joined_at}
country={data.country}
/>
<ProfileTabs tabs={tabs} />
<PublicProfileContext.Provider value={data}>
+38 -1
View File
@@ -13,7 +13,7 @@
* via a query param (kept simple — no new route for the form).
*/
import { Suspense, useEffect } from "react";
import { Suspense, useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import {
AtSign,
@@ -23,6 +23,7 @@ import {
} from "lucide-react";
import { useAuth } from "@/components/auth/auth-provider";
import { useAuthGate } from "@/components/auth/auth-gate";
import { profile } from "@/lib/social";
import { ProfileHeader } from "@/components/profile/profile-header";
import { ProfileTabs, type ProfileTab } from "@/components/profile/profile-tabs";
@@ -43,6 +44,37 @@ export default function MeLayout({ children }: { children: React.ReactNode }) {
};
}, [loading, user, requireAuth, router]);
// Own profile counts (posts / followers / following) for the header
// stat line. /auth/me doesn't carry these, so we read them from the
// public profile endpoint (which returns no relationship for self).
const [counts, setCounts] = useState<{
posts: number;
followers: number;
following: number;
} | null>(null);
const handle = user?.handle;
useEffect(() => {
if (!handle) return;
let cancelled = false;
profile
.get(handle)
.then((p) => {
if (!cancelled) {
setCounts({
posts: p.post_count,
followers: p.follower_count,
following: p.following_count,
});
}
})
.catch(() => {
/* counts are non-critical; leave the stat line minimal on error */
});
return () => {
cancelled = true;
};
}, [handle]);
if (loading || !user) {
return (
<main className="flex-1 flex items-center justify-center px-4 py-12">
@@ -80,11 +112,16 @@ export default function MeLayout({ children }: { children: React.ReactNode }) {
return (
<main className="flex-1 max-w-3xl w-full mx-auto px-4 sm:px-6">
<ProfileHeader
// Re-seed the header's count state once the async counts land.
key={counts ? "me-ready" : "me-pending"}
avatarURL={user.avatar_url}
name={user.name}
handle={user.handle}
joinedAt={user.created_at}
editHref="/u/me?edit=1"
postCount={counts?.posts}
followerCount={counts?.followers}
followingCount={counts?.following}
/>
{/* Tab strip hides on /u/me?edit=1 (see ConditionalTabs). The
Suspense boundary is required by Next.js because useSearchParams