From c520690f1e0d1b6d05068558b0e2b1061b18a1e0 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 6 Sep 2025 22:37:06 +0200 Subject: [PATCH] feat: add TopFriendsCombobox component for selecting top friends, update edit profile form to use it, and implement getFriends API --- .../app/thoughts/[thoughtId]/page.tsx | 2 +- .../components/edit-profile-form.tsx | 19 ++-- thoughts-frontend/components/thought-card.tsx | 28 +++-- .../components/top-friends-combobox.tsx | 105 ++++++++++++++++++ thoughts-frontend/lib/api.ts | 8 ++ 5 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 thoughts-frontend/components/top-friends-combobox.tsx diff --git a/thoughts-frontend/app/thoughts/[thoughtId]/page.tsx b/thoughts-frontend/app/thoughts/[thoughtId]/page.tsx index 4499496..664972f 100644 --- a/thoughts-frontend/app/thoughts/[thoughtId]/page.tsx +++ b/thoughts-frontend/app/thoughts/[thoughtId]/page.tsx @@ -70,7 +70,7 @@ export default async function ThoughtPage({ params }: ThoughtPageProps) { return (
-

Conversation

+

Thoughts

( - + Top Friends - - field.onChange( - e.target.value.split(",").map((s) => s.trim()) - ) - } + - A comma-separated list of usernames. + Select up to 8 of your friends to display on your profile. diff --git a/thoughts-frontend/components/thought-card.tsx b/thoughts-frontend/components/thought-card.tsx index 3e4dd3c..2ee86a0 100644 --- a/thoughts-frontend/components/thought-card.tsx +++ b/thoughts-frontend/components/thought-card.tsx @@ -112,14 +112,14 @@ export function ThoughtCard({ {timeAgo}
- {isAuthor && ( - - - - - + + + + + + {isAuthor && ( setIsAlertOpen(true)} @@ -127,9 +127,15 @@ export function ThoughtCard({ Delete - - - )} + )} + + + + View + + + +

{thought.content}

diff --git a/thoughts-frontend/components/top-friends-combobox.tsx b/thoughts-frontend/components/top-friends-combobox.tsx new file mode 100644 index 0000000..0d0b92f --- /dev/null +++ b/thoughts-frontend/components/top-friends-combobox.tsx @@ -0,0 +1,105 @@ +"use client"; + +import * as React from "react"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { getFriends, User } from "@/lib/api"; +import { useAuth } from "@/hooks/use-auth"; +import { Skeleton } from "./ui/skeleton"; + +interface TopFriendsComboboxProps { + value: string[]; + onChange: (value: string[]) => void; +} + +export function TopFriendsCombobox({ + value, + onChange, +}: TopFriendsComboboxProps) { + const [open, setOpen] = React.useState(false); + const [friends, setFriends] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(true); + const { token } = useAuth(); + + React.useEffect(() => { + if (token) { + getFriends(token) + .then((data) => setFriends(data.users)) + .catch(() => console.error("Failed to fetch friends")) + .finally(() => setIsLoading(false)); + } else { + setIsLoading(false); + } + }, [token]); + + if (isLoading) { + return ; + } + + return ( + + + + + + + + + No friends found. + + {friends.map((friend) => ( + { + const newValue = value.includes(currentValue) + ? value.filter((v) => v !== currentValue) + : [...value, currentValue]; + + if (newValue.length <= 8) { + onChange(newValue); + } + }} + > + + {friend.username} + + ))} + + + + + + ); +} diff --git a/thoughts-frontend/lib/api.ts b/thoughts-frontend/lib/api.ts index 7cee691..94d0b7c 100644 --- a/thoughts-frontend/lib/api.ts +++ b/thoughts-frontend/lib/api.ts @@ -224,4 +224,12 @@ export const getFollowersList = (username: string, token: string | null) => {}, z.object({ users: z.array(UserSchema) }), token + ); + + export const getFriends = (token: string) => + apiFetch( + "/friends", + {}, + z.object({ users: z.array(UserSchema) }), + token ); \ No newline at end of file