feat(members): update roleEnum to support OWNER, MODERATOR, MEMBER, and GUEST
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 2m39s

This commit is contained in:
Chneemann 2026-09-18 04:27:20 +02:00
parent 009c1ef9de
commit 8ab022eabe
No known key found for this signature in database
3 changed files with 32 additions and 30 deletions

View file

@ -5,7 +5,7 @@
"use client"; "use client";
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect, ReactNode } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { clsx } from "clsx"; import { clsx } from "clsx";
import { UserAvatar } from "../ui/UserAvatar"; import { UserAvatar } from "../ui/UserAvatar";
@ -16,16 +16,11 @@ import {
} from "@/lib/context/ServerContext"; } from "@/lib/context/ServerContext";
import { useSidebarStore } from "@/lib/stores/useSidebarStore"; import { useSidebarStore } from "@/lib/stores/useSidebarStore";
import { useUser } from "@/lib/context/UserContext"; import { useUser } from "@/lib/context/UserContext";
import { Crown, Shield } from "lucide-react"; import { Crown, ShieldCheck } from "lucide-react";
import { Member } from "@/db/schema";
/** Props for the MemberItem component. */
interface MemberItemProps {
member: ServerMemberWithUser;
isOffline?: boolean;
}
/** Renders an individual member item with an interactive user profile popover and role badge. */ /** Renders an individual member item with an interactive user profile popover and role badge. */
export function MemberItem({ member, isOffline = false }: MemberItemProps) { export function MemberItem({ member }: { member: ServerMemberWithUser }) {
const router = useRouter(); const router = useRouter();
const { setActiveServer } = useActiveServer(); const { setActiveServer } = useActiveServer();
const { closeMembers } = useSidebarStore(); const { closeMembers } = useSidebarStore();
@ -37,7 +32,13 @@ export function MemberItem({ member, isOffline = false }: MemberItemProps) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null); const triggerRef = useRef<HTMLButtonElement>(null);
// Close popover on click outside or ESC key press /** Map of server member roles to their corresponding icon elements. */
const ROLE_ICONS: Partial<Record<Member["role"], ReactNode>> = {
OWNER: <Crown className="w-4 h-4 text-amber-400" />,
MODERATOR: <ShieldCheck className="w-4 h-4 text-cyan-400" />,
};
/** Closes popover on click outside or ESC key press. */
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if ( if (
@ -125,26 +126,26 @@ export function MemberItem({ member, isOffline = false }: MemberItemProps) {
onClick={() => setIsProfileOpen((prev) => !prev)} onClick={() => setIsProfileOpen((prev) => !prev)}
className={clsx( className={clsx(
"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", "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.status == "OFFLINE" && "opacity-50",
)} )}
> >
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2.5 min-w-0">
<div className="relative shrink-0"> <div className="relative shrink-0">
<UserAvatar user={member} size="md" /> <UserAvatar user={member} size="md" />
</div> </div>
<span className="text-sm font-medium text-muted group-hover:text-white truncate"> <div className="flex-1 min-w-0">
{member.username} <p className="text-sm font-semibold text-foreground truncate leading-tight group-hover:text-accent transition-colors">
</span> {member.username}
</p>
<p className="text-xs text-muted truncate leading-tight font-medium capitalize">
{member.role.toLowerCase()}
</p>
</div>
</div> </div>
{/* Rolle-Badge/Icon */} {/* Role Badges & Icons */}
<div className="shrink-0 ml-1"> <div className="shrink-0 ml-1 flex items-center gap-1">
{member.role === "OWNER" && ( {ROLE_ICONS[member.role]}
<Crown className="w-4 h-4 text-amber-400" />
)}
{member.role === "ADMIN" && (
<Shield className="w-4 h-4 text-indigo-400" />
)}
</div> </div>
</button> </button>

View file

@ -8,11 +8,7 @@ import { MemberItem } from "@/components/members/MemberItem";
import { ServerMemberWithUser } from "@/lib/context/ServerContext"; import { ServerMemberWithUser } from "@/lib/context/ServerContext";
/** Renders online and offline community members in distinct sections. */ /** Renders online and offline community members in distinct sections. */
export function MemberList({ export function MemberList({ members }: { members: ServerMemberWithUser[] }) {
members = [],
}: {
members?: ServerMemberWithUser[];
}) {
const { onlineMembers, offlineMembers } = useMemo(() => { const { onlineMembers, offlineMembers } = useMemo(() => {
return { return {
onlineMembers: members.filter((m) => m.status !== "OFFLINE"), onlineMembers: members.filter((m) => m.status !== "OFFLINE"),
@ -46,7 +42,7 @@ export function MemberList({
</h2> </h2>
<div className="space-y-0.5"> <div className="space-y-0.5">
{offlineMembers.map((member) => ( {offlineMembers.map((member) => (
<MemberItem key={member.id} member={member} isOffline /> <MemberItem key={member.id} member={member} />
))} ))}
</div> </div>
</div> </div>

View file

@ -19,7 +19,12 @@ import {
// ========================================== // ==========================================
/** Enum representing member access roles within a server. */ /** Enum representing member access roles within a server. */
export const roleEnum = pgEnum("role", ["OWNER", "ADMIN", "MEMBER"]); export const roleEnum = pgEnum("role", [
"OWNER",
"MODERATOR",
"MEMBER",
"GUEST",
]);
/** Enum representing online status of a user. */ /** Enum representing online status of a user. */
export const userStatusEnum = pgEnum("user_status", [ export const userStatusEnum = pgEnum("user_status", [
@ -67,7 +72,7 @@ export const servers = pgTable("servers", {
/** Database table definition linking users to servers with specific roles. */ /** Database table definition linking users to servers with specific roles. */
export const members = pgTable("members", { export const members = pgTable("members", {
id: uuid("id").primaryKey().defaultRandom(), id: uuid("id").primaryKey().defaultRandom(),
role: roleEnum("role").default("MEMBER").notNull(), role: roleEnum("role").default("GUEST").notNull(),
userId: uuid("user_id") userId: uuid("user_id")
.references(() => users.id, { onDelete: "cascade" }) .references(() => users.id, { onDelete: "cascade" })
.notNull(), .notNull(),