diff --git a/thoughts-frontend/app/page.tsx b/thoughts-frontend/app/page.tsx index 0ef49b5..08fb0b4 100644 --- a/thoughts-frontend/app/page.tsx +++ b/thoughts-frontend/app/page.tsx @@ -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 ( -
-
-

Your Feed

-
-
+
+
+
+

Your Feed

+
{feedData.thoughts.map((thought) => ( )}
+
); } diff --git a/thoughts-frontend/app/users/[username]/page.tsx b/thoughts-frontend/app/users/[username]/page.tsx index 9741626..7d6b675 100644 --- a/thoughts-frontend/app/users/[username]/page.tsx +++ b/thoughts-frontend/app/users/[username]/page.tsx @@ -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,53 +60,58 @@ export default async function ProfilePage({ params }: ProfilePageProps) { }} /> -
- -
-
-
- -
-
-

- {user.displayName || user.username} -

-

- @{user.username} -

+
+ +
+ +
+
+
+ +
+
+

+ {user.displayName || user.username} +

+

+ @{user.username} +

+
+ + {/* Render the FollowButton if it's not the user's own profile */} + {!isOwnProfile && token && ( + + )}
- {/* Render the FollowButton if it's not the user's own profile */} - {!isOwnProfile && token && ( - {user.bio}

+
+ + Joined {new Date(user.joinedAt).toLocaleDateString()} +
+
+ + {/* Thoughts Feed */} +
+ {thoughts.map((thought) => ( + + ))} + {thoughts.length === 0 && ( +

+ This user hasn't posted any thoughts yet. +

)}
- -

{user.bio}

-
- - Joined {new Date(user.joinedAt).toLocaleDateString()} -
- - - {/* Thoughts Feed */} -
- {thoughts.map((thought) => ( - - ))} - {thoughts.length === 0 && ( -

- This user hasn't posted any thoughts yet. -

- )}
diff --git a/thoughts-frontend/components/popular-tags.tsx b/thoughts-frontend/components/popular-tags.tsx new file mode 100644 index 0000000..9464f68 --- /dev/null +++ b/thoughts-frontend/components/popular-tags.tsx @@ -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 ( + + + Popular Tags + + +

+ No popular tags to display. +

+
+
+ ); + } + + return ( + + + Popular Tags + + + {tags.map((tag) => ( + + + + {tag} + + + ))} + + + ); +} diff --git a/thoughts-frontend/components/top-friends.tsx b/thoughts-frontend/components/top-friends.tsx new file mode 100644 index 0000000..494d4d8 --- /dev/null +++ b/thoughts-frontend/components/top-friends.tsx @@ -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 ( + + + Top Friends + + +

+ No top friends to display. +

+
+
+ ); + } + + // 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 => + result.status === "fulfilled" + ) + .map((result) => result.value); + + return ( + + + Top Friends + + + {friends.map((friend) => ( + + + + {friend.displayName || friend.username} + + + ))} + + + ); +} diff --git a/thoughts-frontend/lib/api.ts b/thoughts-frontend/lib/api.ts index dabb99a..dbb1cf9 100644 --- a/thoughts-frontend/lib/api.ts +++ b/thoughts-frontend/lib/api.ts @@ -154,4 +154,11 @@ export const unfollowUser = (username: string, token: string) => ); export const getMe = (token: string) => - apiFetch("/users/me", {}, MeSchema, token); \ No newline at end of file + apiFetch("/users/me", {}, MeSchema, token); + + export const getPopularTags = () => + apiFetch( + "/tags/popular", + {}, + z.array(z.string()) // Expect an array of strings + ); \ No newline at end of file