feat: social graph features
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user