diff --git a/frontend/src/pages/org/[id]/overview/index.tsx b/frontend/src/pages/org/[id]/overview/index.tsx
index 3f9355863..8a8d832cf 100644
--- a/frontend/src/pages/org/[id]/overview/index.tsx
+++ b/frontend/src/pages/org/[id]/overview/index.tsx
@@ -1,3 +1,5 @@
+// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex
+
import crypto from "crypto";
import { useEffect, useState } from "react";
@@ -11,8 +13,11 @@ import { faSlack } from "@fortawesome/free-brands-svg-icons";
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import {
faArrowRight,
+ faArrowUpRightFromSquare,
+ faCheck,
faCheckCircle,
faExclamationCircle,
+ faClipboard,
faHandPeace,
faMagnifyingGlass,
faNetworkWired,
@@ -22,6 +27,7 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
+import * as Tabs from "@radix-ui/react-tabs";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
@@ -36,7 +42,6 @@ import {
Skeleton,
UpgradePlanModal
} from "@app/components/v2";
-import { TabsObject } from "@app/components/v2/Tabs";
import { useSubscription, useUser, useWorkspace } from "@app/context";
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useRegisterUserAction,useUploadWsKey } from "@app/hooks/api";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
@@ -63,6 +68,210 @@ type ItemProps = {
link?: string;
};
+function copyToClipboard(id: string, setState: (value: boolean) => void) {
+ // Get the text field
+ const copyText = document.getElementById(id) as HTMLInputElement;
+
+ // Select the text field
+ copyText.select();
+ copyText.setSelectionRange(0, 99999); // For mobile devices
+
+ // Copy the text inside the text field
+ navigator.clipboard.writeText(copyText.value);
+
+ setState(true);
+ setTimeout(() => setState(false), 2000);
+ // Alert the copied text
+ // alert("Copied the text: " + copyText.value);
+}
+
+const CodeItem = ({
+ isCopied,
+ setIsCopied,
+ textExplanation,
+ code,
+ id
+}: {
+ isCopied: boolean;
+ setIsCopied: (value: boolean) => void;
+ textExplanation: string;
+ code: string;
+ id: string;
+}) => {
+ return (
+ <>
+
{textExplanation}
+
+
+
+
+ >
+ );
+};
+
+const TabsObject = () => {
+ const [downloadCodeCopied, setDownloadCodeCopied] = useState(false);
+ const [downloadCode2Copied, setDownloadCode2Copied] = useState(false);
+ const [loginCodeCopied, setLoginCodeCopied] = useState(false);
+ const [initCodeCopied, setInitCodeCopied] = useState(false);
+ const [runCodeCopied, setRunCodeCopied] = useState(false);
+
+ return (
+
+
+
+ MacOS
+
+
+ Windows
+
+ {/*
+ Arch Linux
+ */}
+
+ Other Platforms
+
+
+
+
+
+
+
+
+ You can find example of start commands for different frameworks{" "}
+
+ here
+
+ .{" "}
+
+
+
+
+
+
+
+
+
+
+
+
+ You can find example of start commands for different frameworks{" "}
+
+ here
+
+ .{" "}
+
+
+
+ );
+};
+
const LearningItem = ({
text,
subText,
diff --git a/frontend/src/views/Org/MembersPage/MembersPage.tsx b/frontend/src/views/Org/MembersPage/MembersPage.tsx
index 0d6b8e7f2..065825b48 100644
--- a/frontend/src/views/Org/MembersPage/MembersPage.tsx
+++ b/frontend/src/views/Org/MembersPage/MembersPage.tsx
@@ -1,216 +1,28 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { useState } from "react";
import { useTranslation } from "react-i18next";
-import { useRouter } from "next/router";
+import { motion } from "framer-motion";
-import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
-import {
- decryptAssymmetric,
- encryptAssymmetric
-} from "@app/components/utilities/cryptography/crypto";
-import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
-import {
- useAddIncidentContact,
- useAddUserToOrg,
- useDeleteIncidentContact,
- useDeleteOrgMembership,
- useGetOrgIncidentContact,
- useGetOrgUsers,
- useGetUserWorkspaceMemberships,
- useGetUserWsKey,
- useRenameOrg,
- useUpdateOrgUserRole,
- useUploadWsKey
-} from "@app/hooks/api";
+import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
+import { useOrganization } from "@app/context";
+import { useGetRoles } from "@app/hooks/api";
-import {
- OrgIncidentContactsTable,
- OrgMembersTable,
- OrgNameChangeSection,
- OrgServiceAccountsTable
-} from "./components";
+import { OrgMembersTable } from "./components/OrgMembersTable";
+import { OrgRoleTabSection } from "./components/OrgRoleTabSection";
+
+enum TabSections {
+ Member = "members",
+ Roles = "roles"
+}
export const MembersPage = () => {
- const host = window.location.origin;
- const router = useRouter();
- const { action } = router.query;
-
const { t } = useTranslation();
const { currentOrg } = useOrganization();
- const { currentWorkspace } = useWorkspace();
- const { user } = useUser();
- const { subscription } = useSubscription();
- const { createNotification } = useNotificationContext();
const orgId = currentOrg?._id || "";
- const { data: orgUsers, isLoading: isOrgUserLoading } = useGetOrgUsers(orgId);
- const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } =
- useGetUserWorkspaceMemberships(orgId);
- const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id || "");
- const { data: incidentContact, isLoading: IsIncidentContactLoading } =
- useGetOrgIncidentContact(orgId);
-
- const renameOrg = useRenameOrg();
- const removeUserOrgMembership = useDeleteOrgMembership();
- const addUserToOrg = useAddUserToOrg();
- const updateOrgUserRole = useUpdateOrgUserRole();
- const uploadWsKey = useUploadWsKey();
- const addIncidentContact = useAddIncidentContact();
- const removeIncidentContact = useDeleteIncidentContact();
-
- const [completeInviteLink, setcompleteInviteLink] = useState("");
-
- const isMoreUsersNotAllowed = subscription?.memberLimit ? (subscription.membersUsed >= subscription.memberLimit) : false;
-
- const onRenameOrg = async (name: string) => {
- if (!currentOrg?._id) return;
-
- try {
- await renameOrg.mutateAsync({ orgId: currentOrg?._id, newOrgName: name });
- createNotification({
- text: "Successfully renamed organization",
- type: "success"
- });
- } catch (error) {
- console.error(error);
- createNotification({
- text: "Failed to rename organization",
- type: "error"
- });
- }
- };
-
- const onRemoveUserOrgMembership = async (membershipId: string) => {
- if (!currentOrg?._id) return;
-
- try {
- 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 onAddUserToOrg = async (email: string) => {
- if (!currentOrg?._id) return;
-
- try {
- const { data } = await addUserToOrg.mutateAsync({
- organizationId: currentOrg?._id,
- inviteeEmail: email
- });
- setcompleteInviteLink(data?.completeInviteLink);
-
- // 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 onUpdateOrgUserRole = 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 onGrantUserAccess = async (userId: 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,
- nonce,
- encryptedKey: ciphertext,
- workspaceId: currentWorkspace?._id || ""
- });
- } catch (err) {
- console.error(err);
- createNotification({
- text: "Failed to grant access to user",
- type: "error"
- });
- }
- };
-
- const onAddIncidentContact = async (email: string) => {
- if (!currentOrg?._id) return;
-
- try {
- await addIncidentContact.mutateAsync({ orgId, email });
- createNotification({
- text: "Successfully added incident contact",
- type: "success"
- });
- } catch (error) {
- console.error(error);
- createNotification({
- text: "Failed to add incident contact",
- type: "error"
- });
- }
- };
-
- const onRemoveIncidentContact = async (email: string) => {
- if (!currentOrg?._id) return;
-
- try {
- await removeIncidentContact.mutateAsync({ orgId, email });
- createNotification({
- text: "Successfully removed incident contact",
- type: "success"
- });
- } catch (error) {
- console.error(error);
- createNotification({
- text: "Failed to remove incident contact",
- type: "error"
- });
- }
- };
+ const { data: roles } = useGetRoles({
+ orgId
+ });
return (
@@ -218,20 +30,28 @@ export const MembersPage = () => {
{t("section.members.org-members")}
-
+
+
+ Members
+ {process.env.NEXT_PUBLIC_NEW_PERMISSION_FLAG === "true" && (
+ Roles
+ )}
+
+
+
+
+
+
+
+
+
+
);
diff --git a/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx
deleted file mode 100644
index cfafae765..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { useState } from "react";
-import { Controller, useForm } from "react-hook-form";
-import {
- faContactBook,
- faMagnifyingGlass,
- faPlus,
- faTrash
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { yupResolver } from "@hookform/resolvers/yup";
-import * as yup from "yup";
-
-import {
- Button,
- DeleteActionModal,
- EmailServiceSetupModal,
- EmptyState,
- FormControl,
- IconButton,
- Input,
- Modal,
- ModalContent,
- Table,
- TableContainer,
- TableSkeleton,
- TBody,
- Td,
- Th,
- THead,
- Tr
-} from "@app/components/v2";
-import { usePopUp } from "@app/hooks";
-import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
-import { IncidentContact } from "@app/hooks/api/types";
-
-type Props = {
- isLoading?: boolean;
- contacts?: IncidentContact[];
- onRemoveContact: (email: string) => Promise;
- onAddContact: (email: string) => Promise;
-};
-
-const addContactFormSchema = yup.object({
- email: yup.string().email().required().label("Email").trim()
-});
-
-type TAddContactForm = yup.InferType;
-
-export const OrgIncidentContactsTable = ({
- contacts = [],
- onAddContact,
- onRemoveContact,
- isLoading
-}: Props) => {
- const [searchContact, setSearchContact] = useState("");
- const { data: serverDetails } = useFetchServerStatus();
- const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
- "addContact",
- "removeContact",
- "setUpEmail"
- ] as const);
-
- const {
- control,
- handleSubmit,
- reset,
- formState: { isSubmitting }
- } = useForm({ resolver: yupResolver(addContactFormSchema) });
-
- const onAddIncidentContact = ({ email }: TAddContactForm) => {
- onAddContact(email);
- handlePopUpClose("addContact");
- reset();
- };
-
- const onRemoveIncidentContact = async () => {
- const incidentContactEmail = (popUp?.removeContact?.data as { email: string })?.email;
- await onRemoveContact(incidentContactEmail);
- handlePopUpClose("removeContact");
- };
-
- const filteredContacts = contacts.filter(({ email }) =>
- email.toLocaleLowerCase().includes(searchContact)
- );
-
- return (
-
-
-
- setSearchContact(e.target.value)}
- leftIcon={}
- placeholder="Search incident contact by email..."
- />
-
-
- }
- onClick={() => {
- if (serverDetails?.emailConfigured) {
- handlePopUpOpen("addContact");
- } else {
- handlePopUpOpen("setUpEmail");
- }
- }}
- >
- Add Contact
-
-
-
-
-
-
-
-
- | Email |
- |
-
-
-
- {isLoading && }
- {filteredContacts?.map(({ email }) => (
-
- | {email} |
-
- handlePopUpOpen("removeContact", { email })}
- >
-
-
- |
-
- ))}
-
-
- {filteredContacts?.length === 0 && !isLoading && (
-
- )}
-
-
-
{
- handlePopUpToggle("addContact", isOpen);
- reset();
- }}
- >
-
-
-
-
-
handlePopUpToggle("removeContact", isOpen)}
- onDeleteApproved={onRemoveIncidentContact}
- />
- handlePopUpToggle("setUpEmail", isOpen)}
- />
-
- );
-};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/index.tsx
deleted file mode 100644
index b1df7cd68..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgIncidentContactsTable/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
diff --git a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx
index 2955f5b51..e13a2ca0a 100644
--- a/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx
+++ b/frontend/src/views/Org/MembersPage/components/OrgMembersTable/OrgMembersTable.tsx
@@ -1,4 +1,4 @@
-import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import {
@@ -14,6 +14,10 @@ import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
+import {
+ decryptAssymmetric,
+ encryptAssymmetric
+} from "@app/components/utilities/cryptography/crypto";
import {
Button,
DeleteActionModal,
@@ -37,26 +41,23 @@ import {
Tr,
UpgradePlanModal
} from "@app/components/v2";
-import { useOrganization, useWorkspace } from "@app/context";
+import { useOrganization, useSubscription, useUser, useWorkspace } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks";
-import { useGetSSOConfig } from "@app/hooks/api";
+import {
+ useAddUserToOrg,
+ useDeleteOrgMembership,
+ useGetOrgUsers,
+ useGetSSOConfig,
+ useGetUserWorkspaceMemberships,
+ useGetUserWsKey,
+ useUpdateOrgUserRole,
+ useUploadWsKey
+} from "@app/hooks/api";
+import { TRole } from "@app/hooks/api/roles/types";
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
-import { OrgUser, Workspace } from "@app/hooks/api/types";
type Props = {
- members?: OrgUser[];
- workspaceMemberships?: Record;
- orgName: string;
- isLoading?: boolean;
- isMoreUserNotAllowed: boolean;
- onRemoveMember: (userId: string) => Promise;
- onInviteMember: (email: string) => Promise;
- onRoleChange: (membershipId: string, role: string) => Promise;
- onGrantAccess: (userId: string, publicKey: string) => Promise;
- // the current user id to block remove org button
- userId: string;
- completeInviteLink: string | undefined;
- setCompleteInviteLink: Dispatch>;
+ roles?: TRole[];
};
const addMemberFormSchema = yup.object({
@@ -65,27 +66,21 @@ const addMemberFormSchema = yup.object({
type TAddMemberForm = yup.InferType;
-export const OrgMembersTable = ({
- members = [],
- workspaceMemberships = {},
- orgName,
- isMoreUserNotAllowed,
- onRemoveMember,
- onInviteMember,
- onGrantAccess,
- onRoleChange,
- userId,
- isLoading,
- completeInviteLink,
- setCompleteInviteLink
-}: Props) => {
+export const OrgMembersTable = ({ roles = [] }: Props) => {
const router = useRouter();
const { createNotification } = useNotificationContext();
+
const { currentOrg } = useOrganization();
- const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(currentOrg?._id ?? "");
+ const { workspaces, currentWorkspace } = useWorkspace();
+ const { user } = useUser();
+ const userId = user?._id || "";
+ const orgId = currentOrg?._id || "";
+ const workspaceId = currentWorkspace?._id || "";
+
+ const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(orgId);
const [searchMemberFilter, setSearchMemberFilter] = useState("");
const { data: serverDetails } = useFetchServerStatus();
- const { workspaces } = useWorkspace();
+
const [isInviteLinkCopied, setInviteLinkCopied] = useToggle(false);
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"addMember",
@@ -93,6 +88,23 @@ export const OrgMembersTable = ({
"upgradePlan",
"setUpEmail"
] as const);
+ const { subscription } = useSubscription();
+
+ const { data: members, isLoading: isMembersLoading } = useGetOrgUsers(orgId);
+ const { data: workspaceMemberships, isLoading: IsWsMembershipLoading } =
+ useGetUserWorkspaceMemberships(orgId);
+ const { data: wsKey } = useGetUserWsKey(workspaceId);
+
+ const removeUserOrgMembership = useDeleteOrgMembership();
+ const addUserToOrg = useAddUserToOrg();
+ const updateOrgUserRole = useUpdateOrgUserRole();
+ const uploadWsKey = useUploadWsKey();
+
+ const [completeInviteLink, setCompleteInviteLink] = useState("");
+
+ const isMoreUsersNotAllowed = subscription?.memberLimit
+ ? subscription.membersUsed >= subscription.memberLimit
+ : false;
useEffect(() => {
if (router.query.action === "invite") {
@@ -108,32 +120,100 @@ export const OrgMembersTable = ({
} = useForm({ resolver: yupResolver(addMemberFormSchema) });
const onAddMember = async ({ email }: TAddMemberForm) => {
- await onInviteMember(email);
+ if (!currentOrg?._id) return;
+
+ try {
+ const { data } = await addUserToOrg.mutateAsync({
+ organizationId: currentOrg?._id,
+ inviteeEmail: email
+ });
+ setCompleteInviteLink(data?.completeInviteLink);
+ // 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"
+ });
+ }
if (serverDetails?.emailConfigured) {
handlePopUpClose("addMember");
}
-
reset();
};
+ const onAddUserToOrg = async (email: string) => {
+ if (!currentOrg?._id) return;
+
+ try {
+ const { data } = await addUserToOrg.mutateAsync({
+ organizationId: currentOrg?._id,
+ inviteeEmail: email
+ });
+ setCompleteInviteLink(data?.completeInviteLink);
+
+ // 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 onRemoveOrgMemberApproved = async () => {
- const orgMembershipId = (popUp?.removeMember?.data as { id: string })?.id;
- await onRemoveMember(orgMembershipId);
+ const membershipId = (popUp?.removeMember?.data as { id: string })?.id;
+ if (!currentOrg?._id) return;
+
+ try {
+ 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"
+ });
+ }
handlePopUpClose("removeMember");
};
const isIamOwner = useMemo(
- () => members.find(({ user }) => userId === user?._id)?.role === "owner",
+ () => members?.find(({ user: u }) => userId === u?._id)?.role === "owner",
[userId, members]
);
+ const findRoleFromId = useCallback(
+ (roleId: string) => {
+ return roles.find(({ _id: id }) => id === roleId);
+ },
+ [roles]
+ );
+
const filterdUser = useMemo(
() =>
- members.filter(
- ({ user, inviteEmail }) =>
- user?.firstName?.toLowerCase().includes(searchMemberFilter) ||
- user?.lastName?.toLowerCase().includes(searchMemberFilter) ||
- user?.email?.toLowerCase().includes(searchMemberFilter) ||
+ members?.filter(
+ ({ user: u, inviteEmail }) =>
+ u?.firstName?.toLowerCase().includes(searchMemberFilter) ||
+ u?.lastName?.toLowerCase().includes(searchMemberFilter) ||
+ u?.email?.toLowerCase().includes(searchMemberFilter) ||
inviteEmail?.includes(searchMemberFilter)
),
[members, searchMemberFilter]
@@ -147,11 +227,65 @@ export const OrgMembersTable = ({
return () => clearTimeout(timer);
}, [isInviteLinkCopied]);
+ 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 onGrantAccess = async (grantedUserId: 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: grantedUserId,
+ nonce,
+ encryptedKey: ciphertext,
+ workspaceId: currentWorkspace?._id || ""
+ });
+ } catch (err) {
+ console.error(err);
+ createNotification({
+ text: "Failed to grant access to user",
+ type: "error"
+ });
+ }
+ };
+
const copyTokenToClipboard = () => {
navigator.clipboard.writeText(completeInviteLink as string);
setInviteLinkCopied.on();
};
+ const isLoading = isMembersLoading || IsWsMembershipLoading;
+
return (
@@ -175,7 +309,7 @@ export const OrgMembersTable = ({
return;
}
- if (isMoreUserNotAllowed) {
+ if (isMoreUsersNotAllowed) {
handlePopUpOpen("upgradePlan");
} else {
handlePopUpOpen("addMember");
@@ -200,106 +334,116 @@ export const OrgMembersTable = ({
{isLoading && }
{!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];
+ filterdUser?.map(
+ ({ user: u, inviteEmail, role, customRole, _id: orgMembershipId, status }) => {
+ const name = u ? `${u.firstName} ${u.lastName}` : "-";
+ const email = u?.email || inviteEmail;
+ const userWs = workspaceMemberships?.[u?._id];
- return (
-
- | {name} |
- {email} |
-
- {status === "accepted" && (
-
- )}
- {(status === "invited" || status === "verified") &&
- serverDetails?.emailConfigured && (
- |
+
+ {userId !== u?._id && (
+
+ handlePopUpOpen("removeMember", { id: orgMembershipId })
+ }
+ >
+
+
+ )}
+ |
+
+ );
+ }
+ )}
{!isLoading && filterdUser?.length === 0 && (
@@ -315,7 +459,7 @@ export const OrgMembersTable = ({
}}
>
{!completeInviteLink && (
diff --git a/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx
deleted file mode 100644
index 525b52471..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useEffect } from "react";
-import { Controller, useForm } from "react-hook-form";
-import { useTranslation } from "react-i18next";
-import { faCheck } from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { yupResolver } from "@hookform/resolvers/yup";
-import * as yup from "yup";
-
-import { Button, FormControl, Input } from "@app/components/v2";
-
-type Props = {
- orgName?: string;
- onOrgNameChange: (name: string) => Promise;
-};
-
-const formSchema = yup.object({
- name: yup.string().required().label("Project Name")
-});
-
-type FormData = yup.InferType;
-
-export const OrgNameChangeSection = ({ onOrgNameChange, orgName }: Props): JSX.Element => {
- const {
- handleSubmit,
- control,
- reset,
- formState: { isDirty, isSubmitting }
- } = useForm({ resolver: yupResolver(formSchema) });
- const { t } = useTranslation();
-
- useEffect(() => {
- reset({ name: orgName });
- }, [orgName]);
-
- const onFormSubmit = async ({ name }: FormData) => {
- await onOrgNameChange(name);
- };
-
- return (
-
- );
-};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/index.tsx
deleted file mode 100644
index 4d86fcddb..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgNameChangeSection/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { OrgNameChangeSection } from "./OrgNameChangeSection";
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx
new file mode 100644
index 000000000..d761706b4
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.tsx
@@ -0,0 +1,167 @@
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { faArrowLeft, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { yupResolver } from "@hookform/resolvers/yup";
+
+import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
+import { Button, FormControl, Input } from "@app/components/v2";
+import { useOrganization } from "@app/context";
+import { useCreateRole, useUpdateRole } from "@app/hooks/api";
+import { TRole } from "@app/hooks/api/roles/types";
+
+import {
+ formRolePermission2API,
+ formSchema,
+ rolePermission2Form,
+ TFormSchema
+} from "./OrgRoleModifySection.utils";
+import { OrgRoleWorkspacePermission } from "./OrgRoleWorkspacePermission";
+
+type Props = {
+ role?: TRole;
+ onGoBack: VoidFunction;
+};
+
+export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
+ const [searchPermission, setSearchPermission] = useState("");
+
+ const isNonEditable = ["owner", "admin", "member"].includes(role?.slug || "");
+ const isNewRole = !role?.slug;
+
+ const { createNotification } = useNotificationContext();
+ const { currentOrg } = useOrganization();
+ const orgId = currentOrg?._id || "";
+ const {
+ handleSubmit,
+ register,
+ formState: { isSubmitting, isDirty, errors },
+ setValue,
+ control
+ } = useForm({
+ defaultValues: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : {},
+ resolver: yupResolver(formSchema)
+ });
+
+ const { mutateAsync: createRole } = useCreateRole();
+ const { mutateAsync: updateRole } = useUpdateRole();
+
+ const handleRoleUpdate = async (el: TFormSchema) => {
+ if (!role?._id) return;
+
+ try {
+ await updateRole({
+ orgId,
+ id: role?._id,
+ ...el,
+ permissions: formRolePermission2API(el.permissions)
+ });
+ createNotification({ type: "success", text: "Successfully updated role" });
+ onGoBack();
+ } catch (err) {
+ console.log(err);
+ createNotification({ type: "error", text: "Failed to update role" });
+ }
+ };
+
+ const handleFormSubmit = async (el: TFormSchema) => {
+ if (!isNewRole) {
+ await handleRoleUpdate(el);
+ return;
+ }
+
+ try {
+ await createRole({
+ orgId,
+ ...el,
+ permissions: formRolePermission2API(el.permissions)
+ });
+ createNotification({ type: "success", text: "Created new role" });
+ onGoBack();
+ } catch (err) {
+ console.log(err);
+ createNotification({ type: "error", text: "Failed to create role" });
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils.ts b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils.ts
new file mode 100644
index 000000000..3a2936697
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleModifySection.utils.ts
@@ -0,0 +1,93 @@
+/* eslint-disable no-param-reassign */
+import * as yup from "yup";
+
+import { TPermission } from "@app/hooks/api/roles/types";
+
+const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
+
+export const formSchema = yup.object({
+ name: yup.string().required().label("Name"),
+ description: yup.string(),
+ slug: yup.string().required().label("Slug"),
+ permissions: yup.object({
+ workspace: yup.lazy((val) =>
+ yup.object(
+ Object.fromEntries(
+ Object.entries(val || {}).map(([k]) => [
+ k,
+ yup.object({
+ read: yup.bool(),
+ edit: yup.bool(),
+ delete: yup.bool(),
+ create: yup.bool()
+ })
+ ])
+ )
+ )
+ )
+ })
+});
+
+export type TFormSchema = yup.InferType;
+
+const api2FormWorkspace = (
+ formVal: TFormSchema["permissions"]["workspace"],
+ permission: TPermission
+) => {
+ if (permission.subject !== "workspace") return;
+ const isCustomRule = Boolean(permission?.condition?.id);
+ if (isCustomRule && !formVal?.custom) {
+ formVal.custom = { read: true, edit: true, delete: true, create: true };
+ }
+
+ const workspaceId = permission?.condition?.id || "all";
+ if (!formVal?.[workspaceId])
+ formVal[workspaceId] = { read: false, edit: false, create: false, delete: false };
+ formVal[workspaceId][permission.action] = true;
+};
+
+// convert role permission to form compatiable data structure
+export const rolePermission2Form = (permissions: TPermission[] = []) => {
+ const formVal: TFormSchema["permissions"] = {
+ workspace: {}
+ };
+
+ permissions.forEach((permission) => {
+ api2FormWorkspace(formVal?.workspace, permission);
+ });
+
+ return formVal;
+};
+
+const form2ApiWorkspace = (
+ permissions: TPermission[],
+ workspace: TFormSchema["permissions"]["workspace"]
+) => {
+ const isFullAccess = PERMISSION_ACTIONS.every((action) => workspace?.all?.[action]);
+
+ PERMISSION_ACTIONS.forEach((action) => {
+ if (workspace?.all?.[action]) permissions.push({ action, subject: "workspace" });
+ });
+
+ if (!isFullAccess) {
+ Object.keys(workspace)
+ .filter((id) => id !== "all" && id !== "custom") // remove all and custom for iter
+ .forEach((workspaceId) => {
+ const actions = Object.keys(workspace[workspaceId]) as ["read", "edit", "create", "delete"];
+ actions.forEach((action) => {
+ // if not full access for an action
+ if (!workspace?.all?.[action] && workspace[workspaceId][action]) {
+ permissions.push({ action, subject: "workspace", condition: { id: workspaceId } });
+ }
+ });
+ });
+ }
+};
+
+export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
+ const permissions: TPermission[] = [];
+ // easy deep copy
+ if (formVal?.workspace)
+ form2ApiWorkspace(permissions, JSON.parse(JSON.stringify(formVal.workspace)));
+ return permissions;
+};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleWorkspacePermission.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleWorkspacePermission.tsx
new file mode 100644
index 000000000..deb1bed20
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/OrgRoleWorkspacePermission.tsx
@@ -0,0 +1,205 @@
+import { useMemo } from "react";
+import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
+import { faClipboardList } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { motion } from "framer-motion";
+
+import {
+ Checkbox,
+ Select,
+ SelectItem,
+ Table,
+ TableContainer,
+ TBody,
+ Td,
+ Th,
+ THead,
+ Tr
+} from "@app/components/v2";
+import { useWorkspace } from "@app/context";
+
+import { TFormSchema } from "./OrgRoleModifySection.utils";
+
+type Props = {
+ isNonEditable?: boolean;
+ setValue: UseFormSetValue;
+ control: Control;
+};
+
+enum WorkspacePermission {
+ NoAccess = "no-access",
+ ReadOnly = "read-only",
+ FullAccess = "full-acess",
+ Custom = "custom"
+}
+
+export const OrgRoleWorkspacePermission = ({ isNonEditable, setValue, control }: Props) => {
+ const { workspaces } = useWorkspace();
+
+ const customWorkspaceRule = useWatch({
+ control,
+ name: "permissions.workspace.custom"
+ });
+ const isCustom = Boolean(customWorkspaceRule);
+ const allWorkspaceRule = useWatch({ control, name: "permissions.workspace.all" });
+
+ const selectedWsTopVal = useMemo(() => {
+ const { read, delete: del, edit, create } = allWorkspaceRule || {};
+ if (read && del && edit && create) return WorkspacePermission.FullAccess;
+ if (read) return WorkspacePermission.ReadOnly;
+ return WorkspacePermission.NoAccess;
+ }, [allWorkspaceRule]);
+
+ const handleTopLevelPermissionChange = (val: WorkspacePermission) => {
+ switch (val) {
+ case WorkspacePermission.NoAccess:
+ setValue("permissions.workspace", {}, { shouldDirty: true });
+ break;
+ case WorkspacePermission.FullAccess:
+ setValue(
+ "permissions.workspace",
+ { all: { read: true, edit: true, create: true, delete: true } },
+ { shouldDirty: true }
+ );
+ break;
+ case WorkspacePermission.ReadOnly:
+ setValue(
+ "permissions.workspace",
+ { all: { read: true, edit: false, create: false, delete: false } },
+ { shouldDirty: true }
+ );
+ break;
+ default:
+ setValue(
+ "permissions.workspace",
+ { custom: { read: false, edit: false, create: false, delete: false } },
+ { shouldDirty: true }
+ );
+ break;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
Projects
+
User project access control
+
+
+
+
+
+
+
+
+
+
+ |
+ Read |
+ Create |
+ Edit |
+ Delete |
+
+
+
+ {isCustom &&
+ workspaces?.map(({ name, _id: id }) => (
+
+ | {name} |
+
+ (
+
+
+
+ )}
+ />
+ |
+
+ (
+
+
+
+ )}
+ />
+ |
+
+ (
+
+
+
+ )}
+ />
+ |
+
+ (
+
+
+
+ )}
+ />
+ |
+
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx
new file mode 100644
index 000000000..86de1647a
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleModifySection/index.tsx
@@ -0,0 +1 @@
+export { OrgRoleModifySection } from "./OrgRoleModifySection";
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx
new file mode 100644
index 000000000..631266168
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx
@@ -0,0 +1,46 @@
+import { motion } from "framer-motion";
+
+import { usePopUp } from "@app/hooks";
+
+import { TRole } from "~/hooks/api/roles/types";
+
+import { OrgRoleModifySection } from "./OrgRoleModifySection";
+import { OrgRoleTable } from "./OrgRoleTable";
+
+type Props = {
+ roles?: TRole[];
+ isRolesLoading?: boolean;
+};
+
+export const OrgRoleTabSection = ({ roles = [], isRolesLoading }: Props) => {
+ const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["editRole"] as const);
+
+ return popUp.editRole.isOpen ? (
+
+ handlePopUpClose("editRole")}
+ />
+
+ ) : (
+
+ handlePopUpOpen("editRole", role)}
+ />
+
+ );
+};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx
new file mode 100644
index 000000000..2bc09cb89
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/OrgRoleTable.tsx
@@ -0,0 +1,140 @@
+import { useState } from "react";
+import { faEdit, faMagnifyingGlass, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { format } from "date-fns";
+
+import {
+ Button,
+ DeleteActionModal,
+ IconButton,
+ Input,
+ Table,
+ TableContainer,
+ TableSkeleton,
+ TBody,
+ Td,
+ Th,
+ THead,
+ Tooltip,
+ Tr
+} from "@app/components/v2";
+import { useOrganization } from "@app/context";
+import { useDeleteRole } from "@app/hooks/api";
+import { TRole } from "@app/hooks/api/roles/types";
+
+import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider";
+import { usePopUp } from "~/hooks/usePopUp";
+
+type Props = {
+ isRolesLoading?: boolean;
+ roles?: TRole[];
+ onSelectRole: (role?: TRole) => void;
+};
+
+export const OrgRoleTable = ({ isRolesLoading, roles = [], onSelectRole }: Props) => {
+ const [searchRoles, setSearchRoles] = useState("");
+ const { currentOrg } = useOrganization();
+ const orgId = currentOrg?._id || "";
+ const { createNotification } = useNotificationContext();
+ const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const);
+
+ const { mutateAsync: deleteRole } = useDeleteRole();
+
+ const handleRoleDelete = async () => {
+ const { _id: id } = popUp?.deleteRole?.data as TRole;
+ try {
+ await deleteRole({
+ orgId,
+ id
+ });
+ createNotification({ type: "success", text: "Successfully removed the role" });
+ handlePopUpClose("deleteRole");
+ } catch (err) {
+ console.log(err);
+ createNotification({ type: "error", text: "Failed to create role" });
+ }
+ };
+
+ return (
+
+
+
+ setSearchRoles(e.target.value)}
+ leftIcon={}
+ placeholder="Search roles..."
+ />
+
+
} onClick={() => onSelectRole()}>
+ Add Role
+
+
+
+
+
+
+
+ | Name |
+ Slug |
+ Created At |
+ |
+
+
+
+ {isRolesLoading && }
+ {roles?.map((role) => {
+ const { _id: id, name, createdAt, slug } = role;
+ const isNonMutatable = ["owner", "admin", "member"].includes(slug);
+
+ return (
+
+ | {name} |
+ {slug} |
+
+ {createdAt ? format(new Date(createdAt), "yyyy-MM-dd, hh:mm aaa") : "-"}
+ |
+
+
+
+ onSelectRole(role)}
+ variant="plain"
+ >
+
+
+
+
+ handlePopUpOpen("deleteRole", role)}
+ variant="plain"
+ isDisabled={isNonMutatable}
+ >
+
+
+
+
+ |
+
+ );
+ })}
+
+
+
+
+
handlePopUpClose("deleteRole")}
+ onDeleteApproved={handleRoleDelete}
+ />
+
+ );
+};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/index.tsx
new file mode 100644
index 000000000..a78c88bcb
--- /dev/null
+++ b/frontend/src/views/Org/MembersPage/components/OrgRoleTabSection/index.tsx
@@ -0,0 +1 @@
+export { OrgRoleTabSection } from "./OrgRoleTabSection";
diff --git a/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx b/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx
deleted file mode 100644
index bd9f601c2..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/OrgServiceAccountsTable.tsx
+++ /dev/null
@@ -1,367 +0,0 @@
-import { useEffect, useMemo, useState } from "react";
-import { Controller, useForm } from "react-hook-form";
-import { useRouter } from "next/router";
-import {
- faCheck,
- faCopy,
- faMagnifyingGlass,
- faPencil,
- faPlus,
- faServer,
- faTrash
-} from "@fortawesome/free-solid-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { yupResolver } from "@hookform/resolvers/yup";
-import * as yup from "yup";
-
-import { generateKeyPair } from "@app/components/utilities/cryptography/crypto";
-import {
- Button,
- DeleteActionModal,
- EmptyState,
- FormControl,
- IconButton,
- Input,
- Modal,
- ModalContent,
- Select,
- SelectItem,
- Table,
- TableContainer,
- TableSkeleton,
- TBody,
- Td,
- Th,
- THead,
- Tr
-} from "@app/components/v2";
-import { useOrganization, useWorkspace } from "@app/context";
-import { usePopUp, useToggle } from "@app/hooks";
-import {
- useCreateServiceAccount,
- useDeleteServiceAccount,
- useGetServiceAccounts
-} from "@app/hooks/api";
-
-const serviceAccountExpiration = [
- { label: "1 Day", value: 86400 },
- { label: "7 Days", value: 604800 },
- { label: "1 Month", value: 2592000 },
- { label: "6 months", value: 15552000 },
- { label: "12 months", value: 31104000 },
- { label: "Never", value: -1 }
-];
-
-const addServiceAccountFormSchema = yup.object({
- name: yup.string().required().label("Name").trim(),
- expiresIn: yup.string().required().label("Service Account Expiration")
-});
-
-type TAddServiceAccountForm = yup.InferType;
-
-export const OrgServiceAccountsTable = () => {
- const router = useRouter();
- const { currentOrg } = useOrganization();
- const { currentWorkspace } = useWorkspace();
-
- const orgId = currentOrg?._id || "";
- const [step, setStep] = useState(0);
- const [isAccessKeyCopied, setIsAccessKeyCopied] = useToggle(false);
- const [isPublicKeyCopied, setIsPublicKeyCopied] = useToggle(false);
- const [isPrivateKeyCopied, setIsPrivateKeyCopied] = useToggle(false);
- const [accessKey, setAccessKey] = useState("");
- const [publicKey, setPublicKey] = useState("");
- const [privateKey, setPrivateKey] = useState("");
- const [searchServiceAccountFilter, setSearchServiceAccountFilter] = useState("");
- const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
- "addServiceAccount",
- "removeServiceAccount"
- ] as const);
-
- const { data: serviceAccounts = [], isLoading: isServiceAccountsLoading } =
- useGetServiceAccounts(orgId);
-
- const createServiceAccount = useCreateServiceAccount();
- const removeServiceAccount = useDeleteServiceAccount();
-
- useEffect(() => {
- let timer: NodeJS.Timeout;
- if (isAccessKeyCopied) {
- timer = setTimeout(() => setIsAccessKeyCopied.off(), 2000);
- }
-
- if (isPublicKeyCopied) {
- timer = setTimeout(() => setIsPublicKeyCopied.off(), 2000);
- }
-
- if (isPrivateKeyCopied) {
- timer = setTimeout(() => setIsPrivateKeyCopied.off(), 2000);
- }
-
- return () => clearTimeout(timer);
- }, [isAccessKeyCopied, isPublicKeyCopied, isPrivateKeyCopied]);
-
- const {
- control,
- handleSubmit,
- reset,
- formState: { isSubmitting }
- } = useForm({ resolver: yupResolver(addServiceAccountFormSchema) });
-
- const onAddServiceAccount = async ({ name, expiresIn }: TAddServiceAccountForm) => {
- if (!currentOrg?._id) return;
-
- const keyPair = generateKeyPair();
- setPublicKey(keyPair.publicKey);
- setPrivateKey(keyPair.privateKey);
-
- const serviceAccountDetails = await createServiceAccount.mutateAsync({
- name,
- organizationId: currentOrg?._id,
- publicKey: keyPair.publicKey,
- expiresIn: Number(expiresIn)
- });
-
- setAccessKey(serviceAccountDetails.serviceAccountAccessKey);
-
- setStep(1);
- reset();
- };
-
- const onRemoveServiceAccount = async () => {
- const serviceAccountId = (popUp?.removeServiceAccount?.data as { _id: string })?._id;
- await removeServiceAccount.mutateAsync(serviceAccountId);
- handlePopUpClose("removeServiceAccount");
- };
-
- const filteredServiceAccounts = useMemo(
- () =>
- serviceAccounts.filter(({ name }) => name.toLowerCase().includes(searchServiceAccountFilter)),
- [serviceAccounts, searchServiceAccountFilter]
- );
-
- const renderStep = (stepToRender: number) => {
- switch (stepToRender) {
- case 0:
- return (
-
- );
- case 1:
- return (
- <>
- Access Key
-
-
{accessKey}
-
{
- navigator.clipboard.writeText(accessKey);
- setIsAccessKeyCopied.on();
- }}
- >
-
-
- Copy
-
-
-
- Public Key
-
-
{publicKey}
-
{
- navigator.clipboard.writeText(publicKey);
- setIsPublicKeyCopied.on();
- }}
- >
-
-
- Copy
-
-
-
- Private Key
-
-
{privateKey}
-
{
- navigator.clipboard.writeText(privateKey);
- setIsPrivateKeyCopied.on();
- }}
- >
-
-
- Copy
-
-
-
- >
- );
- default:
- return ;
- }
- };
-
- return (
-
-
-
- setSearchServiceAccountFilter(e.target.value)}
- leftIcon={}
- placeholder="Search service accounts..."
- />
-
-
}
- onClick={() => {
- setStep(0);
- reset();
- handlePopUpOpen("addServiceAccount");
- }}
- >
- Add Service Account
-
-
-
-
-
- | Name |
- Valid Until |
- |
-
-
- {isServiceAccountsLoading && (
-
- )}
- {!isServiceAccountsLoading &&
- filteredServiceAccounts.map(({ name, expiresAt, _id: serviceAccountId }) => {
- return (
-
- | {name} |
- {new Date(expiresAt).toUTCString()} |
-
-
- {
- if (currentWorkspace?._id) {
- router.push(
- `/settings/org/${currentWorkspace._id}/service-accounts/${serviceAccountId}`
- );
- }
- }}
- className="mr-2"
- >
-
-
-
- handlePopUpOpen("removeServiceAccount", { _id: serviceAccountId })
- }
- >
-
-
-
- |
-
- );
- })}
-
-
- {!isServiceAccountsLoading && filteredServiceAccounts?.length === 0 && (
-
- )}
-
-
{
- handlePopUpToggle("addServiceAccount", isOpen);
- reset();
- }}
- >
-
- {renderStep(step)}
-
-
-
handlePopUpToggle("removeServiceAccount", isOpen)}
- onDeleteApproved={onRemoveServiceAccount}
- />
-
- );
-};
diff --git a/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/index.tsx b/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/index.tsx
deleted file mode 100644
index 8e900f6fa..000000000
--- a/frontend/src/views/Org/MembersPage/components/OrgServiceAccountsTable/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
\ No newline at end of file
diff --git a/frontend/src/views/Org/MembersPage/components/index.tsx b/frontend/src/views/Org/MembersPage/components/index.tsx
deleted file mode 100644
index 796bb6aa3..000000000
--- a/frontend/src/views/Org/MembersPage/components/index.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-export { OrgIncidentContactsTable } from "./OrgIncidentContactsTable";
-export { OrgMembersTable } from "./OrgMembersTable";
-export { OrgNameChangeSection } from "./OrgNameChangeSection";
-export { OrgServiceAccountsTable } from "./OrgServiceAccountsTable";
-