feat(integration-page): implemented new optimized integrations page

This commit is contained in:
akhilmhdh
2023-06-24 23:31:37 +05:30
parent c04ea7e731
commit 0ef4ac1cdc
10 changed files with 695 additions and 444 deletions
@@ -0,0 +1,140 @@
import { useTranslation } from 'react-i18next';
import { faCheck, faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DeleteActionModal,Skeleton, Tooltip } from '@app/components/v2';
import { usePopUp } from '@app/hooks';
import { IntegrationAuth, TCloudIntegration } from '@app/hooks/api/types';
type Props = {
isLoading?: boolean;
integrationAuths?: Record<string, IntegrationAuth>;
cloudIntegrations?: TCloudIntegration[];
onIntegrationStart: (slug: string) => void;
// cb: handle popUpClose child->parent communication pattern
onIntegrationRevoke: (slug: string, cb: () => void) => void;
};
type TRevokeIntegrationPopUp = { provider: string };
export const CloudIntegrationSection = ({
isLoading,
cloudIntegrations = [],
integrationAuths = {},
onIntegrationStart,
onIntegrationRevoke
}: Props) => {
const { t } = useTranslation();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
'deleteConfirmation'
] as const);
const isEmpty = !isLoading && !cloudIntegrations?.length;
return (
<div>
<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 grid-flow-dense gap-4"
style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(256px, 1fr))' }}
>
{isLoading &&
Array.from({ length: 12 }).map((_, index) => (
<Skeleton className="h-32" key={`cloud-integration-skeleton-${index + 1}`} />
))}
{!isLoading &&
cloudIntegrations?.map((cloudIntegration) => (
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
className={`group relative ${
cloudIntegration.isAvailable
? 'cursor-pointer duration-200 hover:bg-mineshaft-700'
: 'opacity-50'
} flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4`}
onClick={() => {
if (!cloudIntegration.isAvailable) return;
onIntegrationStart(cloudIntegration.slug);
}}
key={cloudIntegration.slug}
>
<img
src={`/images/integrations/${cloudIntegration.image}`}
height={70}
width={70}
alt="integration logo"
/>
{cloudIntegration.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>{cloudIntegration.name.split(' ')[0]}</div>
<div className="text-base">
{cloudIntegration.name.split(' ')[1]} {cloudIntegration.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">
{cloudIntegration.name}
</div>
)}
{cloudIntegration.isAvailable &&
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
<div className="absolute top-0 right-0 z-40 h-full">
<div className="relative h-full">
<div className="absolute top-0 right-0 w-24 flex-row items-center overflow-hidden whitespace-nowrap rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-80 transition-all duration-300 group-hover:w-0 group-hover:p-0">
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
Authorized
</div>
<Tooltip content="Revoke Access">
<div
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={async (event) => {
event.stopPropagation();
handlePopUpOpen('deleteConfirmation', {
provider: cloudIntegration.slug
});
}}
className="absolute top-0 right-0 flex h-0 w-12 cursor-pointer items-center justify-center overflow-hidden rounded-r-md bg-red text-xs opacity-50 transition-all duration-300 hover:opacity-100 group-hover:h-full"
>
<FontAwesomeIcon icon={faXmark} size="xl" />
</div>
</Tooltip>
</div>
</div>
)}
</div>
))}
</div>
{isEmpty && (
<div className="mx-6 grid max-w-5xl grid-cols-4 grid-rows-2 gap-4">
{Array.from({ length: 16 }).map((_, index) => (
<div
key={`dummy-cloud-integration-${index + 1}`}
className="h-32 animate-pulse rounded-md border border-mineshaft-600 bg-mineshaft-800"
/>
))}
</div>
)}
<DeleteActionModal
isOpen={popUp.deleteConfirmation.isOpen}
title={`Are you sure want to revoke access ${
(popUp?.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider || ' '
}?`}
subTitle="This will remove all the secret integration of this provider!!!"
onChange={(isOpen) => handlePopUpToggle('deleteConfirmation', isOpen)}
deleteKey={(popUp?.deleteConfirmation?.data as TRevokeIntegrationPopUp)?.provider || ''}
onDeleteApproved={async () => {
onIntegrationRevoke(
(popUp.deleteConfirmation.data as TRevokeIntegrationPopUp)?.provider,
() => handlePopUpClose('deleteConfirmation')
);
}}
/>
</div>
);
};
@@ -0,0 +1 @@
export { CloudIntegrationSection } from './CloudIntegrationSection';
@@ -0,0 +1,54 @@
import { useTranslation } from 'react-i18next';
type Props = {
frameworks: Array<{
name: string;
image: string;
slug: string;
docsLink: string;
}>;
};
export const FrameworkIntegrationSection = ({ frameworks }: Props) => {
const { t } = useTranslation();
return (
<>
<div className="mx-4 mt-12 mb-4 flex 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 grid-flow-dense gap-4"
style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))' }}
>
{frameworks.map((framework) => (
<a
key={`framework-integration-${framework.slug}`}
href={framework.docsLink}
rel="noopener noreferrer"
target="_blank"
className="relative flex h-32 cursor-pointer flex-row items-center justify-center rounded-md p-0.5 duration-200"
>
<div
className={`flex h-full w-full cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 font-semibold text-gray-300 duration-200 hover:bg-mineshaft-700 group-hover:text-gray-200 ${
framework?.name?.split(' ').length > 1 ? 'px-1 text-sm' : 'px-2 text-xl'
} w-full max-w-xs text-center`}
>
{framework?.image && (
<img
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>
))}
</div>
</>
);
};
@@ -0,0 +1 @@
export { FrameworkIntegrationSection } from './FrameworkIntegrationSection';
@@ -0,0 +1,145 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { integrationSlugNameMapping } from 'public/data/frequentConstants';
import {
DeleteActionModal,
EmptyState,
FormControl,
FormLabel,
IconButton,
Select,
SelectItem,
Skeleton
} from '@app/components/v2';
import { usePopUp } from '@app/hooks';
import { TIntegration } from '@app/hooks/api/types';
type Props = {
environments: Array<{ name: string; slug: string }>;
integrations?: TIntegration[];
isLoading?: boolean;
onIntegrationDelete: (integration: TIntegration, cb: () => void) => void;
};
export const IntegrationsSection = ({
integrations = [],
environments = [],
isLoading,
onIntegrationDelete
}: Props) => {
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
'deleteConfirmation'
] as const);
return (
<div className="mb-8">
<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>
{isLoading && (
<div className="p-6 pt-0">
<Skeleton className="h-28" />
</div>
)}
{!isLoading && !integrations.length && (
<EmptyState
className="mx-6 py-8"
title="No integrations found. Click on one of the below providers to sync secrets."
/>
)}
{!isLoading && (
<div className="flex flex-col space-y-4 p-6 pt-0">
{integrations?.map((integration) => (
<div
className="flex max-w-6xl justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 pb-2"
key={`integration-${integration?._id.toString()}`}
>
<div className="flex">
<div>
<FormControl label="Environment">
<Select
value={integration.environment}
isDisabled={integration.isActive}
className="min-w-[8rem]"
>
{environments.map((environment) => {
return (
<SelectItem
value={environment.slug}
key={`environment-${environment.slug}`}
>
{environment.name}
</SelectItem>
);
})}
</Select>
</FormControl>
</div>
<div className="ml-2 flex flex-col">
<FormLabel label="Secret Path" />
<div className="min-w-[8rem] rounded-md bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
{integration.secretPath}
</div>
</div>
<div className="flex h-full items-center">
<FontAwesomeIcon icon={faArrowRight} className="mx-4 text-gray-400" />
</div>
<div className="ml-4 flex flex-col">
<FormLabel label="Integration" />
<div className="min-w-[8rem] rounded-md bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
{integrationSlugNameMapping[integration.integration]}
</div>
</div>
<div className="ml-2 flex flex-col">
<FormLabel label="App" />
<div className="min-w-[8rem] rounded-md bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
{integration.integration === 'hashicorp-vault'
? `${integration.app} - path: ${integration.path}`
: integration.app}
</div>
</div>
{(integration.integration === 'vercel' ||
integration.integration === 'netlify' ||
integration.integration === 'railway' ||
integration.integration === 'gitlab') && (
<div className="ml-4 flex flex-col">
<FormLabel label="Target Environment" />
<div className="rounded-md bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
{integration.targetEnvironment}
</div>
</div>
)}
</div>
<div className="flex cursor-default items-center">
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
<IconButton
onClick={() => handlePopUpOpen('deleteConfirmation', integration)}
ariaLabel="delete"
colorSchema="danger"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
</div>
</div>
))}
</div>
)}
<DeleteActionModal
isOpen={popUp.deleteConfirmation.isOpen}
title={`Are you sure want to remove ${
(popUp?.deleteConfirmation.data as TIntegration)?.integrationAuth || ' '
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || ' '}?`}
onChange={(isOpen) => handlePopUpToggle('deleteConfirmation', isOpen)}
deleteKey={(popUp?.deleteConfirmation?.data as TIntegration)?.app || ''}
onDeleteApproved={async () =>
onIntegrationDelete(popUp?.deleteConfirmation.data as TIntegration, () =>
handlePopUpClose('deleteConfirmation')
)
}
/>
</div>
);
};
@@ -0,0 +1 @@
export { IntegrationsSection } from './IntegrationsSection';