Files
twenty_crm/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx
gitstart-app[bot] 0a28c15747 Migrate to twenty-ui - input/button (#7994)
This PR was created by [GitStart](https://gitstart.com/) to address the
requirements from this ticket:
[TWNTY-7529](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-7529).

 --- 

### Description

- Migrated all button components to `twenty-ui`    \
  \
  `Button`\
  `ButtonGroup`\
  `ColorPickerButton`\
  `FloatingButton`\
  `FloatingButtonGroup`\
  `FloatingIconButton`\
  `FloatingIconButtonGroup`\
  `IconButton`\
  `IconButtonGroup`\
  `LightButton`\
  `LightIconButton`\
  `LightIconButtonGroup`\
  `MainButton`\
  \
  Fixes twentyhq/private-issues#89

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2024-10-24 13:20:02 +02:00

270 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { DateTime } from 'luxon';
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { Button, H2Title, IconRepeat, IconTrash } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
import { ApiKeyNameInput } from '@/settings/developers/components/ApiKeyNameInput';
import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { computeNewExpirationDate } from '@/settings/developers/utils/computeNewExpirationDate';
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { useGenerateApiKeyTokenMutation } 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 { enqueueSnackBar } = useSnackBar();
const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] =
useState(false);
const [isDeleteApiKeyModalOpen, setIsDeleteApiKeyModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const { apiKeyId = '' } = useParams();
const [apiKeyToken, setApiKeyToken] = useRecoilState(apiKeyTokenState);
const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation();
const { createOneRecord: createOneApiKey } = useCreateOneRecord<ApiKey>({
objectNameSingular: CoreObjectNameSingular.ApiKey,
});
const { updateOneRecord: updateApiKey } = useUpdateOneRecord<ApiKey>({
objectNameSingular: CoreObjectNameSingular.ApiKey,
});
const [apiKeyName, setApiKeyName] = useState('');
const { record: apiKeyData, loading } = useFindOneRecord({
objectNameSingular: CoreObjectNameSingular.ApiKey,
objectRecordId: apiKeyId,
onCompleted: (record) => {
setApiKeyName(record.name);
},
});
const developerPath = getSettingsPagePath(SettingsPath.Developers);
const deleteIntegration = async (redirect = true) => {
setIsLoading(true);
try {
await updateApiKey?.({
idToUpdate: apiKeyId,
updateOneRecordInput: { revokedAt: DateTime.now().toString() },
});
if (redirect) {
navigate(developerPath);
}
} catch (err) {
enqueueSnackBar(`Error deleting api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
setIsLoading(false);
}
};
const createIntegration = async (
name: string,
newExpiresAt: string | null,
) => {
const newApiKey = await createOneApiKey?.({
name: name,
expiresAt: newExpiresAt ?? '',
});
if (!newApiKey) {
return;
}
const tokenData = await generateOneApiKeyToken({
variables: {
apiKeyId: newApiKey.id,
expiresAt: newApiKey?.expiresAt,
},
});
return {
id: newApiKey.id,
token: tokenData.data?.generateApiKeyToken.token,
};
};
const regenerateApiKey = async () => {
setIsLoading(true);
try {
if (isNonEmptyString(apiKeyData?.name)) {
const newExpiresAt = computeNewExpirationDate(
apiKeyData?.expiresAt,
apiKeyData?.createdAt,
);
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt);
await deleteIntegration(false);
if (isNonEmptyString(apiKey?.token)) {
setApiKeyToken(apiKey.token);
navigate(`/settings/developers/api-keys/${apiKey.id}`);
}
}
} catch (err) {
enqueueSnackBar(`Error regenerating api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
setIsLoading(false);
}
};
return (
<>
{apiKeyData?.name && (
<SubMenuTopBarContainer
title={apiKeyData?.name}
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Developers', href: developerPath },
{ children: `${apiKeyName} API Key` },
]}
>
<SettingsPageContainer>
<Section>
{apiKeyToken ? (
<>
<H2Title
title="API Key"
description="Copy this key as it will only be visible this one time"
/>
<ApiKeyInput apiKey={apiKeyToken} />
<StyledInfo>
{formatExpiration(apiKeyData?.expiresAt || '', true, false)}
</StyledInfo>
</>
) : (
<>
<H2Title
title="API Key"
description="Regenerate an API key"
/>
<StyledInputContainer>
<Button
title="Regenerate Key"
Icon={IconRepeat}
onClick={() => setIsRegenerateKeyModalOpen(true)}
/>
<StyledInfo>
{formatExpiration(
apiKeyData?.expiresAt || '',
true,
false,
)}
</StyledInfo>
</StyledInputContainer>
</>
)}
</Section>
<Section>
<H2Title title="Name" description="Name of your API key" />
<ApiKeyNameInput
apiKeyName={apiKeyName}
apiKeyId={apiKeyData?.id}
disabled={loading}
onNameUpdate={setApiKeyName}
/>
</Section>
<Section>
<H2Title
title="Expiration"
description="When the key will be disabled"
/>
<TextInput
placeholder="E.g. backoffice integration"
value={formatExpiration(
apiKeyData?.expiresAt || '',
true,
false,
)}
disabled
fullWidth
/>
</Section>
<Section>
<H2Title
title="Danger zone"
description="Delete this integration"
/>
<Button
accent="danger"
variant="secondary"
title="Delete"
Icon={IconTrash}
onClick={() => setIsDeleteApiKeyModalOpen(true)}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
)}
<ConfirmationModal
confirmationPlaceholder="yes"
confirmationValue="yes"
isOpen={isDeleteApiKeyModalOpen}
setIsOpen={setIsDeleteApiKeyModalOpen}
title="Delete API key"
subtitle={
<>
Please type "yes" to confirm you want to delete this API Key. Be
aware that any script using this key will stop working.
</>
}
onConfirmClick={deleteIntegration}
deleteButtonText="Delete"
loading={isLoading}
/>
<ConfirmationModal
confirmationPlaceholder="yes"
confirmationValue="yes"
isOpen={isRegenerateKeyModalOpen}
setIsOpen={setIsRegenerateKeyModalOpen}
title="Regenerate an API key"
subtitle={
<>
If youve lost this key, you can regenerate it, but be aware that
any script using this key will need to be updated. Please type "yes"
to confirm.
</>
}
onConfirmClick={regenerateApiKey}
deleteButtonText="Regenerate key"
loading={isLoading}
/>
</>
);
};