199 lines
6.2 KiB
TypeScript
199 lines
6.2 KiB
TypeScript
/**
|
|
* @file components/friends/FriendsView.tsx
|
|
* @description Interactive Discord-style friends list view component orchestrating tabs.
|
|
*/
|
|
|
|
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { AppHeader } from "@/components/layout/AppHeader";
|
|
import { TabType } from "@/components/friends/FriendsHeader";
|
|
import { AddFriendTab } from "./AddFriendTab";
|
|
import { PendingRequestsTab } from "./PendingRequestsTab";
|
|
import { AllFriendsTab } from "./AllFriendsTab";
|
|
import { UserStatus } from "@/db/schema";
|
|
|
|
/**
|
|
* Status types for a friendship relation.
|
|
*
|
|
* @type {FriendshipStatus}
|
|
*/
|
|
export type FriendshipStatus = "PENDING" | "ACCEPTED" | "BLOCKED";
|
|
|
|
/**
|
|
* Represents a user within a friendship context.
|
|
*
|
|
* @interface FriendUser
|
|
* @property {string} id - The unique identifier of the user.
|
|
* @property {string} username - The display username of the user.
|
|
* @property {string} color - The associated profile color of the user.
|
|
* @property {UserStatus} status - The current online/activity status of the user.
|
|
*/
|
|
export interface FriendUser {
|
|
id: string;
|
|
username: string;
|
|
color: string;
|
|
status: UserStatus;
|
|
}
|
|
|
|
/**
|
|
* Represents a friendship entry between two users.
|
|
*
|
|
* @interface Friendship
|
|
* @property {string} id - The unique identifier of the friendship record.
|
|
* @property {string} senderId - The user ID of the friend request sender.
|
|
* @property {string} receiverId - The user ID of the friend request receiver.
|
|
* @property {FriendshipStatus} status - The current status of the friendship.
|
|
* @property {FriendUser} sender - The profile details of the sender user.
|
|
* @property {FriendUser} receiver - The profile details of the receiver user.
|
|
*/
|
|
export interface Friendship {
|
|
id: string;
|
|
senderId: string;
|
|
receiverId: string;
|
|
status: FriendshipStatus;
|
|
sender: FriendUser;
|
|
receiver: FriendUser;
|
|
}
|
|
|
|
/**
|
|
* Properties for the FriendsView component.
|
|
*
|
|
* @interface FriendsViewProps
|
|
* @property {string} currentUserId - The unique identifier of the currently logged-in user.
|
|
* @property {Friendship[]} initialFriendships - Initial list of friendships fetched from the server.
|
|
*/
|
|
export interface FriendsViewProps {
|
|
currentUserId: string;
|
|
initialFriendships: Friendship[];
|
|
}
|
|
|
|
/**
|
|
* Renders the friends view container, managing navigation tabs, state synchronization, and friendship mutation actions.
|
|
*
|
|
* @param {FriendsViewProps} props - The component props.
|
|
* @param {string} props.currentUserId - The unique identifier of the currently logged-in user.
|
|
* @param {Friendship[]} props.initialFriendships - Initial list of friendships.
|
|
* @returns {JSX.Element} The rendered friends view interface.
|
|
*/
|
|
export function FriendsView({
|
|
currentUserId,
|
|
initialFriendships,
|
|
}: FriendsViewProps) {
|
|
const router = useRouter();
|
|
const [activeTab, setActiveTab] = useState<TabType>("all");
|
|
const [friendships, setFriendships] =
|
|
useState<Friendship[]>(initialFriendships);
|
|
|
|
useEffect(() => {
|
|
setFriendships(initialFriendships);
|
|
}, [initialFriendships]);
|
|
|
|
/**
|
|
* Retrieves the counter-party user object from a given friendship record relative to the current user.
|
|
*
|
|
* @function getFriendUser
|
|
* @param {Friendship} f - The friendship object to evaluate.
|
|
* @returns {FriendUser} The other user involved in the friendship.
|
|
*/
|
|
const getFriendUser = (f: Friendship): FriendUser => {
|
|
return f.senderId === currentUserId ? f.receiver : f.sender;
|
|
};
|
|
|
|
const pendingRequests = friendships.filter((f) => f.status === "PENDING");
|
|
const acceptedFriends = friendships.filter((f) => f.status === "ACCEPTED");
|
|
|
|
/**
|
|
* Handles accepting a pending friend request via API.
|
|
*
|
|
* @async
|
|
* @function handleAccept
|
|
* @param {string} friendshipId - The unique identifier of the friendship request to accept.
|
|
* @returns {Promise<void>} Resolves when the request is processed.
|
|
*/
|
|
const handleAccept = async (friendshipId: string) => {
|
|
try {
|
|
const res = await fetch(`/api/friends/${friendshipId}`, {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ status: "ACCEPTED" }),
|
|
});
|
|
if (res.ok) {
|
|
setFriendships((prev) =>
|
|
prev.map((item) =>
|
|
item.id === friendshipId ? { ...item, status: "ACCEPTED" } : item,
|
|
),
|
|
);
|
|
router.refresh();
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to accept request:", err);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles deleting or declining a friendship or pending request via API.
|
|
*
|
|
* @async
|
|
* @function handleDeleteOrDecline
|
|
* @param {string} friendshipId - The unique identifier of the friendship to delete.
|
|
* @returns {Promise<void>} Resolves when the deletion is processed.
|
|
*/
|
|
const handleDeleteOrDecline = async (friendshipId: string) => {
|
|
try {
|
|
const res = await fetch(`/api/friends/${friendshipId}`, {
|
|
method: "DELETE",
|
|
});
|
|
if (res.ok) {
|
|
setFriendships((prev) =>
|
|
prev.filter((item) => item.id !== friendshipId),
|
|
);
|
|
router.refresh();
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to delete friendship/request:", err);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full w-full bg-background text-white">
|
|
{/* Header */}
|
|
<AppHeader
|
|
showFriendsTabs
|
|
activeTab={activeTab}
|
|
setActiveTab={setActiveTab}
|
|
allCount={acceptedFriends.length}
|
|
pendingCount={pendingRequests.length}
|
|
/>
|
|
|
|
{/* Main Tab Content */}
|
|
<div className="flex-1 overflow-y-auto pt-4">
|
|
{activeTab === "add" && (
|
|
<AddFriendTab
|
|
currentUserId={currentUserId}
|
|
initialFriendships={friendships}
|
|
/>
|
|
)}
|
|
|
|
{activeTab === "pending" && (
|
|
<PendingRequestsTab
|
|
currentUserId={currentUserId}
|
|
pendingRequests={pendingRequests}
|
|
getFriendUser={getFriendUser}
|
|
onAccept={handleAccept}
|
|
onDeclineOrCancel={handleDeleteOrDecline}
|
|
/>
|
|
)}
|
|
|
|
{activeTab === "all" && (
|
|
<AllFriendsTab
|
|
acceptedFriends={acceptedFriends}
|
|
getFriendUser={getFriendUser}
|
|
onRemove={handleDeleteOrDecline}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|