[WEB-1959]: Chore/settings member page (#5144)
* chore: implemented table component in ui library * chore: added export in the UI package * chore/member-page-revamp * fix: added custom popover className * fix: updated ui for projects * fix: hide pending invites for members * fix: added ee component * removed unwanted logging * fix: seperated components * fix: used collapsible instead of disclosure * fix: removed commented code --------- Co-authored-by: gurusainath <gurusainath007@gmail.com>
This commit is contained in:
parent
474d7ef3c0
commit
fff27c60e4
20 changed files with 758 additions and 491 deletions
|
|
@ -79,7 +79,7 @@ export const WorkspaceInvitationsListItem: FC<Props> = observer((props) => {
|
|||
}}
|
||||
onSubmit={handleRemoveInvitation}
|
||||
/>
|
||||
<div className="group flex items-center justify-between px-3 py-4 hover:bg-custom-background-90">
|
||||
<div className="group flex items-center justify-between px-3 py-4 hover:bg-custom-background-90 w-full">
|
||||
<div className="flex items-center gap-x-4 gap-y-2">
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded bg-gray-700 p-4 capitalize text-white">
|
||||
{(invitationDetails.email ?? "?")[0]}
|
||||
|
|
@ -137,17 +137,19 @@ export const WorkspaceInvitationsListItem: FC<Props> = observer((props) => {
|
|||
);
|
||||
})}
|
||||
</CustomSelect>
|
||||
<Tooltip tooltipContent="Remove member" disabled={!isAdmin} isMobile={isMobile}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRemoveMemberModal(true)}
|
||||
className={`pointer-events-none opacity-0 ${
|
||||
isAdmin ? "group-hover:pointer-events-auto group-hover:opacity-100" : ""
|
||||
}`}
|
||||
>
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500" strokeWidth={2} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{isAdmin && (
|
||||
<Tooltip tooltipContent="Remove member" disabled={!isAdmin} isMobile={isMobile}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRemoveMemberModal(true)}
|
||||
className={`pointer-events-none opacity-0 ${
|
||||
isAdmin ? "group-hover:pointer-events-auto group-hover:opacity-100" : ""
|
||||
}`}
|
||||
>
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500" strokeWidth={2} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
141
web/core/components/workspace/settings/member-columns.tsx
Normal file
141
web/core/components/workspace/settings/member-columns.tsx
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import Link from "next/link";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { IUser, IWorkspaceMember } from "@plane/types";
|
||||
import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
import { EUserWorkspaceRoles, ROLE } from "@/constants/workspace";
|
||||
import { useMember } from "@/hooks/store";
|
||||
|
||||
export interface RowData {
|
||||
member: IWorkspaceMember;
|
||||
role: EUserWorkspaceRoles;
|
||||
}
|
||||
|
||||
type NameProps = {
|
||||
rowData: RowData;
|
||||
workspaceSlug: string;
|
||||
isAdmin: boolean;
|
||||
currentUser: IUser | undefined;
|
||||
setRemoveMemberModal: (rowData: RowData) => void;
|
||||
};
|
||||
|
||||
type AccountTypeProps = {
|
||||
rowData: RowData;
|
||||
currentWorkspaceRole: EUserWorkspaceRoles | undefined;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const NameColumn: React.FC<NameProps> = (props: any) => {
|
||||
const { rowData, workspaceSlug, isAdmin, currentUser, setRemoveMemberModal } = props;
|
||||
return (
|
||||
<Disclosure>
|
||||
{({}) => (
|
||||
<div className="relative group">
|
||||
<div className="flex items-center gap-x-4 gap-y-2 w-72 justify-between">
|
||||
<div className="flex items-center gap-x-4 gap-y-2 flex-1">
|
||||
{rowData.member.avatar && rowData.member.avatar.trim() !== "" ? (
|
||||
<Link href={`/${workspaceSlug}/profile/${rowData.member.id}`}>
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full p-4 capitalize text-white">
|
||||
<img
|
||||
src={rowData.member.avatar}
|
||||
className="absolute left-0 top-0 h-full w-full rounded-full object-cover"
|
||||
alt={rowData.member.display_name || rowData.member.email}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${rowData.member.id}`}>
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full bg-gray-700 p-4 capitalize text-white">
|
||||
{(rowData.member.email ?? rowData.member.display_name ?? "?")[0]}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
{rowData.member.first_name} {rowData.member.last_name}
|
||||
</div>
|
||||
|
||||
{(isAdmin || rowData.member?.id === currentUser?.id) && (
|
||||
<PopoverMenu
|
||||
data={[""]}
|
||||
keyExtractor={(item) => item}
|
||||
popoverClassName="justify-end"
|
||||
buttonClassName="outline-none origin-center rotate-90 size-8 aspect-square flex-shrink-0 grid place-items-center opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
render={() => (
|
||||
<div
|
||||
className="flex items-center gap-x-3 cursor-pointer"
|
||||
onClick={() => setRemoveMemberModal(rowData)}
|
||||
>
|
||||
<Trash2 className="size-3.5 align-middle" />{" "}
|
||||
{rowData.member?.id === currentUser?.id ? "Leave " : "Remove "}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountTypeColumn: React.FC<AccountTypeProps> = (props) => {
|
||||
const { rowData, currentWorkspaceRole, workspaceSlug } = props;
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm();
|
||||
const {
|
||||
workspace: { updateMember },
|
||||
} = useMember();
|
||||
return rowData.role === EUserWorkspaceRoles.ADMIN || currentWorkspaceRole !== EUserWorkspaceRoles.ADMIN ? (
|
||||
<div className="w-32 flex ">
|
||||
<span>{ROLE[rowData.role as keyof typeof ROLE]}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Controller
|
||||
name="role"
|
||||
control={control}
|
||||
rules={{ required: "Role is required." }}
|
||||
render={({ field: { value } }) => (
|
||||
<CustomSelect
|
||||
value={value}
|
||||
onChange={(value: EUserProjectRoles) => {
|
||||
console.log({ value, workspaceSlug }, "onChange");
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
updateMember(workspaceSlug.toString(), rowData.member.id, {
|
||||
role: value as unknown as EUserWorkspaceRoles, // Cast value to unknown first, then to EUserWorkspaceRoles
|
||||
}).catch((err) => {
|
||||
console.log(err, "err");
|
||||
const error = err.error;
|
||||
const errorString = Array.isArray(error) ? error[0] : error;
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: errorString ?? "An error occurred while updating member role. Please try again.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
label={
|
||||
<div className="flex ">
|
||||
<span>{ROLE[rowData.role as keyof typeof ROLE]}</span>
|
||||
</div>
|
||||
}
|
||||
buttonClassName={`!px-0 !justify-start hover:bg-custom-background-100 ${errors.role ? "border-red-500" : "border-none"}`}
|
||||
className="rounded-md p-0 w-32"
|
||||
optionsClassName="w-full"
|
||||
input
|
||||
>
|
||||
{Object.keys(ROLE).map((item) => (
|
||||
<CustomSelect.Option key={item} value={item as unknown as EUserProjectRoles}>
|
||||
{ROLE[item as unknown as keyof typeof ROLE]}
|
||||
</CustomSelect.Option>
|
||||
))}
|
||||
</CustomSelect>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,48 +1,40 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
// lucide icons
|
||||
import { ChevronDown, Dot, XCircle } from "lucide-react";
|
||||
// ui
|
||||
import { CustomSelect, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
import { IWorkspaceMember } from "@plane/types";
|
||||
import { TOAST_TYPE, Table, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { ConfirmWorkspaceMemberRemove } from "@/components/workspace";
|
||||
// constants
|
||||
import { WORKSPACE_MEMBER_LEAVE } from "@/constants/event-tracker";
|
||||
import { EUserWorkspaceRoles, ROLE } from "@/constants/workspace";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useUser } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import useMemberColumns from "@/plane-web/components/workspace/settings/useMemberColumns";
|
||||
|
||||
type Props = {
|
||||
memberId: string;
|
||||
memberDetails: (IWorkspaceMember | null)[];
|
||||
};
|
||||
|
||||
export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
||||
const { memberId } = props;
|
||||
// states
|
||||
const [removeMemberModal, setRemoveMemberModal] = useState(false);
|
||||
const { memberDetails } = props;
|
||||
const { columns, workspaceSlug, removeMemberModal, setRemoveMemberModal } = useMemberColumns();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const {
|
||||
// currentUser,
|
||||
// currentUserSettings,
|
||||
membership: { currentWorkspaceRole, leaveWorkspace },
|
||||
membership: { leaveWorkspace },
|
||||
} = useUser();
|
||||
const { data: currentUser } = useUser();
|
||||
const {
|
||||
workspace: { updateMember, removeMemberFromWorkspace, getWorkspaceMemberDetails },
|
||||
workspace: { removeMemberFromWorkspace },
|
||||
} = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isMobile } = usePlatformOS();
|
||||
// derived values
|
||||
const memberDetails = getWorkspaceMemberDetails(memberId);
|
||||
|
||||
const handleLeaveWorkspace = async () => {
|
||||
if (!workspaceSlug || !currentUser) return;
|
||||
|
|
@ -64,10 +56,10 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const handleRemoveMember = async () => {
|
||||
if (!workspaceSlug || !memberDetails) return;
|
||||
const handleRemoveMember = async (memberId: string) => {
|
||||
if (!workspaceSlug || !memberId) return;
|
||||
|
||||
await removeMemberFromWorkspace(workspaceSlug.toString(), memberDetails.member.id).catch((err) =>
|
||||
await removeMemberFromWorkspace(workspaceSlug.toString(), memberId).catch((err) =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
|
|
@ -76,143 +68,41 @@ export const WorkspaceMembersListItem: FC<Props> = observer((props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (memberDetails?.member.id === currentUser?.id) await handleLeaveWorkspace();
|
||||
else await handleRemoveMember();
|
||||
const handleRemove = async (memberId: string) => {
|
||||
if (memberId === currentUser?.id) await handleLeaveWorkspace();
|
||||
else await handleRemoveMember(memberId);
|
||||
};
|
||||
|
||||
if (!memberDetails) return null;
|
||||
|
||||
// is the member current logged in user
|
||||
const isCurrentUser = memberDetails?.member.id === currentUser?.id;
|
||||
// const isCurrentUser = memberDetails?.member.id === currentUser?.id;
|
||||
// is the current logged in user admin
|
||||
const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
|
||||
// role change access-
|
||||
// 1. user cannot change their own role
|
||||
// 2. only admin or member can change role
|
||||
// 3. user cannot change role of higher role
|
||||
const hasRoleChangeAccess =
|
||||
currentWorkspaceRole &&
|
||||
!isCurrentUser &&
|
||||
[EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole) &&
|
||||
memberDetails.role <= currentWorkspaceRole;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmWorkspaceMemberRemove
|
||||
isOpen={removeMemberModal}
|
||||
onClose={() => setRemoveMemberModal(false)}
|
||||
userDetails={{
|
||||
id: memberDetails.member.id,
|
||||
display_name: `${memberDetails.member.display_name}`,
|
||||
}}
|
||||
onSubmit={handleRemove}
|
||||
{removeMemberModal && (
|
||||
<ConfirmWorkspaceMemberRemove
|
||||
isOpen={removeMemberModal.member.id.length > 0}
|
||||
onClose={() => setRemoveMemberModal(null)}
|
||||
userDetails={{
|
||||
id: removeMemberModal.member.id,
|
||||
display_name: removeMemberModal.member.display_name || "",
|
||||
}}
|
||||
onSubmit={() => handleRemove(removeMemberModal.member.id)}
|
||||
/>
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={memberDetails?.filter((member): member is IWorkspaceMember => member !== null) ?? []}
|
||||
keyExtractor={(rowData) => rowData?.member.id ?? ""}
|
||||
thClassName="text-left font-medium divide-x-0 border-b border-t divide-custom-border-200"
|
||||
tBodyClassName="divide-y-0"
|
||||
tBodyTrClassName="divide-x-0"
|
||||
tHeadTrClassName="divide-x-0"
|
||||
/>
|
||||
<div className="group w-full flex items-center justify-between px-3 py-4 hover:bg-custom-background-90">
|
||||
<div className="flex w-full items-center gap-x-4 gap-y-2">
|
||||
{memberDetails.member.avatar && memberDetails.member.avatar.trim() !== "" ? (
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.member.id}`}>
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded p-4 capitalize text-white">
|
||||
<img
|
||||
src={memberDetails.member.avatar}
|
||||
className="absolute left-0 top-0 h-full w-full rounded object-cover"
|
||||
alt={memberDetails.member.display_name || memberDetails.member.email}
|
||||
/>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.member.id}`}>
|
||||
<span className="relative flex h-10 w-10 items-center justify-center rounded bg-gray-700 p-4 capitalize text-white">
|
||||
{(memberDetails.member.email ?? memberDetails.member.display_name ?? "?")[0]}
|
||||
</span>
|
||||
</Link>
|
||||
)}
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<div className="truncate">
|
||||
<Link href={`/${workspaceSlug}/profile/${memberDetails.member.id}`} className="truncate">
|
||||
<div className="w-full truncate">
|
||||
<span className="text-sm font-medium truncate">
|
||||
{memberDetails.member.first_name} {memberDetails.member.last_name}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center truncate">
|
||||
<p className="text-xs text-custom-text-300">{memberDetails.member.display_name}</p>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Dot height={16} width={16} className="text-custom-text-300 hidden sm:block" />
|
||||
<p className="text-xs text-custom-text-300 line-clamp-1 truncate">{memberDetails.member.email}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2 text-xs">
|
||||
<CustomSelect
|
||||
customButton={
|
||||
<div className="item-center flex gap-1 rounded px-2 py-0.5">
|
||||
<span
|
||||
className={`flex items-center rounded text-xs font-medium ${
|
||||
hasRoleChangeAccess ? "" : "text-custom-sidebar-text-400"
|
||||
}`}
|
||||
>
|
||||
{ROLE[memberDetails.role]}
|
||||
</span>
|
||||
{hasRoleChangeAccess && (
|
||||
<span className="grid place-items-center">
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
value={memberDetails.role}
|
||||
onChange={(value: EUserWorkspaceRoles) => {
|
||||
if (!workspaceSlug || !value) return;
|
||||
|
||||
updateMember(workspaceSlug.toString(), memberDetails.member.id, {
|
||||
role: value,
|
||||
}).catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "An error occurred while updating member role. Please try again.",
|
||||
});
|
||||
});
|
||||
}}
|
||||
disabled={!hasRoleChangeAccess}
|
||||
placement="bottom-end"
|
||||
>
|
||||
{Object.keys(ROLE).map((key) => {
|
||||
if (currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < parseInt(key))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<CustomSelect.Option key={key} value={parseInt(key, 10)}>
|
||||
<>{ROLE[parseInt(key) as keyof typeof ROLE]}</>
|
||||
</CustomSelect.Option>
|
||||
);
|
||||
})}
|
||||
</CustomSelect>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={isCurrentUser ? "Leave workspace" : "Remove member"}
|
||||
disabled={!isAdmin && !isCurrentUser}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRemoveMemberModal(true)}
|
||||
className={
|
||||
isAdmin || isCurrentUser
|
||||
? "pointer-events-none md:opacity-0 group-hover:pointer-events-auto md:group-hover:opacity-100"
|
||||
: "pointer-events-none hidden md:opacity-0 md:block"
|
||||
}
|
||||
>
|
||||
<XCircle className="h-3.5 w-3.5 text-red-500" strokeWidth={2} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
import { FC } from "react";
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { Collapsible } from "@plane/ui";
|
||||
import { CountChip } from "@/components/common";
|
||||
import { MembersSettingsLoader } from "@/components/ui";
|
||||
import { WorkspaceInvitationsListItem, WorkspaceMembersListItem } from "@/components/workspace";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
|
||||
export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props) => {
|
||||
const { searchQuery } = props;
|
||||
export const WorkspaceMembersList: FC<{ searchQuery: string; isAdmin: boolean }> = observer((props) => {
|
||||
const { searchQuery, isAdmin } = props;
|
||||
const [showPendingInvites, setShowPendingInvites] = useState<boolean>(false);
|
||||
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
|
|
@ -21,6 +26,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props
|
|||
getSearchedWorkspaceMemberIds,
|
||||
workspaceMemberInvitationIds,
|
||||
getSearchedWorkspaceInvitationIds,
|
||||
getWorkspaceMemberDetails,
|
||||
},
|
||||
} = useMember();
|
||||
// fetching workspace invitations
|
||||
|
|
@ -39,20 +45,44 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props
|
|||
// derived values
|
||||
const searchedMemberIds = getSearchedWorkspaceMemberIds(searchQuery);
|
||||
const searchedInvitationsIds = getSearchedWorkspaceInvitationIds(searchQuery);
|
||||
const memberDetails = searchedMemberIds?.map((memberId) => getWorkspaceMemberDetails(memberId));
|
||||
|
||||
return (
|
||||
<div className="divide-y-[0.5px] divide-custom-border-100">
|
||||
{searchedInvitationsIds && searchedInvitationsIds.length > 0
|
||||
? searchedInvitationsIds?.map((invitationId) => (
|
||||
<WorkspaceInvitationsListItem key={invitationId} invitationId={invitationId} />
|
||||
))
|
||||
: null}
|
||||
{searchedMemberIds && searchedMemberIds.length > 0
|
||||
? searchedMemberIds?.map((memberId) => <WorkspaceMembersListItem key={memberId} memberId={memberId} />)
|
||||
: null}
|
||||
{searchedInvitationsIds?.length === 0 && searchedMemberIds?.length === 0 && (
|
||||
<h4 className="mt-16 text-center text-sm text-custom-text-400">No matching members</h4>
|
||||
<>
|
||||
<div className="divide-y-[0.5px] divide-custom-border-100 overflow-scroll ">
|
||||
<WorkspaceMembersListItem memberDetails={memberDetails ?? []} />
|
||||
{searchedInvitationsIds?.length === 0 && searchedMemberIds?.length === 0 && (
|
||||
<h4 className="mt-16 text-center text-sm text-custom-text-400">No matching members</h4>
|
||||
)}
|
||||
</div>
|
||||
{isAdmin && (
|
||||
<Collapsible
|
||||
isOpen={showPendingInvites}
|
||||
onToggle={() => setShowPendingInvites((prev) => !prev)}
|
||||
buttonClassName="w-full"
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between pt-4">
|
||||
<div className="flex">
|
||||
<h4 className="text-xl font-medium pt-2 pb-2">Pending invites</h4>
|
||||
{searchedInvitationsIds && (
|
||||
<CountChip count={searchedInvitationsIds.length} className="h-5 m-auto ml-2" />
|
||||
)}
|
||||
</div>{" "}
|
||||
<ChevronDown className={`h-5 w-5 transition-all ${showPendingInvites ? "rotate-180" : ""}`} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Disclosure.Panel>
|
||||
<div className="ml-auto items-center gap-1.5 rounded-md bg-custom-background-100 py-1.5">
|
||||
{searchedInvitationsIds && searchedInvitationsIds.length > 0
|
||||
? searchedInvitationsIds?.map((invitationId) => (
|
||||
<WorkspaceInvitationsListItem key={invitationId} invitationId={invitationId} />
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</Collapsible>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue