feat: add PopularTags and TopFriends components, update profile and feed layouts to include them

This commit is contained in:
2025-09-06 19:58:53 +02:00
parent 7348433b9c
commit fc7dacc6fb
5 changed files with 172 additions and 47 deletions

View File

@@ -5,6 +5,7 @@ import { ThoughtCard } from "@/components/thought-card";
import { PostThoughtForm } from "@/components/post-thought-form";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { PopularTags } from "@/components/popular-tags";
// This is now an async Server Component
export default async function Home() {
@@ -31,11 +32,11 @@ async function FeedPage({ token }: { token: string }) {
);
return (
<div className="container mx-auto max-w-2xl p-4 sm:p-6">
<div className="container mx-auto max-w-4xl p-4 sm:p-6 grid grid-cols-1 md:grid-cols-3 gap-8">
<main className="md:col-span-2 space-y-6">
<header className="my-6">
<h1 className="text-3xl font-bold">Your Feed</h1>
</header>
<main className="space-y-6">
<PostThoughtForm />
{feedData.thoughts.map((thought) => (
<ThoughtCard
@@ -53,6 +54,9 @@ async function FeedPage({ token }: { token: string }) {
</p>
)}
</main>
<aside className="md:col-span-1 space-y-6 pt-20">
<PopularTags />
</aside>
</div>
);
}

View File

@@ -6,6 +6,7 @@ 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";
interface ProfilePageProps {
params: { username: string };
@@ -59,7 +60,11 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
}}
/>
<main className="container mx-auto max-w-3xl p-4 -mt-16">
<main className="container mx-auto max-w-3xl p-4 -mt-16 grid grid-cols-1 md:grid-cols-3 gap-8">
<aside className="md:col-span-1 space-y-6 pt-24">
<TopFriends usernames={user.topFriends} />
</aside>
<div className="md:col-span-2 mt-8 md:mt-0 space-y-4">
<Card className="p-6 bg-card/80 backdrop-blur-lg">
<div className="flex justify-between items-start">
<div className="flex items-end gap-4">
@@ -107,6 +112,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
</p>
)}
</div>
</div>
</main>
</div>
);

View File

@@ -0,0 +1,45 @@
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { getPopularTags } from "@/lib/api";
import { Hash } from "lucide-react";
export async function PopularTags() {
const tags = await getPopularTags().catch(() => []);
if (tags.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>Popular Tags</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
No popular tags to display.
</p>
</CardContent>
</Card>
);
}
return (
<Card>
<CardHeader>
<CardTitle>Popular Tags</CardTitle>
</CardHeader>
<CardContent className="flex flex-wrap gap-2">
{tags.map((tag) => (
<Link href={`/tags/${tag}`} key={tag}>
<Badge
variant="secondary"
className="hover:bg-accent cursor-pointer"
>
<Hash className="mr-1 h-3 w-3" />
{tag}
</Badge>
</Link>
))}
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,63 @@
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { UserAvatar } from "./user-avatar";
import { getUserProfile, User } from "@/lib/api";
import { cookies } from "next/headers";
interface TopFriendsProps {
usernames: string[];
}
// This is an async Server Component
export async function TopFriends({ usernames }: TopFriendsProps) {
const token = (await cookies()).get("auth_token")?.value ?? null;
if (usernames.length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>Top Friends</CardTitle>
</CardHeader>
<CardContent>
<p className="text-center text-muted-foreground">
No top friends to display.
</p>
</CardContent>
</Card>
);
}
// Fetch all top friend profiles in parallel
const friendsResults = await Promise.allSettled(
usernames.map((username) => getUserProfile(username, token))
);
const friends = friendsResults
.filter(
(result): result is PromiseFulfilledResult<User> =>
result.status === "fulfilled"
)
.map((result) => result.value);
return (
<Card>
<CardHeader>
<CardTitle>Top Friends</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-4 gap-4">
{friends.map((friend) => (
<Link
href={`/users/${friend.username}`}
key={friend.id}
className="flex flex-col items-center gap-2 text-center group"
>
<UserAvatar src={friend.avatarUrl} alt={friend.username} />
<span className="text-xs font-medium truncate w-full group-hover:underline">
{friend.displayName || friend.username}
</span>
</Link>
))}
</CardContent>
</Card>
);
}

View File

@@ -155,3 +155,10 @@ export const unfollowUser = (username: string, token: string) =>
export const getMe = (token: string) =>
apiFetch("/users/me", {}, MeSchema, token);
export const getPopularTags = () =>
apiFetch(
"/tags/popular",
{},
z.array(z.string()) // Expect an array of strings
);