-
-
-
- {member.username.slice(0, 2).toUpperCase()}
-
-
- {/* Status indicator */}
-
= 3}
+ >
+
+
-
-
-
- {isOwner && (
-
- )}
- {!isOwner && userRole !== "member" && (
-
- )}
-
- {member.nickname || member.username}
-
-
- {member.bio && (
-
- {member.bio}
-
- )}
-
+
+ {member.username?.slice(0, 2).toUpperCase() || "???"}
+
+
+ {/* Status indicator */}
+
-
- >
+
+
+
+ {isOwner && (
+
+ )}
+ {/* Display Shield for Admins and Mods, not for Members */}
+ {!isOwner && effectiveMemberRolePriority > 1 && (
+
+ )}
+
+ {member.nickname || member.username}
+
+
+ {member.bio && (
+
+ {member.bio}
+
+ )}
+
+
+
);
};
const MemberList: React.FC = () => {
- const { instanceId } = useParams();
+ const { instanceId } = useParams<{ instanceId: string }>();
const { data: members, isLoading } = useInstanceMembers(instanceId);
const { user: currentUser } = useAuthStore();
- const currentUserRole = React.useMemo(() => {
- if (!currentUser || !instanceId) return "member";
- if (currentUser.admin) return "admin";
+ const currentUserRoleInfo = React.useMemo(() => {
+ if (!currentUser || !instanceId) {
+ return { role: "member", priority: 1, name: "Member", color: null };
+ }
- const userRole = currentUser.roles.find(
- (role) => role.instanceId === instanceId,
- );
- return userRole?.role || "member";
+ // If the current user is a global admin, they are effectively an admin of any instance.
+ if (currentUser.admin) {
+ return { role: "admin", priority: 3, name: "Admin", color: "#ff6b6b" };
+ }
+
+ const role = getUserRoleForInstance(currentUser.roles, instanceId);
+ return { ...getRoleInfo(role), role: role };
}, [currentUser, instanceId]);
if (!instanceId) {
@@ -140,17 +166,26 @@ const MemberList: React.FC = () => {
if (!members || members.length === 0) {
return (
-
No members
+
No members found
);
}
- // Group members by role
+ // Group members by their role for the current instance.
const groupedMembers = members.reduce(
(acc, member) => {
- const userRole =
- member.roles.find((r) => r.instanceId === instanceId)?.role || "member";
- const roleInfo = getRoleInfo(userRole);
+ // Determine the effective role for this instance.
+ let effectiveRoleName = getUserRoleForInstance(
+ member.roles as Role[],
+ instanceId,
+ );
+
+ // Global admin is instance admin
+ if (member.admin && effectiveRoleName !== "admin") {
+ effectiveRoleName = "admin";
+ }
+
+ const roleInfo = getRoleInfo(effectiveRoleName);
if (!acc[roleInfo.name]) {
acc[roleInfo.name] = [];
@@ -161,11 +196,11 @@ const MemberList: React.FC = () => {
{} as Record
,
);
- // Sort role groups by priority (admin > mod > member)
- const sortedRoleGroups = Object.entries(groupedMembers).sort(
- ([roleNameA], [roleNameB]) => {
- const priorityA = getRoleInfo(roleNameA.toLowerCase())?.priority || 1;
- const priorityB = getRoleInfo(roleNameB.toLowerCase())?.priority || 1;
+ // Get all unique role names present and sort them by priority.
+ const sortedRoleNames = Object.keys(groupedMembers).sort(
+ (roleNameA, roleNameB) => {
+ const priorityA = getRoleInfo(roleNameA).priority;
+ const priorityB = getRoleInfo(roleNameB).priority;
return priorityB - priorityA;
},
);
@@ -174,40 +209,53 @@ const MemberList: React.FC = () => {
{/* Header */}
-
-
- {members.length} Members
+
+
+ {members.length}
{/* Member List */}
- {sortedRoleGroups.map(([roleName, roleMembers]) => (
-
- {/* Role Header */}
-
-
- {roleName} — {roleMembers.length}
-
-
+ {sortedRoleNames.map((roleName) => {
+ const roleMembers = groupedMembers[roleName];
+ // Sort members within each role group alphabetically by username.
+ const sortedMembers = roleMembers.sort((a, b) =>
+ (a.nickname || a.username).localeCompare(
+ b.nickname || b.username,
+ ),
+ );
- {/* Role Members */}
-
- {roleMembers
- .sort((a, b) => a.username.localeCompare(b.username))
- .map((member) => (
+ return (
+
+ {/* Role Header */}
+
+
+ {roleName} — {roleMembers.length}
+
+
+
+ {/* Role Members */}
+
+ {sortedMembers.map((member) => (
))}
+
-
- ))}
+ );
+ })}
diff --git a/concord-client/src/components/message/MessageComponent.tsx b/concord-client/src/components/message/MessageComponent.tsx
index b0b2e57..54ca920 100644
--- a/concord-client/src/components/message/MessageComponent.tsx
+++ b/concord-client/src/components/message/MessageComponent.tsx
@@ -16,7 +16,7 @@ import { useState } from "react";
interface MessageUser {
id: string;
username?: string;
- nickName?: string | null;
+ nickname?: string | null;
picture?: string | null;
}
@@ -64,8 +64,8 @@ export const MessageComponent: React.FC = ({
const { mode } = useTheme();
// Get username with fallback
- const username = user.username || user.userName || "Unknown User";
- const displayName = user.nickname || user.nickName || username;
+ const username = user.username || user.username || "Unknown User";
+ const displayName = user.nickname || user.nickname || username;
const isDeleted = message.deleted;
@@ -109,10 +109,7 @@ export const MessageComponent: React.FC = ({
- {replyToUser.nickname ||
- replyToUser.nickName ||
- replyToUser.username ||
- replyToUser.userName}
+ {replyToUser.nickname || replyToUser.username}
{replyTo.text.replace(/```[\s\S]*?```/g, "[code]")}
diff --git a/concord-client/src/components/message/MessageInput.tsx b/concord-client/src/components/message/MessageInput.tsx
index 02a2660..734b1be 100644
--- a/concord-client/src/components/message/MessageInput.tsx
+++ b/concord-client/src/components/message/MessageInput.tsx
@@ -1,12 +1,14 @@
+// src/components/message/MessageInput.tsx
+
import { Message } from "@/lib/api-client";
-import { useState, useRef, useEffect } from "react";
+import { useState, useRef, useEffect } from "react"; // Keep useRef
import { useSendMessage } from "@/hooks/useMessages";
import { Button } from "@/components/ui/button";
interface MessageUser {
id: string;
username?: string;
- nickName?: string | null;
+ nickname?: string | null;
picture?: string | null;
}
interface MessageInputProps {
@@ -15,7 +17,6 @@ interface MessageInputProps {
replyingTo?: Message | null;
onCancelReply?: () => void;
replyingToUser: MessageUser | null;
- messageInputRef: React.RefObject;
}
export const MessageInput: React.FC = ({
@@ -24,22 +25,26 @@ export const MessageInput: React.FC = ({
replyingTo,
onCancelReply,
replyingToUser,
- messageInputRef,
}) => {
const [content, setContent] = useState("");
- const textareaRef = messageInputRef;
+ const textareaRef = useRef(null);
const formRef = useRef(null);
// Use the API hook for sending messages
const sendMessageMutation = useSendMessage();
- // Auto-resize textarea
+ // Auto-resize textarea and focus when replying
useEffect(() => {
if (textareaRef.current) {
textareaRef.current.style.height = "auto";
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
+
+ // Focus the input when a reply is initiated
+ if (replyingTo) {
+ textareaRef.current.focus();
+ }
}
- }, [content]);
+ }, [content, replyingTo]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -73,7 +78,7 @@ export const MessageInput: React.FC = ({
- {replyingToUser.nickname || replyingToUser.userName}
+ {replyingToUser.nickname || replyingToUser.username}
)}