Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/app/(private)/friends/_components/AddFriendButton.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";
import React from 'react';
import { UserRoundPlus } from 'lucide-react';
import React from "react";
import { UserRoundPlus } from "lucide-react";

export default function AddFriendButton() {
return (
<button className="bg-yellow-500 text-black border-black shadow-md px-6 py-3 rounded-md mt-4 flex items-center">
<div>
<UserRoundPlus />
</div>
Add New Friend
</button>
);
}
return (
<button className="bg-yellow-500 text-black border-black shadow-md px-5 py-2 rounded-md flex items-center">
<div>
<UserRoundPlus />
</div>
Add New Friend
</button>
);
}
4 changes: 3 additions & 1 deletion src/app/(private)/friends/_components/FriendRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ interface FriendRequestProps {
name: string;
onAccept: () => void;
onDeny: () => void;
onCancel: () => void;
isSender: boolean;
}

export default function FriendRequest({
name,
onAccept,
onDeny,
onCancel,
isSender,
}: FriendRequestProps) {
{
Expand All @@ -29,7 +31,7 @@ export default function FriendRequest({
{isSender ? (
<button
className="bg-red-500 text-black px-4 py-2 rounded-md border border-black"
onClick={onDeny}
onClick={onCancel}
>
Cancel
</button>
Expand Down
187 changes: 114 additions & 73 deletions src/app/(private)/friends/_components/Friends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import { getApiBase } from "@/utils/etc/apiBase";
import { useUser } from "@/utils/context/userContext";
import { toast } from "react-hot-toast";
import Loading from "@/app/components/loading";
import AddFriendModal from "../../hangouts/_components/AddFriendModal";

type FetchFriendsResponse = {
status: string;
friends: Friend[];
};

type Friend = {
export type Friend = {
id: string;
friend_uuid: string;
friend_auth_id: string;
friend_email: string;
friend_username: string;
status: string;
Expand All @@ -37,10 +38,15 @@ export default function Friends() {
const [query, setQuery] = useState("");
const [suggestion, setSuggestion] = useState<Friend[] | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [hasTrigged, setHasTrigged] = useState<boolean>(false);
const user = useUser();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

async function fetchFriends() {
setLoading(true);
if (!hasTrigged) {
setLoading(true);
setHasTrigged(true);
}
const base = getApiBase();
const response = await fetch(`${base}/fetch-friends`, {
method: "POST",
Expand Down Expand Up @@ -91,7 +97,11 @@ export default function Friends() {
}
}

async function declineFriendRequest(friendshipId: string, removing: boolean) {
async function declineFriendRequest(
friendshipId: string,
removing: boolean,
canceling?: boolean
) {
const base = getApiBase();
const response = await fetch(`${base}/remove-friend`, {
method: "POST",
Expand All @@ -104,12 +114,17 @@ export default function Friends() {
});

if (response.ok) {
const toastSuccessMessage = removing
let toastSuccessMessage = removing
? "Succesfully removed friend"
: "Succesfully declined friend request";
const toastErrorMessage = removing
let toastErrorMessage = removing
? "Unable to removed friend"
: "Unable to decline friend request";
if (canceling) {
toastSuccessMessage = "Succesfully canceled pending friend request.";
toastErrorMessage = "Unable to cancel pending friend-request";
}

const data = (await response.json()) as APIResponse;
if (data.status !== 200) {
toast.error(toastErrorMessage, {
Expand Down Expand Up @@ -145,80 +160,106 @@ export default function Friends() {
}, [query]);

return (
<div className="flex flex-col pt-10 bg-lightBlue min-h-screen text-black w-full px-10">
<div className="font-semibold text-5xl mb-6 pb-8">Friends</div>
{loading ? (
<Loading />
) : (
<>
<div className="pb-8">
<SearchBar
value={query}
onChange={handleChange}
placeholder=" Search friends..."
/>
</div>
{suggestion ? (
suggestion?.map((friend) => {
if (friend.status === "pending") {
return (
<FriendRequest
isSender={friend.sender === user?.auth_id}
name={friend.friend_username}
onAccept={() => {
acceptFriendRequest(friend.id);
}}
onDeny={() => {
declineFriendRequest(friend.id, false);
}}
/>
);
} else
return (
<>
<AddFriendModal
Copy link
Copy Markdown
Collaborator Author

@olukukoyi olukukoyi May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explanation of this huge diff:

added modal to friends page and wrapped both the old code and the new modal in a fragment

friends={friends.map((friend) => friend.friend_auth_id)}
pendingFriends={pendingFriends.map((friend) => friend.friend_auth_id)}
isOpen={isModalOpen}
onClose={(rerender: boolean) => {
if (rerender) {
fetchFriends();
}
setIsModalOpen(false);
}}
/>

<div className="flex flex-col pt-10 bg-lightBlue min-h-screen text-black w-full px-10">
<div className="font-semibold text-5xl mb-6 pb-8">Friends</div>
{loading ? (
<Loading />
) : (
<>
<div className="flex w-full items-center justify-between pb-8">
<SearchBar
value={query}
onChange={handleChange}
placeholder=" Search friends..."
/>
<div
className="flex w-full justify-end"
onClick={() => {
setIsModalOpen(true);
}}
>
<AddFriendButton />
</div>
</div>
{suggestion ? (
suggestion?.map((friend) => {
if (friend.status === "pending") {
return (
<div key={`pending-${friend.id}`} className="pb-8">
<FriendRequest
isSender={friend.sender === user?.auth_id}
name={friend.friend_username}
onAccept={() => {
acceptFriendRequest(friend.id);
}}
onDeny={() => {
declineFriendRequest(friend.id, false);
}}
onCancel={() => {
declineFriendRequest(friend.id, false, true);
}}
/>
</div>
);
} else
return (
<div key={`pending-${friend.id}`} className="pb-8">
<FriendList
name={friend.friend_username}
onRemove={() => {
declineFriendRequest(friend.id, true);
}}
/>
</div>
);
})
) : (
<div className="w-full space-y-4 max-w-full pb-8">
{pendingFriends.map((friend) => (
<div key={`pending-${friend.id}`} className="pb-8">
<FriendRequest
isSender={friend.sender === user?.auth_id}
name={friend.friend_username}
onAccept={() => {
acceptFriendRequest(friend.id);
}}
onDeny={() => {
declineFriendRequest(friend.id, false);
}}
onCancel={() => {
declineFriendRequest(friend.id, false, true);
}}
/>
</div>
))}
{friends.map((friend) => (
<div key={`friend-${friend.id}`} className="pb-8">
<FriendList
name={friend.friend_username}
onRemove={() => {
declineFriendRequest(friend.id, true);
}}
/>
</div>
);
})
) : (
<div className="w-full space-y-4 max-w-full pb-8">
{pendingFriends.map((friend) => (
<div key={`pending-${friend.id}`} className="pb-8">
<FriendRequest
isSender={friend.sender === user?.auth_id}
name={friend.friend_username}
onAccept={() => {
acceptFriendRequest(friend.id);
}}
onDeny={() => {
declineFriendRequest(friend.id, false);
}}
/>
</div>
))}
{friends.map((friend) => (
<div key={`friend-${friend.id}`} className="pb-8">
<FriendList
name={friend.friend_username}
onRemove={() => {
declineFriendRequest(friend.id, true);
}}
/>
</div>
))}
</div>
)}

<div className="flex w-full justify-center">
<AddFriendButton />
</div>
</>
)}
</div>
))}
</div>
)}
</>
)}
</div>
</>
);
}
27 changes: 27 additions & 0 deletions src/app/(private)/friends/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use server";
import { createClient } from "@/utils/supabase/server";

export type Suggestions = {
auth_id: string;
username: string;
}[];

export async function findPeople(username: string) {
Copy link
Copy Markdown
Collaborator Author

@olukukoyi olukukoyi May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little helper fn to get all users in auth table are return them.

no error handling here bc

  1. if there are no results data will be an empty array.
  2. if there was an error during the query, data will also be empty

i wanted this fn to be failsafe and simple

const supabase = await createClient();

const {
data: { user },
} = await supabase.auth.getUser();

if (!user?.id) {
console.error("User not authenticated");
return { status: 401, error: "User not authenticated" };
}

const { data, error } = await supabase
.from("users")
.select("auth_id, username")
.ilike("username", `%${username}%`);

return data as Suggestions;
}
Loading