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
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;