From 8eec94843464e56d49750f086e46c9bb46dae018 Mon Sep 17 00:00:00 2001
From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com>
Date: Wed, 21 Jan 2026 17:13:30 +0530
Subject: [PATCH] feat: enhance team management UI with avatar initials and
role permissions display
- Added a helper function to generate avatar initials for team members without an avatar.
- Improved the MembersTab component by displaying user avatars or initials.
- Introduced a new RolePermissionsDisplay component to present role permissions in a categorized manner.
- Updated table headers in MembersTab for better clarity and added icons for visual enhancement.
---
.../dashboard/[search_space_id]/team/page.tsx | 261 ++++++++++++++----
1 file changed, 210 insertions(+), 51 deletions(-)
diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx
index 132906d9f..a0bc6be03 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx
@@ -3,29 +3,38 @@
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";
import {
+ Bot,
Calendar,
Check,
Clock,
Copy,
Crown,
Edit2,
+ FileText,
Hash,
Link2,
LinkIcon,
Loader2,
+ Logs,
+ type LucideIcon,
+ MessageCircle,
+ MessageSquare,
+ Mic,
MoreHorizontal,
+ Plug,
Plus,
RefreshCw,
Search,
+ Settings,
Shield,
ShieldCheck,
Trash2,
- User,
UserMinus,
UserPlus,
Users,
} from "lucide-react";
import { motion } from "motion/react";
+import Image from "next/image";
import { useParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
@@ -512,6 +521,25 @@ export default function TeamManagementPage() {
// ============ Members Tab ============
+// Helper function to get avatar initials
+function getAvatarInitials(member: Membership): string {
+ // Try display name first
+ if (member.user_display_name) {
+ const parts = member.user_display_name.trim().split(/\s+/);
+ if (parts.length >= 2) {
+ return (parts[0][0] + parts[1][0]).toUpperCase();
+ }
+ return member.user_display_name.slice(0, 2).toUpperCase();
+ }
+ // Try email
+ if (member.user_email) {
+ const emailName = member.user_email.split("@")[0];
+ return emailName.slice(0, 2).toUpperCase();
+ }
+ // Fallback
+ return "U";
+}
+
function MembersTab({
members,
roles,
@@ -560,7 +588,7 @@ function MembersTab({
setSearchQuery(e.target.value)}
className="pl-9"
@@ -573,10 +601,30 @@ function MembersTab({
- Member
- Role
- Joined
- Actions
+
+
+
+ Member
+
+
+
+
+
+ Role
+
+
+
+
+
+ Joined
+
+
+
+
+
+ Actions
+
+
@@ -601,19 +649,36 @@ function MembersTab({
-
-
-
+ {member.user_avatar_url ? (
+
+ ) : (
+
+
+ {getAvatarInitials(member)}
+
+
+ )}
{member.is_owner && (
-
- {member.user_email || "Unknown"}
+ {member.user_display_name || member.user_email || "Unknown"}
+ {member.user_display_name && member.user_email && (
+
+ {member.user_email}
+
+ )}
{member.is_owner && (
No role
{roles.map((role) => (
-
-
- {role.name}
-
+ {role.name}
))}
) : (
-
-
- {member.role?.name || "No role"}
-
+
+ {member.role?.name || "No role"}
+
)}
-
-
+
{new Date(member.joined_at).toLocaleDateString()}
-
+
{canRemove && !member.is_owner && (
@@ -708,6 +768,130 @@ function MembersTab({
);
}
+// ============ Role Permissions Display ============
+
+const CATEGORY_CONFIG: Record = {
+ documents: { label: "Documents", icon: FileText, order: 1 },
+ chats: { label: "Chats", icon: MessageSquare, order: 2 },
+ comments: { label: "Comments", icon: MessageCircle, order: 3 },
+ llm_configs: { label: "LLM Configs", icon: Bot, order: 4 },
+ podcasts: { label: "Podcasts", icon: Mic, order: 5 },
+ connectors: { label: "Connectors", icon: Plug, order: 6 },
+ logs: { label: "Logs", icon: Logs, order: 7 },
+ members: { label: "Members", icon: Users, order: 8 },
+ roles: { label: "Roles", icon: Shield, order: 9 },
+ settings: { label: "Settings", icon: Settings, order: 10 },
+};
+
+const ACTION_LABELS: Record = {
+ create: "Create",
+ read: "Read",
+ update: "Update",
+ delete: "Delete",
+ invite: "Invite",
+ view: "View",
+ remove: "Remove",
+ manage_roles: "Manage Roles",
+};
+
+function RolePermissionsDisplay({ permissions }: { permissions: string[] }) {
+ if (permissions.includes("*")) {
+ return (
+
+
+
+
+
+
Full Access
+
All permissions granted
+
+
+ );
+ }
+
+ // Group permissions by category
+ const grouped: Record = {};
+ for (const perm of permissions) {
+ const [category, action] = perm.split(":");
+ if (!grouped[category]) grouped[category] = [];
+ grouped[category].push(action);
+ }
+
+ // Sort categories by predefined order
+ const sortedCategories = Object.keys(grouped).sort((a, b) => {
+ const orderA = CATEGORY_CONFIG[a]?.order ?? 99;
+ const orderB = CATEGORY_CONFIG[b]?.order ?? 99;
+ return orderA - orderB;
+ });
+
+ const categoryCount = sortedCategories.length;
+
+ return (
+
+ );
+}
+
// ============ Roles Tab ============
function RolesTab({
@@ -852,32 +1036,7 @@ function RolesTab({
)}
-
-
-
- {role.permissions.includes("*") ? (
-
- Full Access
-
- ) : (
- role.permissions.slice(0, 5).map((perm) => (
-
- {perm.replace(":", " ")}
-
- ))
- )}
- {!role.permissions.includes("*") && role.permissions.length > 5 && (
-
- +{role.permissions.length - 5} more
-
- )}
-
-
+