fixing the bug with switching orgs

This commit is contained in:
Vladyslav Matsiiako
2023-07-11 22:13:54 -07:00
parent 98b2a2a5c1
commit f9110cedfa
10 changed files with 90 additions and 454 deletions

View File

@@ -66,7 +66,7 @@ export const DropdownMenuItem = <T extends ElementType = "button">({
className
)}
>
<Item type="button" role="menuitem" class="flex w-full items-center" ref={inputRef}>
<Item type="button" role="menuitem" className="flex w-full items-center" ref={inputRef}>
{icon && <span className="flex items-center mr-2">{icon}</span>}
<span className="flex-grow text-left">{children}</span>
</Item>

View File

@@ -3,7 +3,6 @@ import { createContext, ReactNode, useContext, useMemo } from "react";
import { useGetOrganization } from "@app/hooks/api";
import { Organization } from "@app/hooks/api/types";
import { useWorkspace } from "../WorkspaceContext";
type TOrgContext = {
orgs?: Organization[];
@@ -18,10 +17,10 @@ type Props = {
};
export const OrgProvider = ({ children }: Props): JSX.Element => {
const { currentWorkspace } = useWorkspace();
const { data: userOrgs, isLoading } = useGetOrganization();
const currentWsOrgID = currentWorkspace?.organization;
// const currentWsOrgID = currentWorkspace?.organization;
const currentWsOrgID = localStorage.getItem("orgData.id");
// memorize the workspace details for the context
const value = useMemo<TOrgContext>(

View File

@@ -144,6 +144,11 @@ export const AppLayout = ({ children }: LayoutProps) => {
}
};
const changeOrg = async (orgId) => {
localStorage.setItem("orgData.id", orgId);
router.push(`/org/${orgId}/overview`)
}
// TODO(akhilmhdh): This entire logic will be rechecked and will try to avoid
// Placing the localstorage as much as possible
// Wait till tony integrates the azure and its launched
@@ -158,7 +163,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
if (currentOrg && (
(workspaces?.length === 0 && router.asPath.includes("project"))
|| router.asPath.includes("/project/undefined")
|| (!orgs?.includes(router.query.id) && !router.asPath.includes("project") && !router.asPath.includes("integration"))
|| (!orgs?.map(org => org._id)?.includes(router.query.id) && !router.asPath.includes("project") && !router.asPath.includes("integration"))
)) {
router.push(`/org/${currentOrg?._id}/overview`);
}
@@ -254,11 +259,21 @@ export const AppLayout = ({ children }: LayoutProps) => {
<div className="pl-3 text-mineshaft-100 text-sm">{currentOrg?.name} <FontAwesomeIcon icon={faAngleDown} className="text-xs pl-1 pt-1 text-mineshaft-300" /></div>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<DropdownMenuContent align="start" className="">
<div className="text-xs text-mineshaft-400 px-2 py-1">{user?.email}</div>
{orgs?.map(org => <DropdownMenuItem key={org._id}>
<Link href={`/org/${org._id}/overview`}><div className="w-full flex justify-between items-center">{org.name}{currentOrg._id === org._id && <FontAwesomeIcon icon={faCheck} className="ml-auto text-primary"/>}</div></Link>
</DropdownMenuItem>)}
<Button
onClick={() => changeOrg(org?._id)}
variant="plain"
colorSchema="secondary"
size="xs"
className="w-full flex items-center justify-start"
leftIcon={currentOrg._id === org._id && <FontAwesomeIcon icon={faCheck} className="mr-3 text-primary"/>}
>
<div className="w-full flex justify-between items-center">{org.name}</div>
</Button>
</DropdownMenuItem>
)}
<div className="h-1 mt-1 border-t border-mineshaft-600"/>
<button
type="button"
@@ -361,34 +376,28 @@ export const AppLayout = ({ children }: LayoutProps) => {
<div className={`px-1 ${!router.asPath.includes("personal") ? "block" : "hidden"}`}>
{((router.asPath.includes("project") || router.asPath.includes("integrations")) && currentWorkspace) ? <Menu>
<Link href={`/project/${currentWorkspace?._id}/secrets`} passHref>
<a>
<MenuItem
isSelected={router.asPath.includes(`/project/${currentWorkspace?._id}/secrets`)}
icon="system-outline-90-lock-closed"
>
{t("nav.menu.secrets")}
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath.includes(`/project/${currentWorkspace?._id}/secrets`)}
icon="system-outline-90-lock-closed"
>
{t("nav.menu.secrets")}
</MenuItem>
</Link>
<Link href={`/project/${currentWorkspace?._id}/members`} passHref>
<a>
<MenuItem
isSelected={router.asPath === `/project/${currentWorkspace?._id}/members`}
icon="system-outline-96-groups"
>
{t("nav.menu.members")}
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath === `/project/${currentWorkspace?._id}/members`}
icon="system-outline-96-groups"
>
{t("nav.menu.members")}
</MenuItem>
</Link>
<Link href={`/integrations/${currentWorkspace?._id}`} passHref>
<a>
<MenuItem
isSelected={router.asPath === `/integrations/${currentWorkspace?._id}`}
icon="system-outline-82-extension"
>
{t("nav.menu.integrations")}
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath === `/integrations/${currentWorkspace?._id}`}
icon="system-outline-82-extension"
>
{t("nav.menu.integrations")}
</MenuItem>
</Link>
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
<MenuItem
@@ -400,28 +409,24 @@ export const AppLayout = ({ children }: LayoutProps) => {
</MenuItem>
</Link>
<Link href={`/project/${currentWorkspace?._id}/settings`} passHref>
<a>
<MenuItem
isSelected={
router.asPath === `/project/${currentWorkspace?._id}/settings`
}
icon="system-outline-109-slider-toggle-settings"
>
{t("nav.menu.project-settings")}
</MenuItem>
</a>
<MenuItem
isSelected={
router.asPath === `/project/${currentWorkspace?._id}/settings`
}
icon="system-outline-109-slider-toggle-settings"
>
{t("nav.menu.project-settings")}
</MenuItem>
</Link>
</Menu>
: <Menu className="mt-4">
<Link href={`/org/${currentOrg?._id}/overview`} passHref>
<a>
<MenuItem
isSelected={router.asPath.includes("/overview")}
icon="system-outline-165-view-carousel"
>
Overview
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath.includes("/overview")}
icon="system-outline-165-view-carousel"
>
Overview
</MenuItem>
</Link>
{/* {workspaces.map(project => <Link key={project._id} href={`/project/${project?._id}/secrets`} passHref>
<a>
@@ -437,36 +442,30 @@ export const AppLayout = ({ children }: LayoutProps) => {
</div>
</Link>)} */}
<Link href={`/org/${currentOrg?._id}/members`} passHref>
<a>
<MenuItem
isSelected={router.asPath === `/org/${currentOrg?._id}/members`}
icon="system-outline-96-groups"
>
Organization Members
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath === `/org/${currentOrg?._id}/members`}
icon="system-outline-96-groups"
>
Members
</MenuItem>
</Link>
<Link href={`/org/${currentOrg?._id}/billing`} passHref>
<a>
<MenuItem
isSelected={router.asPath === `/org/${currentOrg?._id}/billing`}
icon="system-outline-103-coin-cash-monetization"
>
Usage & Billing
</MenuItem>
</a>
<MenuItem
isSelected={router.asPath === `/org/${currentOrg?._id}/billing`}
icon="system-outline-103-coin-cash-monetization"
>
Usage & Billing
</MenuItem>
</Link>
<Link href={`/org/${currentOrg?._id}/settings`} passHref>
<a>
<MenuItem
isSelected={
router.asPath === `/org/${currentOrg?._id}/settings`
}
icon="system-outline-109-slider-toggle-settings"
>
Organization Settings
</MenuItem>
</a>
<MenuItem
isSelected={
router.asPath === `/org/${currentOrg?._id}/settings`
}
icon="system-outline-109-slider-toggle-settings"
>
Organization Settings
</MenuItem>
</Link>
</Menu>}
</div>

View File

@@ -77,8 +77,8 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<AuthProvider>
<WorkspaceProvider>
<OrgProvider>
<OrgProvider>
<WorkspaceProvider>
<SubscriptionProvider>
<UserProvider>
<NotificationProvider>
@@ -90,8 +90,8 @@ const App = ({ Component, pageProps, ...appProps }: NextAppProp): JSX.Element =>
</NotificationProvider>
</UserProvider>
</SubscriptionProvider>
</OrgProvider>
</WorkspaceProvider>
</WorkspaceProvider>
</OrgProvider>
</AuthProvider>
</TooltipProvider>
</QueryClientProvider>

View File

@@ -147,9 +147,8 @@ export default function Organization() {
const router = useRouter();
const { workspaces,
// isLoading: isWorkspaceLoading
} = useWorkspace();
const { workspaces } = useWorkspace();
const orgWorkspaces = workspaces?.filter(workspace => workspace.organization === localStorage.getItem("orgData.id")) || []
const currentOrg = String(router.query.id);
const { createNotification } = useNotificationContext();
const addWsUser = useAddUserToWs();
@@ -243,7 +242,7 @@ export default function Organization() {
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4">
<p className="mr-4 font-semibold text-white">Projects</p>
<p className="mr-4 font-semibold text-white">Projects {orgWorkspaces.map(ws => ws?.name).join("")}</p>
<div className="w-full flex flex-row mt-6">
<Input
className="h-[2.3rem] text-sm bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
@@ -268,7 +267,7 @@ export default function Organization() {
</Button>
</div>
<div className="mt-4 w-full grid gap-4 grid-cols-3 grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
{workspaces.filter(ws => ws.name.toLowerCase().includes(searchFilter.toLowerCase())).map(workspace => <div key={workspace._id} className="h-32 lg:h-40 min-w-72 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between">
{orgWorkspaces.filter(ws => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase())).map(workspace => <div key={workspace._id} className="h-32 lg:h-40 min-w-72 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between">
<div className="text-lg text-mineshaft-100 mt-0">{workspace.name}</div>
<div className="text-sm text-mineshaft-300 mt-0 lg:pb-6">{(workspace.environments?.length || 0)} environments</div>
<Link href={`/project/${workspace._id}/secrets`}>
@@ -276,7 +275,7 @@ export default function Organization() {
</Link>
</div>)}
</div>
{workspaces.length === 0 && (
{orgWorkspaces.length === 0 && (
<div className="w-full rounded-md bg-mineshaft-800 border border-mineshaft-700 px-4 py-6 text-mineshaft-300 text-base">
<FontAwesomeIcon icon={faFolderOpen} className="w-full text-center text-5xl mb-4 mt-2 text-mineshaft-400" />
<div className="text-center font-light">
@@ -300,16 +299,16 @@ export default function Organization() {
userAction: "intro_cta_clicked",
link: "https://www.youtube.com/watch?v=PK23097-25I"
})}
{workspaces.length !== 0 && learningItem({
{orgWorkspaces.length !== 0 && learningItem({
text: "Add your secrets",
subText: "Click to see example secrets, and add your own.",
complete: hasUserPushedSecrets,
icon: faPlus,
time: "1 min",
userAction: "first_time_secrets_pushed",
link: `/project/${workspaces[0]?._id}/secrets`
link: `/project/${orgWorkspaces[0]?._id}/secrets`
})}
{workspaces.length !== 0 && <div className="group text-mineshaft-100 relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 shadow-xl duration-200">
{orgWorkspaces.length !== 0 && <div className="group text-mineshaft-100 relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-800 pl-2 pr-2 pt-4 pb-2 shadow-xl duration-200">
<div className="mb-4 flex w-full flex-row items-center pr-4">
<div className="mr-4 flex w-full flex-row items-center">
<FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" />
@@ -332,7 +331,7 @@ export default function Organization() {
<TabsObject />
{false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
</div>}
{workspaces.length !== 0 && learningItem({
{orgWorkspaces.length !== 0 && learningItem({
text: "Integrate Infisical with your infrastructure",
subText: "Connect Infisical to various 3rd party services and platforms.",
complete: false,

View File

@@ -27,6 +27,7 @@ import {
THead,
Tr,
UpgradePlanModal} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { OrgUser, Workspace } from "@app/hooks/api/types";
@@ -70,6 +71,7 @@ export const OrgMembersTable = ({
const router = useRouter();
const [searchMemberFilter, setSearchMemberFilter] = useState("");
const {data: serverDetails } = useFetchServerStatus()
const { workspaces } = useWorkspace();
const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false);
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addMember",
@@ -230,7 +232,7 @@ export const OrgMembersTable = ({
: <Tag colorSchema="red">This user isn&apos;t part of any projects yet</Tag>}
{router.query.id !== "undefined" && !((status === "invited" || status === "verified") && serverDetails?.emailConfigured) && <button
type="button"
onClick={() => router.push(`/users/${router.query.id}`)}
onClick={() => router.push(`/project/${workspaces[0]?._id}/members`)}
className='text-sm bg-mineshaft w-max px-1.5 py-0.5 hover:bg-primary duration-200 hover:text-black cursor-pointer rounded-sm'
>
<FontAwesomeIcon icon={faPlus} className="mr-1" />

View File

@@ -1,57 +0,0 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
UpgradePlanModal
} from "@app/components/v2";
import { useSubscription } from "@app/context";
import { usePopUp } from "@app/hooks";
import { AddOrgMemberModal } from "./AddOrgMemberModal";
import { OrgMembersTable } from "./OrgMembersTable";
export const OrgMembersSection = () => {
const { subscription } = useSubscription();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addMember",
"upgradePlan",
] as const);
const isMoreMembersAllowed = subscription?.memberLimit ? (subscription.membersUsed < subscription.memberLimit) : true;
return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-4">
<p className="text-xl font-semibold text-mineshaft-100">
Organization members
</p>
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (isMoreMembersAllowed) {
handlePopUpOpen("addMember");
return;
}
handlePopUpOpen("upgradePlan");
}}
>
Add Member
</Button>
</div>
<OrgMembersTable />
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="Add more members by upgrading to a higher Infisical plan."
/>
<AddOrgMemberModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
handlePopUpClose={handlePopUpClose}
/>
</div>
);
}

View File

@@ -1,304 +0,0 @@
import { useMemo, useState } from "react";
import { useRouter } from "next/router";
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
decryptAssymmetric,
encryptAssymmetric
} from "@app/components/utilities/cryptography/crypto";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Input,
Select,
SelectItem,
Table,
TableContainer,
TableSkeleton,
Tag,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useOrganization, useUser, useWorkspace } from "@app/context";
import { usePopUp } from "@app/hooks";
import {
useAddUserToOrg,
useDeleteOrgMembership,
useGetOrgUsers,
useGetUserWorkspaceMemberships,
useGetUserWsKey,
useUpdateOrgUserRole,
useUploadWsKey
} from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
export const OrgMembersTable = () => {
const router = useRouter();
const { user: currentUser } = useUser();
const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
const { createNotification } = useNotificationContext();
const [searchMemberFilter, setSearchMemberFilter] = useState("");
const { data: serverDetails } = useFetchServerStatus()
const { data: members, isLoading: isOrgUserLoading } = useGetOrgUsers(currentOrg?._id ?? ""); // members
const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } = useGetUserWorkspaceMemberships(currentOrg?._id ?? "");
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id || "");
const uploadWsKey = useUploadWsKey();
const addUserToOrg = useAddUserToOrg();
const removeUserOrgMembership = useDeleteOrgMembership();
const updateOrgUserRole = useUpdateOrgUserRole();
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeMember",
"setUpEmail"
] as const);
const isLoading = isOrgUserLoading || IsWsMembershipLoading;
const userId = currentUser?._id || "";
const onRemoveMember = async (membershipId: string) => {
if (!currentOrg?._id) return;
try {
if (!currentOrg?._id) return;
await removeUserOrgMembership.mutateAsync({ orgId: currentOrg?._id, membershipId });
createNotification({
text: "Successfully removed user from org",
type: "success"
});
} catch (error) {
console.error(error);
createNotification({
text: "Failed to remove user from the organization",
type: "error"
});
}
};
const onRemoveOrgMemberApproved = async () => {
const orgMembershipId = (popUp?.removeMember?.data as { id: string })?.id;
await onRemoveMember(orgMembershipId);
handlePopUpClose("removeMember");
};
const onInviteMember = async (email: string) => {
if (!currentOrg?._id) return;
try {
const { data } = await addUserToOrg.mutateAsync({
organizationId: currentOrg?._id,
inviteeEmail: email
});
// only show this notification when email is configured. A [completeInviteLink] will not be sent if smtp is configured
if (!data.completeInviteLink) {
createNotification({
text: "Successfully invited user to the organization.",
type: "success"
});
}
} catch (error) {
console.error(error);
createNotification({
text: "Failed to invite user to org",
type: "error"
});
}
};
const onGrantAccess = async (targetUserId: string, publicKey: string) => {
try {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
if (!PRIVATE_KEY || !wsKey) return;
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: wsKey.encryptedKey,
nonce: wsKey.nonce,
publicKey: wsKey.sender.publicKey,
privateKey: PRIVATE_KEY
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
privateKey: PRIVATE_KEY
});
await uploadWsKey.mutateAsync({
userId: targetUserId,
nonce,
encryptedKey: ciphertext,
workspaceId: currentWorkspace?._id || ""
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to grant access to user",
type: "error"
});
}
};
const onRoleChange = async (membershipId: string, role: string) => {
if (!currentOrg?._id) return;
try {
await updateOrgUserRole.mutateAsync({ organizationId: currentOrg?._id, membershipId, role });
createNotification({
text: "Successfully updated user role",
type: "success"
});
} catch (error) {
console.error(error);
createNotification({
text: "Failed to update user role",
type: "error"
});
}
};
const isIamOwner = useMemo(
() => members ? members.find(({ user }) => userId === user?._id)?.role === "owner" : [],
[userId, members]
);
const filterdUser = useMemo(
() =>
members ? members.filter(
({ user, inviteEmail }) =>
user?.firstName?.toLowerCase().includes(searchMemberFilter) ||
user?.lastName?.toLowerCase().includes(searchMemberFilter) ||
user?.email?.toLowerCase().includes(searchMemberFilter) ||
inviteEmail?.includes(searchMemberFilter)
) : [],
[members, searchMemberFilter]
);
return (
<div className="w-full">
<Input
value={searchMemberFilter}
onChange={(e) => setSearchMemberFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search members..."
/>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>Name</Th>
<Th>Email</Th>
<Th>Role</Th>
<Th>Projects</Th>
<Th aria-label="actions" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={5} key="org-memberships" />}
{!isLoading &&
filterdUser.map(({ user, inviteEmail, role, _id: orgMembershipId, status }) => {
const name = user ? `${user.firstName} ${user.lastName}` : "-";
const email = user?.email || inviteEmail;
const userWs = workspaceMemberships?.[user?._id];
return (
<Tr key={`org-membership-${orgMembershipId}`} className="w-full">
<Td>{name}</Td>
<Td>{email}</Td>
<Td>
{status === "accepted" && (
<Select
defaultValue={role}
isDisabled={userId === user?._id}
className="w-40 bg-mineshaft-600"
dropdownContainerClassName="border border-mineshaft-600 bg-mineshaft-800"
onValueChange={(selectedRole) =>
onRoleChange(orgMembershipId, selectedRole)
}
>
{(isIamOwner || role === "owner") && (
<SelectItem value="owner">owner</SelectItem>
)}
<SelectItem value="admin">admin</SelectItem>
<SelectItem value="member">member</SelectItem>
</Select>
)}
{((status === "invited" || status === "verified") && serverDetails?.emailConfigured) && (
<Button className='w-40' colorSchema="primary" variant="outline_bg" onClick={() => onInviteMember(email)}>
Resend Invite
</Button>
)}
{status === "completed" && (
<Button
colorSchema="secondary"
onClick={() => onGrantAccess(user?._id, user?.publicKey)}
>
Grant Access
</Button>
)}
</Td>
<Td>
{userWs ? (
userWs?.map(({ name: wsName, _id }) => (
<Tag key={`user-${currentUser._id}-workspace-${_id}`} className="my-1">
{wsName}
</Tag>
))
) : (
<div className='flex flex-row'>
{((status === "invited" || status === "verified") && serverDetails?.emailConfigured)
? <Tag colorSchema="red">This user hasn&apos;t accepted the invite yet</Tag>
: <Tag colorSchema="red">This user isn&apos;t part of any projects yet</Tag>}
{router.query.id !== "undefined" && !((status === "invited" || status === "verified") && serverDetails?.emailConfigured) && <button
type="button"
onClick={() => router.push(`/users/${router.query.id}`)}
className='text-sm bg-mineshaft w-max px-1.5 py-0.5 hover:bg-primary duration-200 hover:text-black cursor-pointer rounded-sm'
>
<FontAwesomeIcon icon={faPlus} className="mr-1" />
Add to projects
</button>}
</div>
)}
</Td>
<Td>
{userId !== user?._id && (
<IconButton
ariaLabel="delete"
colorSchema="danger"
isDisabled={userId === user?._id}
onClick={() => handlePopUpOpen("removeMember", { id: orgMembershipId })}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
)}
</Td>
</Tr>
);
})}
</TBody>
</Table>
{!isLoading && filterdUser?.length === 0 && (
<EmptyState title="No project members found" icon={faUsers} />
)}
</TableContainer>
<DeleteActionModal
isOpen={popUp.removeMember.isOpen}
deleteKey="remove"
title="Do you want to remove this user from the org?"
onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)}
onDeleteApproved={onRemoveOrgMemberApproved}
/>
</div>
);
};

View File

@@ -1 +0,0 @@
export { OrgMembersSection } from "./OrgMembersSection";

View File

@@ -1,4 +1,3 @@
export { OrgIncidentContactsSection } from "./OrgIncidentContactsSection";
export { OrgMembersSection } from "./OrgMembersSection";
export { OrgNameChangeSection } from "./OrgNameChangeSection";
export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";