import type { Metadata } from "next"; import { getFollowersList, getFollowingList, getMe, getRemoteFollowers, getRemoteFollowing, getUserProfile, getUserThoughts, Me, } from "@/lib/api"; import { CssPreviewListener } from "@/components/css-preview-listener"; interface ProfilePageProps { params: Promise<{ username: string }>; } export async function generateMetadata({ params, }: ProfilePageProps): Promise { const { username } = await params; const user = await getUserProfile(username, null).catch(() => null); if (!user) return { title: username }; const name = user.displayName || user.username; const description = user.bio || `Follow ${name} on Thoughts and across the Fediverse.`; return { title: `${name} (@${user.username})`, description, openGraph: { type: "profile", title: `${name} (@${user.username})`, description, images: user.avatarUrl ? [{ url: user.avatarUrl }] : [], }, twitter: { card: "summary", title: `${name} (@${user.username})`, description, images: user.avatarUrl ? [user.avatarUrl] : [], }, }; } import { EmptyState } from "@/components/empty-state"; import { UserAvatar } from "@/components/user-avatar"; import { Calendar, Settings } from "lucide-react"; import { Card } from "@/components/ui/card"; import { notFound } from "next/navigation"; import { cookies } from "next/headers"; import { FollowButton } from "@/components/follow-button"; import { TopFriends } from "@/components/top-friends"; import { Suspense } from "react"; import { ProfileSkeleton } from "@/components/loading-skeleton"; import { UserThoughtsList } from "@/components/user-thoughts-list"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { PendingRequests } from "@/components/federation/pending-requests"; interface ProfilePageProps { params: Promise<{ username: string }>; } export default async function ProfilePage({ params }: ProfilePageProps) { const { username } = await params; const token = (await cookies()).get("auth_token")?.value ?? null; const userProfilePromise = getUserProfile(username, token); const thoughtsPromise = getUserThoughts(username, token); const mePromise = token ? getMe(token) : Promise.resolve(null); const followersPromise = getFollowersList(username, token); const followingPromise = getFollowingList(username, token); const [ userResult, thoughtsResult, meResult, followersResult, followingResult, ] = await Promise.allSettled([ userProfilePromise, thoughtsPromise, mePromise, followersPromise, followingPromise, ]); if (userResult.status === "rejected") { notFound(); } const user = userResult.value; const me = meResult.status === "fulfilled" ? (meResult.value as Me) : null; const thoughtsData = thoughtsResult.status === "fulfilled" ? thoughtsResult.value : null; const thoughts = thoughtsData?.items ?? []; const totalPages = thoughtsData ? Math.ceil(thoughtsData.total / thoughtsData.per_page) : 1; const localFollowersCount = followersResult.status === "fulfilled" ? followersResult.value.total : 0; const localFollowingCount = followingResult.status === "fulfilled" ? followingResult.value.total : 0; const isOwnProfile = me?.username === user.username; const [remoteFollowersCount, remoteFollowingCount] = isOwnProfile && token ? await Promise.all([ getRemoteFollowers(token).then((r) => r.length).catch(() => 0), getRemoteFollowing(token).then((r) => r.length).catch(() => 0), ]) : [0, 0]; const followersCount = localFollowersCount + remoteFollowersCount; const followingCount = localFollowingCount + remoteFollowingCount; const isFollowing = user.isFollowedByViewer; const apiDomain = process.env.NEXT_PUBLIC_API_URL ? new URL(process.env.NEXT_PUBLIC_API_URL).hostname : null; const fediverseHandle = user.local && apiDomain ? `@${user.username}@${apiDomain}` : null; return (
{user.customCss && (