2062 view edit an api key (#2231)
* Add query to get api keys * Add a link to apiKey detail page * Reset generatedApiKey when leaving page * Simplify stuff * Regenerate key when clicking on button * Simplify * Fix test * Refetch apiKeys when delete or create one * Add test for utils * Create utils function * Enable null expiration dates * Update formatExpiration * Fix display * Fix noteCard * Fix errors * Fix reset * Fix display * Fix renaming * Fix tests * Fix ci * Fix mocked data * Fix test * Update coverage requiremeents * Rename folder * Code review returns * Symplify sht code
This commit is contained in:
@ -1,11 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||
import { generatedApiKeyState } from '@/settings/developers/states/generatedApiKeyState';
|
||||
import { IconSettings, IconTrash } from '@/ui/display/icon';
|
||||
import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys';
|
||||
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';
|
||||
@ -15,62 +22,159 @@ 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 [generatedApiKey] = useRecoilState(generatedApiKeyState);
|
||||
const apiKeyQuery = useGetApiKeyQuery({
|
||||
|
||||
const setGeneratedApi = useGeneratedApiKeys();
|
||||
const [generatedApiKey] = useRecoilState(
|
||||
generatedApiKeyFamilyState(apiKeyId),
|
||||
);
|
||||
|
||||
const [deleteApiKey] = useDeleteOneApiKeyMutation();
|
||||
const [insertOneApiKey] = useInsertOneApiKeyMutation();
|
||||
const apiKeyData = useGetApiKeyQuery({
|
||||
variables: {
|
||||
apiKeyId,
|
||||
},
|
||||
});
|
||||
const [deleteApiKey] = useDeleteOneApiKeyMutation();
|
||||
const deleteIntegration = async () => {
|
||||
await deleteApiKey({ variables: { apiKeyId } });
|
||||
navigate('/settings/developers/api-keys');
|
||||
}).data?.findManyApiKey[0];
|
||||
|
||||
const deleteIntegration = async (redirect = true) => {
|
||||
await deleteApiKey({
|
||||
variables: { apiKeyId },
|
||||
refetchQueries: [getOperationName(GET_API_KEYS) ?? ''],
|
||||
});
|
||||
if (redirect) {
|
||||
navigate('/settings/developers/api-keys');
|
||||
}
|
||||
};
|
||||
const { expiresAt, name } = apiKeyQuery.data?.findManyApiKey[0] || {};
|
||||
|
||||
const regenerateApiKey = async () => {
|
||||
if (apiKeyData?.name) {
|
||||
const newExpiresAt = computeNewExpirationDate(
|
||||
apiKeyData.expiresAt,
|
||||
apiKeyData.createdAt,
|
||||
);
|
||||
const apiKey = await insertOneApiKey({
|
||||
variables: {
|
||||
data: {
|
||||
name: apiKeyData.name,
|
||||
expiresAt: newExpiresAt,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_API_KEYS) ?? ''],
|
||||
});
|
||||
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 (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
<SettingsHeaderContainer>
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{ children: 'APIs', href: '/settings/developers/api-keys' },
|
||||
{ children: name || '' },
|
||||
]}
|
||||
/>
|
||||
</SettingsHeaderContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Api Key"
|
||||
description="Copy this key as it will only be visible this one time"
|
||||
/>
|
||||
<ApiKeyInput expiresAt={expiresAt} apiKey={generatedApiKey || ''} />
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title title="Name" description="Name of your API key" />
|
||||
<TextInput
|
||||
placeholder="E.g. backoffice integration"
|
||||
value={name || ''}
|
||||
disabled={true}
|
||||
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>
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { objectSettingsWidth } from '@/settings/data-model/constants/objectSettings';
|
||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||
import { activeApiKeyItems } from '@/settings/developers/constants/mockObjects';
|
||||
import { formatExpirations } from '@/settings/developers/utils/format-expiration';
|
||||
import { IconPlus, IconSettings } from '@/ui/display/icon';
|
||||
import { H1Title } from '@/ui/display/typography/components/H1Title';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
@ -12,6 +12,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useGetApiKeysQuery } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
height: fit-content;
|
||||
@ -36,6 +37,8 @@ const StyledH1Title = styled(H1Title)`
|
||||
|
||||
export const SettingsDevelopersApiKeys = () => {
|
||||
const navigate = useNavigate();
|
||||
const apiKeysQuery = useGetApiKeysQuery();
|
||||
const apiKeys = apiKeysQuery.data ? formatExpirations(apiKeysQuery.data) : [];
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
@ -63,10 +66,13 @@ export const SettingsDevelopersApiKeys = () => {
|
||||
<TableHeader>Expiration</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
{activeApiKeyItems.map((fieldItem) => (
|
||||
{apiKeys.map((fieldItem) => (
|
||||
<SettingsApiKeysFieldItemTableRow
|
||||
key={fieldItem.id}
|
||||
fieldItem={fieldItem}
|
||||
onClick={() => {
|
||||
navigate(`/settings/developers/api-keys/${fieldItem.id}`);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { ExpirationDates } from '@/settings/developers/constants/expirationDates';
|
||||
import { generatedApiKeyState } from '@/settings/developers/states/generatedApiKeyState';
|
||||
import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys';
|
||||
import { useGeneratedApiKeys } from '@/settings/developers/hooks/useGeneratedApiKeys';
|
||||
import { IconSettings } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
@ -20,10 +21,10 @@ import { useInsertOneApiKeyMutation } from '~/generated/graphql';
|
||||
export const SettingsDevelopersApiKeysNew = () => {
|
||||
const [insertOneApiKey] = useInsertOneApiKeyMutation();
|
||||
const navigate = useNavigate();
|
||||
const [, setGeneratedApiKey] = useRecoilState(generatedApiKeyState);
|
||||
const setGeneratedApi = useGeneratedApiKeys();
|
||||
const [formValues, setFormValues] = useState<{
|
||||
name: string;
|
||||
expirationDate: number;
|
||||
expirationDate: number | null;
|
||||
}>({
|
||||
expirationDate: ExpirationDates[0].value,
|
||||
name: '',
|
||||
@ -33,16 +34,24 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
variables: {
|
||||
data: {
|
||||
name: formValues.name,
|
||||
expiresAt: DateTime.now()
|
||||
.plus({ days: formValues.expirationDate })
|
||||
.toISODate(),
|
||||
expiresAt: formValues.expirationDate
|
||||
? DateTime.now()
|
||||
.plus({ days: formValues.expirationDate })
|
||||
.toISODate()
|
||||
: null,
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_API_KEYS) ?? ''],
|
||||
});
|
||||
setGeneratedApiKey(apiKey.data?.createOneApiKey?.token);
|
||||
navigate(
|
||||
`/settings/developers/api-keys/${apiKey.data?.createOneApiKey?.id}`,
|
||||
);
|
||||
if (apiKey.data?.createOneApiKey) {
|
||||
setGeneratedApi(
|
||||
apiKey.data.createOneApiKey.id,
|
||||
apiKey.data.createOneApiKey.token,
|
||||
);
|
||||
navigate(
|
||||
`/settings/developers/api-keys/${apiKey.data.createOneApiKey.id}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
const canSave = !!formValues.name;
|
||||
return (
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedApiKeyToken } from '~/testing/mock-data/api-keys';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
@ -15,7 +14,6 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
decorators: [PageDecorator],
|
||||
args: {
|
||||
routePath: '/settings/apis/f7c6d736-8fcd-4e9c-ab99-28f6a9031570',
|
||||
state: mockedApiKeyToken,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
|
||||
Reference in New Issue
Block a user