Continue removing unused frontend components/logic, improve querying in select pages

This commit is contained in:
Tuan Dang
2023-08-10 12:18:17 +07:00
parent b47f61f1ad
commit 9963724a6a
44 changed files with 181 additions and 1536 deletions

View File

@@ -1,99 +0,0 @@
import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { Dialog, Transition } from "@headlessui/react";
import addIncidentContact from "@app/pages/api/organization/addIncidentContact";
import Button from "../buttons/Button";
import InputField from "../InputField";
type Props = {
isOpen: boolean;
closeModal: () => void;
incidentContacts: string[];
setIncidentContacts: (arg: string[]) => void;
};
const AddIncidentContactDialog = ({
isOpen,
closeModal,
incidentContacts,
setIncidentContacts
}: Props) => {
const [incidentContactEmail, setIncidentContactEmail] = useState("");
const { t } = useTranslation();
const submit = () => {
setIncidentContacts(
incidentContacts?.length > 0
? incidentContacts.concat([incidentContactEmail])
: [incidentContactEmail]
);
addIncidentContact(localStorage.getItem("orgData.id") as string, incidentContactEmail);
closeModal();
};
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md border border-gray-700 bg-bunker-800 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
{t("section.incident.add-dialog.title")}
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
{t("section.incident.add-dialog.description")}
</p>
</div>
<div className="max-h-28">
<InputField
label={t("common.email")}
onChangeHandler={setIncidentContactEmail}
type="varName"
value={incidentContactEmail}
placeholder=""
isRequired
/>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text={t("section.incident.add-dialog.add-incident") as string}
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
};
export default AddIncidentContactDialog;

View File

@@ -6,8 +6,10 @@ import { useNotificationContext } from "@app/components/context/Notifications/No
import { Select, SelectItem } from "@app/components/v2";
import { useSubscription, useWorkspace } from "@app/context";
import updateUserProjectPermission from "@app/ee/api/memberships/UpdateUserProjectPermission";
import changeUserRoleInWorkspace from "@app/pages/api/workspace/changeUserRoleInWorkspace";
import deleteUserFromWorkspace from "@app/pages/api/workspace/deleteUserFromWorkspace";
import {
useDeleteUserFromWorkspace,
useUpdateUserWorkspaceRole
} from "@app/hooks/api";
import getLatestFileKey from "@app/pages/api/workspace/getLatestFileKey";
import uploadKeys from "@app/pages/api/workspace/uploadKeys";
@@ -40,9 +42,11 @@ type EnvironmentProps = {
const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoading }: Props) => {
const { currentWorkspace } = useWorkspace();
const { subscription } = useSubscription();
const [roleSelected, setRoleSelected] = useState(
Array(userData?.length).fill(userData.map((user) => user.role))
);
const { mutateAsync: deleteUserFromWorkspaceMutateAsync } = useDeleteUserFromWorkspace();
const { mutateAsync: updateUserWorkspaceRoleMutateAsync } = useUpdateUserWorkspaceRole();
// const [roleSelected, setRoleSelected] = useState(
// Array(userData?.length).fill(userData.map((user) => user.role))
// );
const router = useRouter();
const [myRole, setMyRole] = useState("member");
const [workspaceEnvs, setWorkspaceEnvs] = useState<EnvironmentProps[]>([]);
@@ -52,38 +56,15 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
const workspaceId = router.query.id as string;
// Delete the row in the table (e.g. a user)
// #TODO: Add a pop-up that warns you that the user is going to be deleted.
const handleDelete = (membershipId: string, index: number) => {
// setUserIdToBeDeleted(userId);
// onClick();
deleteUserFromWorkspace(membershipId);
changeData(userData.filter((v, i) => i !== index));
setRoleSelected([
...roleSelected.slice(0, index),
...roleSelected.slice(index + 1, userData?.length)
]);
const handleDelete = async (membershipId: string) => {
await deleteUserFromWorkspaceMutateAsync(membershipId);
};
// Update the rold of a certain user
const handleRoleUpdate = (index: number, e: string) => {
changeUserRoleInWorkspace(userData[index].membershipId, e.toLowerCase());
changeData([
...userData.slice(0, index),
...[
{
key: userData[index].key,
firstName: userData[index].firstName,
lastName: userData[index].lastName,
email: userData[index].email,
role: e.toLocaleLowerCase(),
status: userData[index].status,
userId: userData[index].userId,
membershipId: userData[index].membershipId,
publicKey: userData[index].publicKey,
deniedPermissions: userData[index].deniedPermissions
}
],
...userData.slice(index + 1, userData?.length)
]);
const handleRoleUpdate = async (index: number, e: string) => {
await updateUserWorkspaceRoleMutateAsync({
membershipId: userData[index].membershipId,
role: e.toLowerCase()
});
createNotification({
text: "Successfully changed user role.",
type: "success"
@@ -373,7 +354,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter, isUserListLoa
myRole !== "member" ? (
<div className="mt-0.5 flex items-center opacity-50 hover:opacity-100">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
onButtonPressed={() => handleDelete(row.membershipId)}
color="red"
size="icon-sm"
icon={faX}

View File

@@ -1,122 +0,0 @@
import Image from "next/image";
import { faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth";
interface IntegrationOption {
clientId: string;
clientSlug?: string; // vercel-integration specific
docsLink: string;
image: string;
isAvailable: boolean;
name: string;
slug: string;
type: string;
}
interface IntegrationAuth {
_id: string;
integration: string;
workspace: string;
createdAt: string;
updatedAt: string;
}
interface Props {
cloudIntegrationOption: IntegrationOption;
setSelectedIntegrationOption: (cloudIntegration: IntegrationOption) => void;
integrationOptionPress: (cloudIntegrationOption: IntegrationOption) => void;
integrationAuths: IntegrationAuth[];
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
}
const CloudIntegration = ({
cloudIntegrationOption,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths,
handleDeleteIntegrationAuth
}: Props) => {
return integrationAuths ? (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className={`relative ${
cloudIntegrationOption.isAvailable
? "cursor-pointer duration-200 hover:bg-mineshaft-700"
: "opacity-50"
} flex h-32 flex-row items-center rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4`}
onClick={() => {
if (!cloudIntegrationOption.isAvailable) return;
setSelectedIntegrationOption(cloudIntegrationOption);
integrationOptionPress(cloudIntegrationOption);
}}
key={cloudIntegrationOption.name}
>
<Image
src={`/images/integrations/${cloudIntegrationOption.image}`}
height={70}
width={70}
alt="integration logo"
/>
{cloudIntegrationOption.name.split(" ").length > 2 ? (
<div className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
<div>{cloudIntegrationOption.name.split(" ")[0]}</div>
<div className="text-base">
{cloudIntegrationOption.name.split(" ")[1]} {cloudIntegrationOption.name.split(" ")[2]}
</div>
</div>
) : (
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
{cloudIntegrationOption.name}
</div>
)}
{cloudIntegrationOption.isAvailable &&
integrationAuths
.map((authorization) => authorization?.integration)
.includes(cloudIntegrationOption.slug) && (
<div className="group absolute top-0 right-0 z-40 flex flex-row">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={async (event) => {
event.stopPropagation();
const deletedIntegrationAuth = await deleteIntegrationAuth({
integrationAuthId: integrationAuths
.filter(
(authorization) => authorization.integration === cloudIntegrationOption.slug
)
.map((authorization) => authorization._id)[0]
});
handleDeleteIntegrationAuth({
integrationAuth: deletedIntegrationAuth
});
}}
className="flex w-max cursor-pointer flex-row items-center rounded-bl-md bg-red py-0.5 px-2 text-xs opacity-30 duration-200 group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="mr-2 text-xs" />
Revoke
</div>
<div className="flex w-max flex-row items-center rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-70 duration-200 group-hover:opacity-100">
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
Authorized
</div>
</div>
)}
{!cloudIntegrationOption.isAvailable && (
<div className="group absolute top-0 right-0 z-50 flex flex-row">
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-yellow py-0.5 px-2 text-xs text-black opacity-90">
Coming Soon
</div>
</div>
)}
</div>
) : (
<div />
);
};
export default CloudIntegration;

View File

@@ -1,63 +0,0 @@
import { useTranslation } from "react-i18next";
import CloudIntegration from "./CloudIntegration";
interface IntegrationOption {
clientId: string;
clientSlug?: string; // vercel-integration specific
docsLink: string;
image: string;
isAvailable: boolean;
name: string;
slug: string;
type: string;
}
interface IntegrationAuth {
_id: string;
integration: string;
workspace: string;
createdAt: string;
updatedAt: string;
}
interface Props {
cloudIntegrationOptions: IntegrationOption[];
setSelectedIntegrationOption: () => void;
integrationOptionPress: (integrationOption: IntegrationOption) => void;
integrationAuths: IntegrationAuth[];
handleDeleteIntegrationAuth: (args: { integrationAuth: IntegrationAuth }) => void;
}
const CloudIntegrationSection = ({
cloudIntegrationOptions,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths,
handleDeleteIntegrationAuth
}: Props) => {
const { t } = useTranslation();
return (
<>
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">{t("integrations.cloud-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-start")}</p>
</div>
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
<CloudIntegration
cloudIntegrationOption={cloudIntegrationOption}
setSelectedIntegrationOption={setSelectedIntegrationOption}
integrationOptionPress={integrationOptionPress}
integrationAuths={integrationAuths}
handleDeleteIntegrationAuth={handleDeleteIntegrationAuth}
key={`cloud-integration-${cloudIntegrationOption.slug}`}
/>
))}
</div>
</>
);
};
export default CloudIntegrationSection;

View File

@@ -1,36 +0,0 @@
import Image from "next/image";
interface Framework {
name: string;
slug: string;
image: string;
docsLink: string;
}
const FrameworkIntegration = ({ framework }: { framework: Framework }) => (
<a
href={framework.docsLink}
rel="noopener noreferrer"
target="_blank"
className="relative flex flex-row justify-center duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer"
>
<div
className={`hover:bg-mineshaft-700 cursor-pointer font-semibold bg-mineshaft-800 border border-mineshaft-600 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${
framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"
} text-center w-full max-w-xs`}
>
{framework?.image && (
<Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
/>
)}
{framework?.name && framework?.image && <div className="h-2" />}
{framework?.name && framework.name}
</div>
</a>
);
export default FrameworkIntegration;

View File

@@ -1,38 +0,0 @@
import { useTranslation } from "react-i18next";
import FrameworkIntegration from "./FrameworkIntegration";
interface Framework {
name: string;
image: string;
link: string;
slug: string;
docsLink: string;
}
interface Props {
frameworks: [Framework];
}
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
const { t } = useTranslation();
return (
<>
<div className="mx-4 mt-12 mb-4 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">{t("integrations.framework-integrations")}</h1>
<p className="text-base text-gray-400">{t("integrations.click-to-setup")}</p>
</div>
<div className="mx-6 mt-4 grid max-w-5xl grid-cols-7 grid-rows-2 gap-4">
{frameworks.map((framework) => (
<FrameworkIntegration
framework={framework}
key={`framework-integration-${framework.slug}`}
/>
))}
</div>
</>
);
};
export default FrameworkIntegrationSection;

View File

@@ -1,313 +0,0 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { faArrowRight, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// TODO: This needs to be moved from public folder
import {
contextNetlifyMapping,
integrationSlugNameMapping,
reverseContextNetlifyMapping
} from "public/data/frequentConstants";
import Button from "@app/components/basic/buttons/Button";
import ListBox from "@app/components/basic/Listbox";
import deleteIntegration from "../../pages/api/integrations/DeleteIntegration";
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
import updateIntegration from "../../pages/api/integrations/updateIntegration";
interface Integration {
_id: string;
isActive: boolean;
app: string | null;
appId: string | null;
path: string | null;
region: string | null;
createdAt: string;
updatedAt: string;
environment: string;
integration: string;
targetEnvironment: string;
workspace: string;
integrationAuth: string;
secretPath: string;
}
interface IntegrationApp {
name: string;
appId?: string;
owner?: string;
}
type Props = {
integration: Integration;
integrations: Integration[];
setIntegrations: any;
bot: any;
setBot: any;
environments: Array<{ name: string; slug: string }>;
handleDeleteIntegration: (args: { integration: Integration }) => void;
};
// TODO: refactor
const IntegrationTile = ({
integration,
integrations,
bot,
setBot,
setIntegrations,
environments = [],
handleDeleteIntegration
}: Props) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props["environments"][0]>(
environments.find(({ slug }) => slug === integration?.environment) || {
name: "",
slug: ""
}
);
const router = useRouter();
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
const [integrationApp, setIntegrationApp] = useState(""); // integration app name
const [integrationTargetEnvironment, setIntegrationTargetEnvironment] = useState("");
useEffect(() => {
const loadIntegration = async () => {
const tempApps: [IntegrationApp] = await getIntegrationApps({
integrationAuthId: integration?.integrationAuth
});
setApps(tempApps);
if (integration?.app) {
setIntegrationApp(integration.app);
} else if (integration?.path && integration?.region) {
setIntegrationApp(`${integration.path} (${integration.region})`);
} else if (tempApps.length > 0) {
setIntegrationApp(tempApps[0].name);
} else {
setIntegrationApp("");
}
switch (integration.integration) {
case "vercel":
setIntegrationTargetEnvironment(
integration?.targetEnvironment
? integration.targetEnvironment.charAt(0).toUpperCase() +
integration.targetEnvironment.substring(1)
: "Development"
);
break;
case "netlify":
setIntegrationTargetEnvironment(
integration?.targetEnvironment
? contextNetlifyMapping[integration.targetEnvironment]
: "Local development"
);
break;
default:
break;
}
};
loadIntegration();
}, []);
const handleStartIntegration = async () => {
const reformatTargetEnvironment = (targetEnvironment: string) => {
switch (integration.integration) {
case "vercel":
return targetEnvironment.toLowerCase();
case "netlify":
return reverseContextNetlifyMapping[targetEnvironment];
default:
return null;
}
};
try {
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
const appId = siteApp?.appId ?? null;
const owner = siteApp?.owner ?? null;
// return updated integration
const updatedIntegration = await updateIntegration({
integrationId: integration._id,
environment: integrationEnvironment.slug,
isActive: true,
app: integrationApp,
appId,
targetEnvironment: reformatTargetEnvironment(integrationTargetEnvironment),
owner
});
setIntegrations(
integrations.map((i) => (i._id === updatedIntegration._id ? updatedIntegration : i))
);
} catch (err) {
console.error(err);
}
};
// eslint-disable-next-line @typescript-eslint/no-shadow
const renderIntegrationSpecificParams = (integration: Integration) => {
try {
switch (integration.integration) {
case "vercel":
return (
<div>
<div className="mb-2 w-60 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={!integration.isActive ? ["Development", "Preview", "Production"] : null}
isSelected={integrationTargetEnvironment}
onChange={setIntegrationTargetEnvironment}
isFull
/>
</div>
);
case "netlify":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">CONTEXT</div>
<ListBox
data={
!integration.isActive
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
: null
}
isSelected={integrationTargetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
case "railway":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={
!integration.isActive
? ["Production", "Deploy previews", "Branch deploys", "Local development"]
: null
}
isSelected={integration.targetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
case "gitlab":
return (
<div>
<div className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
<ListBox
data={null}
isSelected={integration.targetEnvironment}
onChange={setIntegrationTargetEnvironment}
/>
</div>
);
default:
return <div />;
}
} catch (err) {
console.error(err);
}
return <div />;
};
if (!integrationApp && integration.integration !== "checkly") return <div />;
const isSelected =
integration.integration === "hashicorp-vault"
? `${integration.app} - path: ${integration.path}`
: integrationApp;
return (
<div className="mx-6 mb-8 flex max-w-5xl justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6">
<div className="flex">
<div>
<p className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</p>
<ListBox
data={!integration.isActive ? environments.map(({ name }) => name) : null}
isSelected={integrationEnvironment.name}
onChange={(envName) =>
setIntegrationEnvironment(
environments.find(({ name }) => envName === name) || {
name: "unknown",
slug: "unknown"
}
)
}
isFull
/>
</div>
<div className="ml-2">
<p className="mb-2 text-xs font-semibold text-gray-400">SECRET PATH</p>
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
{integration.secretPath}
</div>
</div>
<div className="pt-2">
<FontAwesomeIcon icon={faArrowRight} className="mx-4 mt-8 text-gray-400" />
</div>
<div className="mr-2">
<p className="mb-2 text-xs font-semibold text-gray-400">INTEGRATION</p>
<div className="cursor-default rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
{/* {integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)} */}
{integrationSlugNameMapping[integration.integration]}
</div>
</div>
<div className="mr-2">
<div className="mb-2 text-xs font-semibold text-gray-400">APP</div>
{integrationApp ? (
<div title={integrationApp}>
<ListBox
data={!integration.isActive ? apps.map((app) => app.name) : null}
isSelected={isSelected}
onChange={(app) => {
setIntegrationApp(app);
}}
/>
</div>
) : (
<div className="h-10 w-52 animate-pulse rounded-md bg-mineshaft-600 px-4 py-2 font-bold">
-
</div>
)}
</div>
{renderIntegrationSpecificParams(integration)}
</div>
<div className="flex cursor-default items-end">
{integration.isActive ? (
<div className="flex max-w-5xl flex-row items-center rounded-md border border-mineshaft-500 bg-mineshaft-600 p-[0.44rem] px-4">
<FontAwesomeIcon icon={faCheck} className="mr-2.5 text-lg text-primary" />
<div className="font-semibold text-gray-300">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={() => handleStartIntegration()}
color="mineshaft"
size="md"
/>
)}
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
<Button
onButtonPressed={() =>
handleDeleteIntegration({
integration
})
}
color="red"
size="icon-md"
icon={faXmark}
/>
</div>
</div>
</div>
);
};
export default IntegrationTile;

View File

@@ -1,63 +0,0 @@
import IntegrationTile from "./Integration";
interface Props {
integrations: any;
setIntegrations: any;
bot: any;
setBot: any;
environments: Array<{ name: string; slug: string }>;
handleDeleteIntegration: (args: { integration: Integration }) => void;
}
interface Integration {
_id: string;
isActive: boolean;
app: string | null;
appId: string | null;
path: string | null;
region: string | null;
createdAt: string;
updatedAt: string;
environment: string;
integration: string;
targetEnvironment: string;
workspace: string;
integrationAuth: string;
secretPath: string;
}
const ProjectIntegrationSection = ({
integrations,
setIntegrations,
bot,
setBot,
environments = [],
handleDeleteIntegration
}: Props) => {
return integrations.length > 0 ? (
<div className="mb-12">
<div className="mx-4 mb-4 mt-6 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
<h1 className="text-3xl font-semibold">Current Integrations</h1>
<p className="text-base text-bunker-300">Manage integrations with third-party services.</p>
</div>
{integrations.map((integration: Integration) => {
return (
<IntegrationTile
key={`integration-${integration?._id.toString()}`}
integration={integration}
integrations={integrations}
bot={bot}
setBot={setBot}
setIntegrations={setIntegrations}
environments={environments}
handleDeleteIntegration={handleDeleteIntegration}
/>
);
})}
</div>
) : (
<div />
);
};
export default ProjectIntegrationSection;

View File

@@ -91,65 +91,55 @@ const initProjectHelper = async ({
organizationId: string;
projectName: string;
}) => {
let project;
try {
// create new project
const { data: { workspace } } = await createWorkspace({
workspaceName: projectName,
organizationId
});
// create and upload new (encrypted) project key
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
// create new project
const { data: { workspace } } = await createWorkspace({
workspaceName: projectName,
organizationId
});
project = workspace;
const user = await fetchUserDetails();
// create and upload new (encrypted) project key
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
if (!PRIVATE_KEY) throw new Error("Failed to find private key");
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: user.publicKey,
privateKey: PRIVATE_KEY
});
const user = await fetchUserDetails();
await uploadKeys(workspace._id, user._id, ciphertext, nonce);
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: user.publicKey,
privateKey: PRIVATE_KEY
});
await uploadKeys(project._id, user._id, ciphertext, nonce);
const workspaceId = project._id;
// encrypt and upload secrets to new project
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded,
workspaceId,
env: "dev"
});
secrets?.forEach((secret) => {
createSecret({
workspaceId,
environment: secret.environment,
type: secret.type,
secretKey: secret.secretName,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
secretPath: "/"
});
});
} catch (err) {
console.error("Failed to init project in organization", err);
}
return project;
// encrypt and upload secrets to new project
const secrets = await encryptSecrets({
secretsToEncrypt: secretsToBeAdded,
workspaceId: workspace._id,
env: "dev"
});
secrets?.forEach((secret) => {
createSecret({
workspaceId: workspace._id,
environment: secret.environment,
type: secret.type,
secretKey: secret.secretName,
secretKeyCiphertext: secret.secretKeyCiphertext,
secretKeyIV: secret.secretKeyIV,
secretKeyTag: secret.secretKeyTag,
secretValueCiphertext: secret.secretValueCiphertext,
secretValueIV: secret.secretValueIV,
secretValueTag: secret.secretValueTag,
secretCommentCiphertext: secret.secretCommentCiphertext,
secretCommentIV: secret.secretCommentIV,
secretCommentTag: secret.secretCommentTag,
secretPath: "/"
});
});
return workspace;
}
export {

View File

@@ -8,18 +8,16 @@ const incidentContactKeys = {
getAllContact: (orgId: string) => ["org-incident-contacts", { orgId }] as const
};
const fetchOrgIncidentContacts = async (orgId: string) => {
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
`/api/v1/organization/${orgId}/incidentContactOrg`
);
return data.incidentContactsOrg;
};
export const useGetOrgIncidentContact = (orgId: string) =>
useQuery({
queryKey: incidentContactKeys.getAllContact(orgId),
queryFn: () => fetchOrgIncidentContacts(orgId),
queryFn: async () => {
const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>(
`/api/v1/organization/${orgId}/incidentContactOrg`
);
return data.incidentContactsOrg;
},
enabled: Boolean(orgId)
});

View File

@@ -32,4 +32,4 @@ export const useDeleteIntegration = () => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceIntegrations(workspaceId));
}
});
};
};

View File

@@ -41,8 +41,10 @@ export const useRenameOrg = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, RenameOrgDTO>({
mutationFn: ({ newOrgName, orgId }) =>
apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }),
mutationFn: ({ newOrgName, orgId }) => {
console.log("useRenameOrg");
return apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName });
},
onSuccess: () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
}

View File

@@ -10,7 +10,6 @@ import {
UserWsTags
} from "./types";
const workspaceTags = {
getWsTags: (workspaceID: string) => ["workspace-tags", { workspaceID }] as const
};
@@ -23,12 +22,13 @@ const fetchWsTag = async (workspaceID: string) => {
return data.workspaceTags;
};
export const useGetWsTags = (workspaceID: string) =>
useQuery({
export const useGetWsTags = (workspaceID: string) => {
return useQuery({
queryKey: workspaceTags.getWsTags(workspaceID),
queryFn: () => fetchWsTag(workspaceID),
enabled: Boolean(workspaceID)
});
}
export const useCreateWsTag = () => {
const queryClient = useQueryClient();
@@ -59,4 +59,4 @@ export const useDeleteWsTag = () => {
queryClient.invalidateQueries(workspaceTags.getWsTags(tagData?.workspace));
}
});
};
};

View File

@@ -3,10 +3,8 @@ export {
useAddUserToOrg,
useAddUserToWs,
useCreateAPIKey,
useCreateMyAction,
useDeleteAPIKey,
useDeleteOrgMembership,
useGetMyActions,
useGetMyAPIKeys,
useGetMyIp,
useGetMySessions,

View File

@@ -158,8 +158,9 @@ export const useDeleteOrgMembership = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeletOrgMembershipDTO>({
mutationFn: ({ membershipId, orgId }) =>
apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`),
mutationFn: ({ membershipId, orgId }) => {
return apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`)
},
onSuccess: (_, { orgId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(orgId));
}
@@ -170,10 +171,11 @@ export const useUpdateOrgUserRole = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, UpdateOrgUserRoleDTO>({
mutationFn: ({ organizationId, membershipId, role }) =>
apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
mutationFn: ({ organizationId, membershipId, role }) => {
return apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
role
}),
});
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
},

View File

@@ -1,6 +1,8 @@
export {
useAddUserToWorkspace,
useCreateWorkspace,
useCreateWsEnvironment,
useDeleteUserFromWorkspace,
useDeleteWorkspace,
useDeleteWsEnvironment,
useGetUserWorkspaceMemberships,
@@ -11,8 +13,9 @@ export {
useGetWorkspaceIndexStatus,
useGetWorkspaceIntegrations,
useGetWorkspaceSecrets,
useGetWorkspaceUsers,
useNameWorkspaceSecrets,
useRenameWorkspace,
useToggleAutoCapitalization,
useUpdateWsEnvironment
} from "./queries";
useUpdateUserWorkspaceRole,
useUpdateWsEnvironment} from "./queries";

View File

@@ -29,7 +29,8 @@ export const workspaceKeys = {
getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"],
getAllUserWorkspace: ["workspaces"] as const,
getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const,
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const
getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const,
getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const
};
const fetchWorkspaceById = async (workspaceId: string) => {
@@ -218,7 +219,9 @@ export const useDeleteWorkspace = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, DeleteWorkspaceDTO>({
mutationFn: ({ workspaceID }) => apiRequest.delete(`/api/v1/workspace/${workspaceID}`),
mutationFn: ({ workspaceID }) => {
return apiRequest.delete(`/api/v1/workspace/${workspaceID}`);
},
onSuccess: () => {
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
}
@@ -273,3 +276,74 @@ export const useDeleteWsEnvironment = () => {
});
};
export const useGetWorkspaceUsers = (workspaceId: string) => {
return useQuery({
queryKey: workspaceKeys.getWorkspaceUsers(workspaceId),
queryFn: async () => {
const { data: { users } } = await apiRequest.get(
`/api/v1/workspace/${workspaceId}/users`
);
return users;
},
enabled: true
});
}
export const useAddUserToWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
email,
workspaceId
}: {
email: string;
workspaceId: string;
}) => {
const { data: { invitee, latestKey } } = await apiRequest.post(`/api/v1/workspace/${workspaceId}/invite-signup`, { email });
return ({
invitee,
latestKey
});
},
onSuccess: (_, dto) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(dto.workspaceId));
}
});
};
export const useDeleteUserFromWorkspace = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (membershipId: string) => {
const { data: { deletedMembership } } = await apiRequest.delete(`/api/v1/membership/${membershipId}`);
return deletedMembership;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
}
});
};
export const useUpdateUserWorkspaceRole = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
membershipId,
role
}: {
membershipId: string;
role: string;
}) => {
const { data: { membership } } = await apiRequest.post(`/api/v1/membership/${membershipId}/change-role`, {
role
});
return membership;
},
onSuccess: (res) => {
queryClient.invalidateQueries(workspaceKeys.getWorkspaceUsers(res.workspace));
}
});
};

View File

@@ -1,25 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
integrationId: string;
}
/**
* This route deletes an integration from a certain project
* @param {*} integrationId
* @returns
*/
const deleteIntegration = ({ integrationId }: Props) =>
SecurityClient.fetchCall(`/api/v1/integration/${integrationId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).integration;
}
return undefined;
});
export default deleteIntegration;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
integrationAuthId: string;
}
/**
* This route deletes an integration authorization from a certain project
* @param {*} integrationAuthId
* @returns
*/
const deleteIntegrationAuth = ({ integrationAuthId }: Props) =>
SecurityClient.fetchCall(`/api/v1/integration-auth/${integrationAuthId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).integrationAuth;
}
console.log("Failed to delete an integration authorization");
return undefined;
});
export default deleteIntegrationAuth;

View File

@@ -1,21 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
integrationAuthId: string;
}
const getIntegrationApps = ({ integrationAuthId }: Props) =>
SecurityClient.fetchCall(`/api/v1/integration-auth/${integrationAuthId}/apps`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).apps;
}
console.log("Failed to get available apps for an integration");
return undefined;
});
export default getIntegrationApps;

View File

@@ -1,17 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
const getIntegrationOptions = () =>
SecurityClient.fetchCall("/api/v1/integration-auth/integration-options", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).integrationOptions;
}
console.log("Failed to get (cloud) integration options");
return undefined;
});
export default getIntegrationOptions;

View File

@@ -1,35 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
integrationId: string;
appName: string;
environment: string;
}
/**
* This route starts the integration after teh default one if gonna set up.
* @param {*} integrationId
* @returns
*/
const startIntegration = ({ integrationId, appName, environment }: Props) =>
SecurityClient.fetchCall(`/api/v1/integration/${integrationId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
update: {
app: appName,
environment,
isActive: true
}
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to start an integration");
return undefined;
});
export default startIntegration;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
workspaceId: string;
}
/**
* This route gets authorizations of a certain project (Heroku, etc.)
* @param {*} workspaceId
* @returns
*/
const getWorkspaceAuthorizations = ({ workspaceId }: Props) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/authorizations`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).authorizations;
}
console.log("Failed to get project authorizations");
return undefined;
});
export default getWorkspaceAuthorizations;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Props {
workspaceId: string;
}
/**
* This route gets integrations of a certain project (Heroku, etc.)
* @param {*} workspaceId
* @returns
*/
const getWorkspaceIntegrations = ({ workspaceId }: Props) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/integrations`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).integrations;
}
console.log("Failed to get the project integrations");
return undefined;
});
export default getWorkspaceIntegrations;

View File

@@ -1,55 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route starts the integration after teh default one if gonna set up.
* Update integration with id [integrationId] to sync envars from the project's
* [environment] to the integration [app] with active state [isActive]
* @param {Object} obj
* @param {String} obj.integrationId - id of integration
* @param {Boolean} obj.isActive - active state
* @param {String} obj.environment - project environment to push secrets from
* @param {String} obj.app - name of app
* @param {String} obj.appId - (optional) app ID for integration
* @param {String} obj.targetEnvironment - target environment for integration
* @param {String} obj.owner - (optional) owner login of repo for GitHub integration
* @returns
*/
const updateIntegration = ({
integrationId,
isActive,
environment,
app,
appId,
targetEnvironment,
owner
}: {
integrationId: string;
isActive: boolean;
environment: string;
app: string;
appId: string | null;
targetEnvironment: string | null;
owner: string | null;
}) =>
SecurityClient.fetchCall(`/api/v1/integration/${integrationId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
app,
environment,
isActive,
appId,
targetEnvironment,
owner
})
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).integration;
}
console.log("Failed to start an integration");
return undefined;
});
export default updateIntegration;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get info about a certain org
* @param {string} orgId - the organization ID
* @returns
*/
const getOrganization = ({ orgId }: { orgId: string }) =>
SecurityClient.fetchCall(`/api/v1/organization/${orgId}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res?.status === 200) {
return (await res.json()).organization;
}
console.log("Failed to get org info");
return undefined;
});
export default getOrganization;

View File

@@ -1,24 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get all the project memebrships of users in an org.
* @param {*} req
* @param {*} res
* @returns
*/
const getOrganizationProjectMemberships = (req: { orgId: string }) =>
SecurityClient.fetchCall(`/api/v1/organization/${req.orgId}/workspace-memberships`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return res.json();
}
console.log("Failed to get project memberships for users in an org");
return undefined;
});
export default getOrganizationProjectMemberships;

View File

@@ -1,25 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get all the users in an org.
* @param {*} req
* @param {*} res
* @returns
*/
// TODO: this file is not used anywhere
const getOrganizationProjects = (req: { orgId: string }) =>
SecurityClient.fetchCall(`/api/organization/${req.orgId}/workspaces`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).workspaces;
}
console.log("Failed to get projects for an org");
return undefined;
});
export default getOrganizationProjects;

View File

@@ -1,23 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route redirects the user to the right stripe billing page.
* @param {*} req
* @param {*} res
* @returns
*/
const StripeRedirect = ({ orgId }: { orgId: string }) =>
SecurityClient.fetchCall(`/api/v1/organization/${orgId}/customer-portal-session`, {
method: "POST",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
window.location.href = (await res.json()).url;
return;
}
console.log("Failed to redirect to Stripe");
});
export default StripeRedirect;

View File

@@ -1,25 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route add an incident contact email to a certain organization
* @param {*} param0
* @returns
*/
const addIncidentContact = (organizationId: string, email: string) =>
SecurityClient.fetchCall(`/api/v1/organization/${organizationId}/incidentContactOrg`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to add an incident contact");
return undefined;
});
export default addIncidentContact;

View File

@@ -1,27 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This function change the access of a user in a certain organization
* @param {string} organizationId
* @param {string} membershipId
* @param {string} role
* @returns
*/
const changeUserRoleInOrganization = (organizationId: string, membershipId: string, role: string) =>
SecurityClient.fetchCall(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
role
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to change the user role in an org");
return undefined;
});
export default changeUserRoleInOrganization;

View File

@@ -1,25 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route deletes an incident Contact from a certain organization
* @param {*} param0
* @returns
*/
const deleteIncidentContact = (organizationId: string, email: string) =>
SecurityClient.fetchCall(`/api/v1/organization/${organizationId}/incidentContactOrg`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to delete an incident contact");
return undefined;
});
export default deleteIncidentContact;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This function removes a certain member from a certain organization
* @param {*} membershipId
* @returns
*/
const deleteUserFromOrganization = (membershipId: string) =>
SecurityClient.fetchCall(`/api/v1/membership-org/${membershipId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to delete a user from an org");
return undefined;
});
export default deleteUserFromOrganization;

View File

@@ -1,27 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
export interface IIncidentContactOrg {
_id: string;
email: string;
organization: string;
}
/**
* This routes gets all the incident contacts of a certain organization
* @param {*} workspaceId
* @returns
*/
const getIncidentContacts = (organizationId: string): Promise<IIncidentContactOrg[]> =>
SecurityClient.fetchCall(`/api/v1/organization/${organizationId}/incidentContactOrg`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return (await res.json()).incidentContactsOrg;
}
console.log("Failed to get incident contacts");
return undefined;
});
export default getIncidentContacts;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us rename a certain org.
* @param {*} req
* @param {*} res
* @returns
*/
const renameOrg = (orgId: string, newOrgName: string) =>
SecurityClient.fetchCall(`/api/v1/organization/${orgId}/name`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
name: newOrgName
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to rename an organization");
return undefined;
});
export default renameOrg;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This function adds a user to a project
* @param {*} email
* @param {*} workspaceId
* @returns
*/
const addUserToWorkspace = (email: string, workspaceId: string) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/invite-signup`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email
})
}).then(async (res) => {
if (res && res.status === 200) {
return res.json();
}
console.log("Failed to add a user to project");
return undefined;
});
export default addUserToWorkspace;

View File

@@ -1,26 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This function change the access of a user in a certain workspace
* @param {*} membershipId
* @param {*} role
* @returns
*/
const changeUserRoleInWorkspace = (membershipId: string, role: string) =>
SecurityClient.fetchCall(`/api/v1/membership/${membershipId}/change-role`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
role
})
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to change the user role in a project");
return undefined;
});
export default changeUserRoleInWorkspace;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This function removes a certain member from a certain workspace
* @param {*} membershipId
* @returns
*/
const deleteUserFromWorkspace = (membershipId: string) =>
SecurityClient.fetchCall(`/api/v1/membership/${membershipId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to delete a user from a project");
return undefined;
});
export default deleteUserFromWorkspace;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route deletes a specified workspace.
* @param {*} workspaceId
* @returns
*/
const deleteWorkspace = (workspaceId: string) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res && res.status === 200) {
return res;
}
console.log("Failed to delete a project");
return undefined;
});
export default deleteWorkspace;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get the public keys of everyone in your workspace.
* @param {string} workspaceId
* @returns
*/
const getWorkspaceKeys = ({ workspaceId }: { workspaceId: string }) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/keys`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res?.status === 200) {
return (await res.json()).publicKeys;
}
console.log("Failed to get the public keys of everyone in the workspace");
return undefined;
});
export default getWorkspaceKeys;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get the tags for a certain project
* @param {string} workspaceId
* @returns
*/
const getWorkspaceTags = ({ workspaceId }: { workspaceId: string }) =>
SecurityClient.fetchCall(`/api/v2/workspace/${workspaceId}/tags`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res?.status === 200) {
return (await res.json()).workspaceTags;
}
console.log("Failed to get the tags available in a certain project");
return undefined;
});
export default getWorkspaceTags;

View File

@@ -1,22 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
/**
* This route lets us get all the users in the workspace.
* @param {string} workspaceId - workspace ID
* @returns
*/
const getWorkspaceUsers = ({ workspaceId }: { workspaceId: string }) =>
SecurityClient.fetchCall(`/api/v1/workspace/${workspaceId}/users`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res?.status === 200) {
return (await res.json()).users;
}
console.log("Failed to get Project Users");
return undefined;
});
export default getWorkspaceUsers;

View File

@@ -1,31 +0,0 @@
import SecurityClient from "@app/components/utilities/SecurityClient";
interface Workspace {
__v: number;
_id: string;
name: string;
autoCapitalization: boolean;
organization: string;
environments: Array<{ name: string; slug: string }>;
}
/**
* This route lets us get the workspaces of a certain user
* @returns
*/
const getWorkspaces = () =>
SecurityClient.fetchCall("/api/v1/workspace", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
}).then(async (res) => {
if (res?.status === 200) {
const data = (await res.json()) as unknown as { workspaces: Workspace[] };
return data.workspaces;
}
throw new Error("Failed to get projects");
});
export default getWorkspaces;

View File

@@ -11,15 +11,13 @@ import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMembe
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
import guidGenerator from "@app/components/utilities/randomId";
import { Input } from "@app/components/v2";
import { useGetUser } from "@app/hooks/api";
import { useAddUserToWorkspace,useGetUser , useGetWorkspaceUsers } from "@app/hooks/api";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../../../components/utilities/cryptography/crypto";
import getOrganizationUsers from "../../../api/organization/GetOrgUsers";
import addUserToWorkspace from "../../../api/workspace/addUserToWorkspace";
import getWorkspaceUsers from "../../../api/workspace/getWorkspaceUsers";
import uploadKeys from "../../../api/workspace/uploadKeys";
interface UserProps {
@@ -42,7 +40,13 @@ interface MembershipProps {
// #TODO: Update all the workspaceIds
export default function Users() {
const router = useRouter();
const workspaceId = router.query.id as string;
const { data: user } = useGetUser();
const { data: workspaceUsers } = useGetWorkspaceUsers(workspaceId);
const { mutateAsync: addUserToWorkspaceMutateAsync } = useAddUserToWorkspace();
const [isAddOpen, setIsAddOpen] = useState(false);
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
@@ -52,22 +56,16 @@ export default function Users() {
const { t } = useTranslation();
const router = useRouter();
const workspaceId = router.query.id as string;
const [userList, setUserList] = useState<any[]>([]);
const [isUserListLoading, setIsUserListLoading] = useState(true);
const [orgUserList, setOrgUserList] = useState<any[]>([]);
useEffect(() => {
if (user) {
if (user && workspaceUsers) {
(async () => {
setPersonalEmail(user.email);
// This part quiries the current users of a project
const workspaceUsers = await getWorkspaceUsers({
workspaceId
});
const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({
key: guidGenerator(),
firstName: membership.user?.firstName,
@@ -100,7 +98,7 @@ export default function Users() {
);
})();
}
}, [user]);
}, [user, workspaceUsers]);
const closeAddModal = () => {
setIsAddOpen(false);
@@ -123,7 +121,11 @@ export default function Users() {
// }
const submitAddModal = async () => {
const result = await addUserToWorkspace(email, workspaceId);
const result = await addUserToWorkspaceMutateAsync({
email,
workspaceId
});
if (result?.invitee && result?.latestKey) {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string;
@@ -145,7 +147,6 @@ export default function Users() {
}
setEmail("");
setIsAddOpen(false);
router.reload();
};
return userList ? (