From 009c1ef9de943f666fc30461a6fca500433d1045 Mon Sep 17 00:00:00 2001 From: Chneemann Date: Thu, 17 Sep 2026 17:22:07 +0200 Subject: [PATCH] feat(members): add role badges to member list items --- components/members/MemberItem.tsx | 37 ++++++++++++++++++++++--------- components/members/MemberList.tsx | 12 +++++----- lib/context/ServerContext.tsx | 11 ++++++--- lib/services/server.service.ts | 18 +++++++++++---- 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/components/members/MemberItem.tsx b/components/members/MemberItem.tsx index 3b826b2..9e9bc55 100644 --- a/components/members/MemberItem.tsx +++ b/components/members/MemberItem.tsx @@ -1,27 +1,30 @@ /** * @file components/members/MemberItem.tsx - * @description Member item component rendering user details with popover toggle for profile views and direct actions. + * @description Member item component rendering user details with role badge and popover toggle. */ "use client"; import { useState, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; -import type { User } from "@/db/schema"; import { clsx } from "clsx"; import { UserAvatar } from "../ui/UserAvatar"; import { UserProfilePopover } from "../ui/UserProfilePopover"; -import { useActiveServer } from "@/lib/context/ServerContext"; +import { + ServerMemberWithUser, + useActiveServer, +} from "@/lib/context/ServerContext"; import { useSidebarStore } from "@/lib/stores/useSidebarStore"; import { useUser } from "@/lib/context/UserContext"; +import { Crown, Shield } from "lucide-react"; /** Props for the MemberItem component. */ interface MemberItemProps { - member: User; + member: ServerMemberWithUser; isOffline?: boolean; } -/** Renders an individual member item with an interactive user profile popover. */ +/** Renders an individual member item with an interactive user profile popover and role badge. */ export function MemberItem({ member, isOffline = false }: MemberItemProps) { const router = useRouter(); const { setActiveServer } = useActiveServer(); @@ -121,16 +124,28 @@ export function MemberItem({ member, isOffline = false }: MemberItemProps) { type="button" onClick={() => setIsProfileOpen((prev) => !prev)} className={clsx( - "w-full flex items-center gap-2.5 px-2 py-1.5 rounded-md hover:bg-background/50 transition-colors cursor-pointer group text-left focus:outline-none", + "w-full flex items-center justify-between px-2 py-1.5 rounded-md hover:bg-background/50 transition-colors cursor-pointer group text-left focus:outline-none", isOffline && "opacity-60", )} > -
- +
+
+ +
+ + {member.username} + +
+ + {/* Rolle-Badge/Icon */} +
+ {member.role === "OWNER" && ( + + )} + {member.role === "ADMIN" && ( + + )}
- - {member.username} - {isProfileOpen && ( diff --git a/components/members/MemberList.tsx b/components/members/MemberList.tsx index dbc42ca..ce5ceb7 100644 --- a/components/members/MemberList.tsx +++ b/components/members/MemberList.tsx @@ -5,14 +5,14 @@ import { useMemo } from "react"; import { MemberItem } from "@/components/members/MemberItem"; -import { User } from "@/db/schema"; - -interface MemberListProps { - members: User[]; -} +import { ServerMemberWithUser } from "@/lib/context/ServerContext"; /** Renders online and offline community members in distinct sections. */ -export function MemberList({ members = [] }: MemberListProps) { +export function MemberList({ + members = [], +}: { + members?: ServerMemberWithUser[]; +}) { const { onlineMembers, offlineMembers } = useMemo(() => { return { onlineMembers: members.filter((m) => m.status !== "OFFLINE"), diff --git a/lib/context/ServerContext.tsx b/lib/context/ServerContext.tsx index ace1f3f..79c2338 100644 --- a/lib/context/ServerContext.tsx +++ b/lib/context/ServerContext.tsx @@ -8,14 +8,19 @@ import { createContext, useContext, useState } from "react"; import type { Server, Channel, Category, User, Member } from "@/db/schema"; -/** Extended server type containing associated channels and categories. */ +/** Member type combining User details with server Role. */ +export type ServerMemberWithUser = User & { + role: Member["role"]; +}; + +/** Server type including associated channels, categories, and members. */ export type ServerWithChannels = Server & { channels: Channel[]; categories: Category[]; - members: User[]; + members: ServerMemberWithUser[]; }; -/** Member representation within a server context. */ +/** Simplified member representation within server context. */ export interface ServerMember { id: string; name: string; diff --git a/lib/services/server.service.ts b/lib/services/server.service.ts index 9948134..475c29a 100644 --- a/lib/services/server.service.ts +++ b/lib/services/server.service.ts @@ -10,7 +10,7 @@ import { z } from "zod"; const uuidSchema = z.uuid(); -/** Retrieves a server along with its channels, categories and members sorted chronologically if the specified user is a verified member. */ +/** Retrieves a server along with channels, categories, and members (with role). */ export async function getUserServer(serverId: string, userId: string) { if ( !uuidSchema.safeParse(serverId).success || @@ -47,7 +47,12 @@ export async function getUserServer(serverId: string, userId: string) { return { ...server, - members: server.members.map((m) => m.user).filter(Boolean), + members: server.members + .filter((m) => Boolean(m.user)) + .map((m) => ({ + ...m.user, + role: m.role, + })), }; } catch (error) { console.error(`Error fetching server ${serverId}:`, error); @@ -55,7 +60,7 @@ export async function getUserServer(serverId: string, userId: string) { } } -/** Fetches all servers that the specified user belongs to, including each server's sorted channels, categories, and members. */ +/** Fetches all servers for a user with channels, categories, and members (with role). */ export async function getUserServers(userId: string) { if (!uuidSchema.safeParse(userId).success) { return []; @@ -88,7 +93,12 @@ export async function getUserServers(userId: string) { if (!membership.server) return null; return { ...membership.server, - members: membership.server.members.map((m) => m.user).filter(Boolean), + members: membership.server.members + .filter((m) => Boolean(m.user)) + .map((m) => ({ + ...m.user, + role: m.role, + })), }; }) .filter((server): server is NonNullable =>