feat(members): add role badges to member list items
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 7m6s
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 7m6s
This commit is contained in:
parent
4622dac9bf
commit
009c1ef9de
4 changed files with 54 additions and 24 deletions
|
|
@ -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",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2.5 min-w-0">
|
||||
<div className="relative shrink-0">
|
||||
<UserAvatar user={member} size="md" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-muted group-hover:text-white truncate">
|
||||
{member.username}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Rolle-Badge/Icon */}
|
||||
<div className="shrink-0 ml-1">
|
||||
{member.role === "OWNER" && (
|
||||
<Crown className="w-4 h-4 text-amber-400" />
|
||||
)}
|
||||
{member.role === "ADMIN" && (
|
||||
<Shield className="w-4 h-4 text-indigo-400" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{isProfileOpen && (
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<typeof server> =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue