Files
twenty/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
2023-11-15 15:57:43 +01:00

195 lines
6.2 KiB
TypeScript

import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date';
import { formatExpiration } from '@/settings/developers/utils/format-expiration';
import { IconRepeat, IconSettings, IconTrash } from '@/ui/display/icon';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import {
useDeleteOneApiKeyMutation,
useGetApiKeyQuery,
useInsertOneApiKeyMutation,
} from '~/generated/graphql';
const StyledInfo = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.sm};
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
const StyledInputContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export const SettingsDevelopersApiKeyDetail = () => {
const navigate = useNavigate();
const { apiKeyId = '' } = useParams();
const { triggerOptimisticEffects } = useOptimisticEffect('ApiKeyV2');
const setGeneratedApi = useGeneratedApiKeys();
const [generatedApiKey] = useRecoilState(
generatedApiKeyFamilyState(apiKeyId),
);
const [deleteApiKey] = useDeleteOneApiKeyMutation();
const [insertOneApiKey] = useInsertOneApiKeyMutation();
const apiKeyData = useGetApiKeyQuery({
variables: {
apiKeyId,
},
}).data?.findManyApiKey[0];
const deleteIntegration = async (redirect = true) => {
await deleteApiKey({
variables: { apiKeyId },
update: (cache) =>
cache.evict({
id: cache.identify({ __typename: 'ApiKey', id: apiKeyId }),
}),
});
if (redirect) {
navigate('/settings/developers/api-keys');
}
};
const createIntegration = async (
name: string,
newExpiresAt: string | null,
) => {
return await insertOneApiKey({
variables: {
data: {
name: name,
expiresAt: newExpiresAt,
},
},
update: (_cache, { data }) => {
if (data?.createOneApiKey) {
triggerOptimisticEffects('ApiKey', [data?.createOneApiKey]);
}
},
});
};
const regenerateApiKey = async () => {
if (apiKeyData?.name) {
const newExpiresAt = computeNewExpirationDate(
apiKeyData.expiresAt,
apiKeyData.createdAt,
);
const apiKey = await createIntegration(apiKeyData.name, newExpiresAt);
await deleteIntegration(false);
if (apiKey.data?.createOneApiKey) {
setGeneratedApi(
apiKey.data.createOneApiKey.id,
apiKey.data.createOneApiKey.token,
);
navigate(
`/settings/developers/api-keys/${apiKey.data.createOneApiKey.id}`,
);
}
}
};
useEffect(() => {
if (apiKeyData) {
return () => {
setGeneratedApi(apiKeyId, null);
};
}
});
return (
<>
{apiKeyData?.name && (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'APIs', href: '/settings/developers/api-keys' },
{ children: apiKeyData.name },
]}
/>
</SettingsHeaderContainer>
<Section>
{generatedApiKey ? (
<>
<H2Title
title="Api Key"
description="Copy this key as it will only be visible this one time"
/>
<ApiKeyInput apiKey={generatedApiKey} />
<StyledInfo>
{formatExpiration(apiKeyData?.expiresAt || '', true, false)}
</StyledInfo>
</>
) : (
<>
<H2Title
title="Api Key"
description="Regenerate an Api key"
/>
<StyledInputContainer>
<Button
title="Regenerate Key"
Icon={IconRepeat}
onClick={regenerateApiKey}
/>
<StyledInfo>
{formatExpiration(
apiKeyData?.expiresAt || '',
true,
false,
)}
</StyledInfo>
</StyledInputContainer>
</>
)}
</Section>
<Section>
<H2Title title="Name" description="Name of your API key" />
<TextInput
placeholder="E.g. backoffice integration"
value={apiKeyData.name}
disabled
fullWidth
/>
</Section>
<Section>
<H2Title
title="Danger zone"
description="Delete this integration"
/>
<Button
accent="danger"
variant="secondary"
title="Disable"
Icon={IconTrash}
onClick={() => deleteIntegration()}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
)}
</>
);
};