feat(servers): include members in activeServer payload and remove extra fetch
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 6m1s
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 6m1s
This commit is contained in:
parent
11722ac3e0
commit
c6af4e1565
6 changed files with 45 additions and 103 deletions
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { getServerWithChannels } from "@/lib/services/server.service";
|
||||
import { getUserServer } from "@/lib/services/server.service";
|
||||
import { ServerStateSync } from "@/lib/providers/ServerStateSync";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ export default async function ServerLayout({
|
|||
|
||||
if (!session?.user?.id) redirect("/login");
|
||||
|
||||
const server = await getServerWithChannels(serverId, session.user.id);
|
||||
const server = await getUserServer(serverId, session.user.id);
|
||||
|
||||
if (!server) redirect("/");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { getServerWithChannels } from "@/lib/services/server.service";
|
||||
import { getUserServer } from "@/lib/services/server.service";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function ServerPage({
|
||||
|
|
@ -17,7 +17,7 @@ export default async function ServerPage({
|
|||
|
||||
if (!session?.user?.id) redirect("/login");
|
||||
|
||||
const server = await getServerWithChannels(serverId, session.user.id);
|
||||
const server = await getUserServer(serverId, session.user.id);
|
||||
|
||||
if (!server) redirect("/");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* @file app/api/servers/[serverId]/members/route.ts
|
||||
* @description API route handler for retrieving all members belonging to a specific server.
|
||||
*/
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
import { getServerMembers } from "@/lib/services/member.service";
|
||||
|
||||
/**
|
||||
* Handles HTTP GET requests to fetch members of a server.
|
||||
*
|
||||
* @param {Request} req - The incoming HTTP request object.
|
||||
* @param {Object} context - Route context parameters.
|
||||
* @param {Promise<{ serverId: string }>} context.params - Asynchronous route parameters containing the serverId.
|
||||
* @returns {Promise<NextResponse>} JSON response containing the list of server members or an error message.
|
||||
*/
|
||||
export async function GET(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ serverId: string }> },
|
||||
) {
|
||||
try {
|
||||
const { serverId } = await params;
|
||||
const members = await getServerMembers(serverId);
|
||||
return NextResponse.json(members);
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch server members" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
/**
|
||||
* @file components/layout/MemberSidebar.tsx
|
||||
* @description Sidebar component fetching and displaying the server member list with responsive overlay support.
|
||||
* @description Sidebar component displaying the server member list directly from activeServer context.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useSidebarStore } from "@/lib/stores/useSidebarStore";
|
||||
import { useActiveServer } from "@/lib/context/ServerContext";
|
||||
import { MemberList } from "@/components/members/MemberList";
|
||||
import { X, Loader2 } from "lucide-react";
|
||||
import { X } from "lucide-react";
|
||||
import { clsx } from "clsx";
|
||||
import type { User } from "@/db/schema";
|
||||
|
||||
/** Props for the MemberHeader component. */
|
||||
interface MemberHeaderProps {
|
||||
|
|
@ -19,7 +18,7 @@ interface MemberHeaderProps {
|
|||
onClose: () => void;
|
||||
}
|
||||
|
||||
/** Header element for the member sidebar with title and close button. */
|
||||
/** Header component for the member sidebar with close button. */
|
||||
function MemberHeader({ title, onClose }: MemberHeaderProps) {
|
||||
return (
|
||||
<div className="h-14 border-b border-surface/50 flex items-center justify-between px-4 shrink-0">
|
||||
|
|
@ -38,52 +37,14 @@ function MemberHeader({ title, onClose }: MemberHeaderProps) {
|
|||
);
|
||||
}
|
||||
|
||||
/** Collapsible sidebar component displaying members of the currently active server. */
|
||||
/** Sidebar component displaying active server members with responsive drawer behavior. */
|
||||
export function MemberSidebar() {
|
||||
const { isMembersOpen, closeMembers } = useSidebarStore();
|
||||
const { activeServer } = useActiveServer();
|
||||
const desktopSidebarRef = useRef<HTMLElement>(null);
|
||||
const members = activeServer?.members ?? [];
|
||||
|
||||
const [members, setMembers] = useState<User[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
/** Fetches members for the active server and manages loading states. */
|
||||
useEffect(() => {
|
||||
if (!activeServer?.id) {
|
||||
setMembers([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
async function fetchMembers() {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const res = await fetch(`/api/servers/${activeServer?.id}/members`, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data: User[] = await res.json();
|
||||
setMembers(data);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
console.error("Error loading members:", err);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
fetchMembers();
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [activeServer?.id]);
|
||||
|
||||
/** Closes the members sidebar when clicking outside of it */
|
||||
/** Closes the members sidebar when clicking outside of it. */
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement;
|
||||
|
|
@ -107,18 +68,6 @@ export function MemberSidebar() {
|
|||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, [isMembersOpen, closeMembers]);
|
||||
|
||||
/** Renders loading indicator or the member list depending on state. */
|
||||
const renderContent = () => {
|
||||
if (isLoading && members.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8 text-muted">
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <MemberList members={members} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMembersOpen && (
|
||||
|
|
@ -142,7 +91,9 @@ export function MemberSidebar() {
|
|||
title="Mitgliederliste einklappen"
|
||||
onClose={closeMembers}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto">{renderContent()}</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<MemberList members={members} />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@
|
|||
"use client";
|
||||
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import type { Server, Channel, Category } from "@/db/schema";
|
||||
import type { Server, Channel, Category, User, Member } from "@/db/schema";
|
||||
|
||||
/** Extended server type containing associated channels and categories. */
|
||||
export type ServerWithChannels = Server & {
|
||||
channels: Channel[];
|
||||
categories: Category[];
|
||||
members: User[];
|
||||
};
|
||||
|
||||
/** Member representation within a server context. */
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { z } from "zod";
|
|||
|
||||
const uuidSchema = z.uuid();
|
||||
|
||||
/** Retrieves a server along with its channels and categories sorted chronologically if the specified user is a verified member. */
|
||||
export async function getServerWithChannels(serverId: string, userId: string) {
|
||||
/** Retrieves a server along with its channels, categories and members sorted chronologically if the specified user is a verified member. */
|
||||
export async function getUserServer(serverId: string, userId: string) {
|
||||
if (
|
||||
!uuidSchema.safeParse(serverId).success ||
|
||||
!uuidSchema.safeParse(userId).success
|
||||
|
|
@ -32,21 +32,30 @@ export async function getServerWithChannels(serverId: string, userId: string) {
|
|||
channels: {
|
||||
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
||||
},
|
||||
// NEU: Categories mitladen
|
||||
categories: {
|
||||
orderBy: (categories, { asc }) => [asc(categories.createdAt)],
|
||||
},
|
||||
members: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return server ?? null;
|
||||
if (!server) return null;
|
||||
|
||||
return {
|
||||
...server,
|
||||
members: server.members.map((m) => m.user).filter(Boolean),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error fetching server ${serverId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetches all servers that the specified user belongs to, including each server's sorted channels and categories list. */
|
||||
/** Fetches all servers that the specified user belongs to, including each server's sorted channels, categories, and members. */
|
||||
export async function getUserServers(userId: string) {
|
||||
if (!uuidSchema.safeParse(userId).success) {
|
||||
return [];
|
||||
|
|
@ -61,18 +70,30 @@ export async function getUserServers(userId: string) {
|
|||
channels: {
|
||||
orderBy: (channels, { asc }) => [asc(channels.createdAt)],
|
||||
},
|
||||
// NEU: Categories mitladen
|
||||
categories: {
|
||||
orderBy: (categories, { asc }) => [asc(categories.createdAt)],
|
||||
},
|
||||
members: {
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return userMemberships
|
||||
.map((membership) => membership.server)
|
||||
.filter(Boolean);
|
||||
.map((membership) => {
|
||||
if (!membership.server) return null;
|
||||
return {
|
||||
...membership.server,
|
||||
members: membership.server.members.map((m) => m.user).filter(Boolean),
|
||||
};
|
||||
})
|
||||
.filter((server): server is NonNullable<typeof server> =>
|
||||
Boolean(server),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error fetching servers for user ${userId}:`, error);
|
||||
return [];
|
||||
|
|
|
|||
Loading…
Reference in a new issue