From 3efa04094d3f4c9d7d6dccd21d6c3665b8bf221e Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Tue, 28 Apr 2026 19:35:23 +0330 Subject: [PATCH] refactor(projects): remove project member management ui --- src/api/logs.ts | 1 - src/api/projects.ts | 88 +-- src/components/logs/LogsFeed.tsx | 1 - src/components/logs/LogsFilterBar.tsx | 1 - src/lib/permissions.ts | 42 -- src/locales/en.ts | 1 - src/locales/fa.ts | 1 - src/pages/ProjectCreate.tsx | 801 ++++++-------------------- src/pages/ProjectEdit.tsx | 786 ++++++------------------- 9 files changed, 361 insertions(+), 1361 deletions(-) diff --git a/src/api/logs.ts b/src/api/logs.ts index 1329bff..725332b 100644 --- a/src/api/logs.ts +++ b/src/api/logs.ts @@ -5,7 +5,6 @@ export type WorkspaceLogSection = | "workspace_members" | "clients" | "projects" - | "project_members" | "tags" | "time_entries" | "rates" diff --git a/src/api/projects.ts b/src/api/projects.ts index be4d7e0..24de159 100644 --- a/src/api/projects.ts +++ b/src/api/projects.ts @@ -12,26 +12,6 @@ export interface ProjectClient { name: string; } -export interface ProjectMemberPayload { - user_id: string; - role: "manager" | "member" | string; -} - -export interface ProjectMembership { - id: string; - project: string; - user: string; - user_details: { - id: string; - first_name: string; - last_name: string; - phone_number: string; - avatar?: string; - }; - role: "manager" | "member" | string; - is_active: boolean; -} - export interface Project { id: string; name: string; @@ -42,8 +22,6 @@ export interface Project { workspace: string; created_by?: AuditUser | null; client: ProjectClient | null; - my_role?: string; - members?: ProjectMembership[]; } export interface ProjectPayload { @@ -84,13 +62,9 @@ export const getProject = async (id: string) => { return response.json(); }; -export const createProject = async ( - data: Partial & { - workspace: string; - name: string; - members?: ProjectMemberPayload[]; - } -) => { +export const createProject = async ( + data: Partial & { workspace: string; name: string } +) => { const response = await authFetch("/api/projects/", { method: "POST", body: JSON.stringify(data), @@ -103,10 +77,10 @@ export const createProject = async ( return response.json(); }; -export const updateProject = async ( - id: string, - data: Partial & { members?: ProjectMemberPayload[] } -) => { +export const updateProject = async ( + id: string, + data: Partial +) => { const response = await authFetch(`/api/projects/${id}/`, { method: "PATCH", body: JSON.stringify(data), @@ -143,50 +117,4 @@ export const toggleArchiveProject = async (id: string) => { throw new Error(errorData?.detail || errorData?.message || `Failed to archive project`); } return response.json(); -}; - - -export const getProjectMemberships = async (projectId: string) => { - const response = await authFetch(`/api/memberships/?project=${projectId}`); - if (!response.ok) throw new Error("Failed to fetch project memberships"); - return response.json(); -}; - -export const addProjectMembership = async (projectId: string, userId: string, role: string) => { - const response = await authFetch(`/api/memberships/`, { - method: "POST", - body: JSON.stringify({ project_id: projectId, user_id: userId, role }), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.detail || errorData?.message || "Failed to add project member"); - } - return response.json(); -}; - -export const updateProjectMembership = async (membershipId: string, role: string, isActive: boolean = true) => { - const response = await authFetch(`/api/memberships/${membershipId}/`, { - method: "PATCH", - body: JSON.stringify({ role, is_active: isActive }), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.detail || errorData?.message || "Failed to update project member"); - } - return response.json(); -}; - -export const removeProjectMembership = async (membershipId: string) => { - const response = await authFetch(`/api/memberships/${membershipId}/`, { - method: "DELETE", - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => null); - throw new Error(errorData?.detail || errorData?.message || "Failed to remove member"); - } - if (response.status === 204) return { success: true }; - return response.json().catch(() => ({ success: true })); -}; +}; diff --git a/src/components/logs/LogsFeed.tsx b/src/components/logs/LogsFeed.tsx index f4d072f..daa21fc 100644 --- a/src/components/logs/LogsFeed.tsx +++ b/src/components/logs/LogsFeed.tsx @@ -21,7 +21,6 @@ const sectionBadgeStyles: Record = { workspace_members: "bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-300", clients: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300", projects: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", - project_members: "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/30 dark:text-cyan-300", tags: "bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300", time_entries: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-300", rates: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300", diff --git a/src/components/logs/LogsFilterBar.tsx b/src/components/logs/LogsFilterBar.tsx index 34b9572..2e59f41 100644 --- a/src/components/logs/LogsFilterBar.tsx +++ b/src/components/logs/LogsFilterBar.tsx @@ -78,7 +78,6 @@ export function LogsFilterBar({ { value: "workspace_members", label: t.logs?.sections?.workspace_members || "Workspace members" }, { value: "clients", label: t.logs?.sections?.clients || "Clients" }, { value: "projects", label: t.logs?.sections?.projects || "Projects" }, - { value: "project_members", label: t.logs?.sections?.project_members || "Project members" }, { value: "tags", label: t.logs?.sections?.tags || "Tags" }, { value: "time_entries", label: t.logs?.sections?.time_entries || "Time entries" }, { value: "rates", label: t.logs?.sections?.rates || "Rates" }, diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index 73c4dfb..446f475 100644 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -1,5 +1,4 @@ export type WorkspaceRole = "owner" | "admin" | "member" | "guest"; -export type ProjectRole = "manager" | "member" | string; export const WORKSPACE_VIEW = "workspace.view"; export const WORKSPACE_EDIT = "workspace.edit"; @@ -22,10 +21,6 @@ export const PROJECTS_CREATE = "projects.create"; export const PROJECTS_EDIT = "projects.edit"; export const PROJECTS_DELETE = "projects.delete"; export const PROJECTS_ARCHIVE = "projects.archive"; -export const PROJECT_MEMBERS_VIEW = "project_members.view"; -export const PROJECT_MEMBERS_ADD = "project_members.add"; -export const PROJECT_MEMBERS_REMOVE = "project_members.remove"; -export const PROJECT_MEMBERS_CHANGE_ROLE = "project_members.change_role"; export const TIME_ENTRIES_VIEW_OWN = "time_entries.view_own"; export const TIME_ENTRIES_MANAGE_OWN = "time_entries.manage_own"; @@ -51,10 +46,6 @@ export type WorkspaceCapability = | typeof PROJECTS_EDIT | typeof PROJECTS_DELETE | typeof PROJECTS_ARCHIVE - | typeof PROJECT_MEMBERS_VIEW - | typeof PROJECT_MEMBERS_ADD - | typeof PROJECT_MEMBERS_REMOVE - | typeof PROJECT_MEMBERS_CHANGE_ROLE | typeof TIME_ENTRIES_VIEW_OWN | typeof TIME_ENTRIES_MANAGE_OWN; @@ -81,10 +72,6 @@ const CAPABILITIES_BY_ROLE: Record> = { PROJECTS_EDIT, PROJECTS_DELETE, PROJECTS_ARCHIVE, - PROJECT_MEMBERS_VIEW, - PROJECT_MEMBERS_ADD, - PROJECT_MEMBERS_REMOVE, - PROJECT_MEMBERS_CHANGE_ROLE, TIME_ENTRIES_VIEW_OWN, TIME_ENTRIES_MANAGE_OWN, ]), @@ -109,10 +96,6 @@ const CAPABILITIES_BY_ROLE: Record> = { PROJECTS_EDIT, PROJECTS_DELETE, PROJECTS_ARCHIVE, - PROJECT_MEMBERS_VIEW, - PROJECT_MEMBERS_ADD, - PROJECT_MEMBERS_REMOVE, - PROJECT_MEMBERS_CHANGE_ROLE, TIME_ENTRIES_VIEW_OWN, TIME_ENTRIES_MANAGE_OWN, ]), @@ -134,15 +117,6 @@ const CAPABILITIES_BY_ROLE: Record> = { ]), }; -const PROJECT_MANAGER_CAPABILITIES = new Set([ - PROJECTS_EDIT, - PROJECTS_ARCHIVE, - PROJECT_MEMBERS_VIEW, - PROJECT_MEMBERS_ADD, - PROJECT_MEMBERS_REMOVE, - PROJECT_MEMBERS_CHANGE_ROLE, -]); - export const canWorkspace = ( role: WorkspaceRole | null | undefined, capability: WorkspaceCapability, @@ -151,22 +125,6 @@ export const canWorkspace = ( return CAPABILITIES_BY_ROLE[role]?.has(capability) ?? false; }; -export const canProject = ({ - workspaceRole, - projectRole, - capability, -}: { - workspaceRole: WorkspaceRole | null | undefined; - projectRole?: ProjectRole | null; - capability: WorkspaceCapability; -}) => { - if (canWorkspace(workspaceRole, capability)) return true; - if (workspaceRole === "member" || workspaceRole === "guest" || !workspaceRole) { - return false; - } - return projectRole === "manager" && PROJECT_MANAGER_CAPABILITIES.has(capability); -}; - export const canDeleteWorkspaceResource = ({ workspaceRole, currentUserId, diff --git a/src/locales/en.ts b/src/locales/en.ts index e9b6946..e9c0fa3 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -543,7 +543,6 @@ export const en = { workspace_members: "Workspace members", clients: "Clients", projects: "Projects", - project_members: "Project members", tags: "Tags", time_entries: "Time entries", rates: "Rates", diff --git a/src/locales/fa.ts b/src/locales/fa.ts index 6ef3505..7b67b3d 100644 --- a/src/locales/fa.ts +++ b/src/locales/fa.ts @@ -538,7 +538,6 @@ export const fa = { workspace_members: "اعضای ورک‌اسپیس", clients: "مشتری‌ها", projects: "پروژه‌ها", - project_members: "اعضای پروژه", tags: "تگ‌ها", time_entries: "ورودی‌های زمان", rates: "نرخ‌ها", diff --git a/src/pages/ProjectCreate.tsx b/src/pages/ProjectCreate.tsx index f53fbea..1138211 100644 --- a/src/pages/ProjectCreate.tsx +++ b/src/pages/ProjectCreate.tsx @@ -1,97 +1,44 @@ -import { useState, useEffect, useCallback, useRef } from "react"; -import { useNavigate, useBlocker } from "react-router-dom"; -import { - Users, - Briefcase, - Trash2, - Search, - Loader2, -} from "lucide-react"; -import { toast } from "sonner"; - -import { createProject } from "../api/projects"; +import { useEffect, useState } from "react"; +import { useBlocker, useNavigate } from "react-router-dom"; +import { Briefcase, Loader2 } from "lucide-react"; +import { toast } from "sonner"; + import { getClients } from "../api/clients"; -import { fetchWorkspaceMemberships } from "../api/workspaces"; -import { searchUserByExactMobile, type SearchedUser } from "../api/users"; -import { useAppContext } from "../context/AppContext"; +import { createProject } from "../api/projects"; +import { Button } from "../components/ui/button"; +import { Input } from "../components/ui/input"; +import { Select } from "../components/ui/Select"; +import { TextAreaInput } from "../components/ui/TextAreaInput"; import { useWorkspace } from "../context/WorkspaceContext"; import { useTranslation } from "../hooks/useTranslation"; import { PROJECTS_CREATE, canWorkspace } from "../lib/permissions"; -import { Button } from "../components/ui/button"; -import { Input } from "../components/ui/input"; -import { Select } from "../components/ui/Select"; -import { TextAreaInput } from "../components/ui/TextAreaInput"; -import { InfiniteScroll } from "../components/InfiniteScroll"; -import { Modal } from "../components/Modal"; - -type ProjectRole = "manager" | "member"; - -interface LocalMember { - localId: string; - user: any; - role: ProjectRole; - isCreator?: boolean; -} - -const COLORS = [ - "#3B82F6", - "#10B981", - "#F59E0B", - "#EF4444", - "#8B5CF6", - "#EC4899", - "#14B8A6", - "#64748B", -]; - -const toEnglishDigits = (str: string) => { - if (!str) return ""; - return str - .replace(/[۰-۹]/g, (d) => "۰۱۲۳۴۵۶۷۸۹".indexOf(d).toString()) - .replace(/[٠-٩]/g, (d) => "٠١٢٣٤٥٦٧٨٩".indexOf(d).toString()); -}; - -const LIMIT = 10; - -export default function ProjectCreate() { + +const COLORS = [ + "#3B82F6", + "#10B981", + "#F59E0B", + "#EF4444", + "#8B5CF6", + "#EC4899", + "#14B8A6", + "#64748B", +]; + +export default function ProjectCreate() { const navigate = useNavigate(); const { t } = useTranslation(); - const { user } = useAppContext(); const { activeWorkspace } = useWorkspace(); - const currentUserId = user?.id || ""; const canCreateProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_CREATE); - - // Project Detail States - const [name, setName] = useState(""); - const [description, setDescription] = useState(""); - const [color, setColor] = useState(COLORS[0]); - const [client, setClient] = useState(""); - const [clientsList, setClientsList] = useState([]); - - // Workspace List & Pagination States - const [workspaceMembers, setWorkspaceMembers] = useState([]); - const [offset, setOffset] = useState(0); - const [hasMore, setHasMore] = useState(true); - const [isLoadingMore, setIsLoadingMore] = useState(false); - const [isLoadingData, setIsLoadingData] = useState(true); - - // Member Management States - const [members, setMembers] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [addAllMembers, setAddAllMembers] = useState(false); - const [isAddingAll, setIsAddingAll] = useState(false); - - // External Search States - const [searchResult, setSearchResult] = useState(null); - const [isSearching, setIsSearching] = useState(false); - const [searchError, setSearchError] = useState(false); - const searchTimeoutRef = useRef | null>(null); - - const [isSaving, setIsSaving] = useState(false); - const [memberIdToDelete, setMemberIdToDelete] = useState(null); - const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); - - const hasUnsavedChanges = name.trim() !== "" || description.trim() !== "" || members.length > 1; + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [color, setColor] = useState(COLORS[0]); + const [client, setClient] = useState(""); + const [clientsList, setClientsList] = useState<{ id: string; name: string }[]>([]); + const [isLoadingData, setIsLoadingData] = useState(true); + const [isSaving, setIsSaving] = useState(false); + + const hasUnsavedChanges = name.trim() !== "" || description.trim() !== "" || client !== "" || color !== COLORS[0]; useEffect(() => { if (activeWorkspace && !canCreateProject) { @@ -99,544 +46,144 @@ export default function ProjectCreate() { navigate("/projects"); } }, [activeWorkspace, canCreateProject, navigate]); - - useBlocker(({ currentLocation, nextLocation }) => { - if (hasUnsavedChanges && !isSaving && currentLocation.pathname !== nextLocation.pathname) { - return !window.confirm(t.confirmLeave || "You have unsaved changes. Are you sure you want to leave?"); - } - return false; - }); - - // EXACT same pagination structure as EditWorkspace.tsx - useEffect(() => { - if (activeWorkspace?.id) { - const workspaceId = activeWorkspace.id; - - setName(""); - setDescription(""); - setColor(COLORS[0]); - setClient(""); - setClientsList([]); - setWorkspaceMembers([]); - setSearchQuery(""); - setSearchResult(null); - setSearchError(false); - setAddAllMembers(false); - - // Reset pagination state - setOffset(0); - setHasMore(true); - setIsLoadingData(true); - - if (user?.id) { - setMembers([{ localId: user.id, user: user, role: "manager", isCreator: true }]); - } else { - setMembers([]); - } - - const loadInitialData = async () => { - try { - const clientsRes = await getClients(workspaceId); - setClientsList(clientsRes.results || []); - - const res = await fetchWorkspaceMemberships({ - workspace: workspaceId, - limit: LIMIT, - offset: 0, - }); - const results = res.results || (Array.isArray(res) ? res : []); - - setWorkspaceMembers(results); - setOffset(LIMIT); - setHasMore(res.next ? true : results.length >= LIMIT); - } catch (err) { - console.error("Failed to fetch initial data", err); - toast.error("Failed to load initial data."); - } finally { - setIsLoadingData(false); - } - }; - - loadInitialData(); - } - }, [activeWorkspace?.id, user?.id]); - - // EXACT same LoadMore logic and deduplication as EditWorkspace.tsx - const loadMoreMembers = useCallback(async () => { - if (isLoadingMore || !hasMore || !activeWorkspace?.id) return; - try { - setIsLoadingMore(true); - - const res = await fetchWorkspaceMemberships({ - workspace: activeWorkspace.id, - limit: LIMIT, - offset: offset - }); - const results = res.results || (Array.isArray(res) ? res : []); - - setWorkspaceMembers((prev) => { - // Safe deduplication to avoid React key warnings breaking the DOM observer - const existingIds = new Set(prev.map(m => m.id)); - const newItems = results.filter((item: any) => !existingIds.has(item.id)); - return [...prev, ...newItems]; - }); - - setOffset(prev => prev + LIMIT); - setHasMore(res.next ? true : results.length >= LIMIT); - - } catch (error) { - console.error("Failed to load more members", error); - } finally { - setIsLoadingMore(false); - } - }, [activeWorkspace?.id, isLoadingMore, hasMore, offset]); - - // Unified Search Logic - useEffect(() => { - if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current); - const cleanQuery = toEnglishDigits(searchQuery.trim()); - setSearchError(false); - - if (cleanQuery.length >= 10 && /^\d+$/.test(cleanQuery)) { - searchTimeoutRef.current = setTimeout(async () => { - setIsSearching(true); - try { - const foundUser = await searchUserByExactMobile(cleanQuery); - if (foundUser && foundUser.id) { - if (foundUser.id === currentUserId) { - setSearchResult(null); - } else { - setSearchResult(foundUser); - setSearchError(false); - } - } else { - setSearchResult(null); - setSearchError(true); - } - } catch (error) { - setSearchResult(null); - setSearchError(true); - } finally { - setIsSearching(false); - } - }, 500); - } else { - setSearchResult(null); - } - return () => { - if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current); - }; - }, [searchQuery, currentUserId]); - - const handleAddMember = (userToAdd: any) => { - if (members.some((m) => m.user.id === userToAdd.id)) return; - const newMember: LocalMember = { - localId: Math.random().toString(36).substr(2, 9), - user: userToAdd, - role: "member", - }; - setMembers((prev) => [newMember, ...prev]); - setSearchQuery(""); - setSearchResult(null); - }; - - const handleToggleAddAllMembers = async () => { - if (addAllMembers) { - setMembers((prev) => prev.filter(m => m.isCreator || m.role === "manager")); - setAddAllMembers(false); - } else { - if (!activeWorkspace?.id) return; - setIsAddingAll(true); - try { - let currentOffset = 0; - let continueFetching = true; - const allWsMembers: any[] = []; - - while (continueFetching) { - const res = await fetchWorkspaceMemberships({ - workspace: activeWorkspace.id, - limit: 50, - offset: currentOffset, - }); - const fetchedResults = res.results || (Array.isArray(res) ? res : []); - allWsMembers.push(...fetchedResults); - if (res.next) { - currentOffset += 50; - } else { - continueFetching = false; - } - } - - const newMembersToAdd = allWsMembers - .map((wm) => wm.user) - .filter((u) => u && u.id !== currentUserId && !members.some((m) => m.user.id === u.id)); - - const localMembers: LocalMember[] = newMembersToAdd.map((u) => ({ - localId: Math.random().toString(36).substr(2, 9), - user: u, - role: "member", - })); - - setMembers((prev) => [...prev, ...localMembers]); - setAddAllMembers(true); - } catch (error) { - toast.error("Could not add all workspace members."); - } finally { - setIsAddingAll(false); - } - } - }; - - const openDeleteModal = (userId: string) => { - setMemberIdToDelete(userId); - setIsDeleteDialogOpen(true); - }; - - const handleDeleteMember = () => { - if (!memberIdToDelete) return; - setMembers(members.filter((m) => m.user.id !== memberIdToDelete)); - setIsDeleteDialogOpen(false); - setMemberIdToDelete(null); - }; - - const handleChangeRole = (userId: string, newRole: string) => { - setMembers( - members.map((m) => (m.user.id === userId ? { ...m, role: newRole as ProjectRole } : m)) - ); - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!name.trim() || !activeWorkspace) return; - - try { - setIsSaving(true); - const membersPayload = members - .filter((m) => !m.isCreator) - .map((m) => ({ user_id: m.user.id, role: m.role })); - - const projectPayload: any = { - name, - description, - color, - workspace: activeWorkspace.id, - members: membersPayload, - }; - if (client) projectPayload.client = client; - - const newProject = await createProject(projectPayload); - - window.dispatchEvent(new CustomEvent("project_created", { detail: newProject })); - toast.success(t.projects?.createSuccess || "Project created successfully."); - navigate("/projects"); - } catch (error: any) { - toast.error(error.message || t.projects?.createError || "Failed to create project."); - } finally { - setIsSaving(false); - } - }; - - // Prepare unified display list - const filteredWorkspaceMembers = workspaceMembers.filter((m) => { - const q = searchQuery.trim().toLowerCase(); - if (!q) return true; - const fullName = `${m.user.first_name || ""} ${m.user.last_name || ""}`.toLowerCase(); - const phone = toEnglishDigits(m.user.mobile || ""); - return fullName.includes(q) || phone.includes(toEnglishDigits(q)); - }); - - const workspaceMemberUserIds = new Set(filteredWorkspaceMembers.map((m) => m.user.id)); - - const externalAddedMembers = members.filter((m) => { - if (workspaceMemberUserIds.has(m.user.id)) return false; - const q = searchQuery.trim().toLowerCase(); - if (!q) return true; - const fullName = `${m.user.first_name || ""} ${m.user.last_name || ""}`.toLowerCase(); - const phone = toEnglishDigits(m.user.mobile || ""); - return fullName.includes(q) || phone.includes(toEnglishDigits(q)); - }); - - const displayList = [ - ...externalAddedMembers.map((m) => ({ listId: m.localId, user: m.user })), - ...filteredWorkspaceMembers.map((m) => ({ listId: m.id || m.user.id, user: m.user })) - ]; - - if (!activeWorkspace) { - return null; - } - - return ( -
-

- {t.projects?.createNew || "Create New Project"} -

- -
-
-
-
-
-
-
- setName(e.target.value)} - placeholder={t.projects?.namePlaceholder || "Project name..."} - required - /> -
-
- {COLORS.map((c) => ( -
-
- -
- - setSearchQuery(e.target.value)} - placeholder={t.projects?.searchWorkspaceMembers || "Search by name or enter mobile number..."} - className="ps-10" - /> - {isSearching && ( - - )} -
- - {searchError && ( -

- {t.projects?.userNotFound || "No user found with this mobile number."} -

- )} - - {searchResult && !searchError && ( -
-
- {searchResult.profile_picture ? ( - {searchResult.first_name} - ) : ( -
- {searchResult.first_name?.[0] || "U"} -
- )} -
- - {searchResult.first_name} {searchResult.last_name} - - - {searchResult.mobile} - -
-
- -
- )} -
- -
- {isLoadingData ? ( -
- - {t.loading || "Loading..."} -
- ) : ( - - {displayList.length === 0 ? ( -
- {t.projects?.noWorkspaceMembers || "No members found."} -
- ) : ( -
    - {displayList.map((item) => { - const addedMemberData = members.find((mm) => mm.user.id === item.user.id); - const isAdded = !!addedMemberData; - - return ( -
  • -
    - {item.user.profile_picture ? ( - {item.user.first_name} - ) : ( -
    - {item.user.first_name?.[0] || "U"} -
    - )} -
    - - {item.user.first_name} {item.user.last_name} - {addedMemberData?.isCreator && ( - - {t.projects?.creator || "Creator"} - - )} - - - {item.user.mobile} - -
    -
    - -
    - {isAdded ? ( -
    - {!addedMemberData.isCreator && ( - setName(e.target.value)} + placeholder={t.projects?.namePlaceholder || "Project name..."} + required + /> +
    +
    + {COLORS.map((paletteColor) => ( +
    +
    + +
    + + setName(e.target.value)} - placeholder={t.projects?.namePlaceholder || "Project name..."} - required - /> -
    -
    - {COLORS.map((c) => ( -
    -
- -
- - setSearchQuery(e.target.value)} - placeholder={t.projects?.searchWorkspaceMembers || "Search by name or enter mobile number..."} - className="ps-10" - /> - {isSearching && ( - - )} -
- - {searchError && ( -

- {t.projects?.userNotFound || "No user found with this mobile number."} -

- )} - - {searchResult && !searchError && ( -
-
- {searchResult.profile_picture ? ( - {searchResult.first_name} - ) : ( -
- {searchResult.first_name?.[0] || "U"} -
- )} -
- - {searchResult.first_name} {searchResult.last_name} - - - {searchResult.mobile} - -
-
- -
- )} -
- -
- {isLoadingData ? ( -
- - {t.loading || "Loading..."} -
- ) : ( - - {displayList.length === 0 ? ( -
- {t.projects?.noWorkspaceMembers || "No members found."} -
- ) : ( -
    - {displayList.map((item) => { - const addedMemberData = members.find((mm) => mm.user.id === item.user.id); - const isAdded = !!addedMemberData; - - return ( -
  • -
    - {item.user.profile_picture ? ( - {item.user.first_name} - ) : ( -
    - {item.user.first_name?.[0] || "U"} -
    - )} -
    - - {item.user.first_name} {item.user.last_name} - {addedMemberData?.isCreator && ( - - {t.projects?.creator || "Creator"} - - )} - - - {item.user.mobile} - -
    -
    - -
    - {isAdded ? ( -
    - setName(e.target.value)} + placeholder={t.projects?.namePlaceholder || "Project name..."} + required + /> +
    +
    + {COLORS.map((paletteColor) => ( +
    +
    + +
    + +